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

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

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 420 B

View File

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

After

Width:  |  Height:  |  Size: 412 B

View File

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

View File

@ -279,6 +279,28 @@ Returns the legend symbol width.
Sets the legend symbol ``width``. Sets the legend symbol ``width``.
.. seealso:: :py:func:`symbolWidth` .. 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 %End
double symbolHeight() const; double symbolHeight() const;

View File

@ -32,18 +32,14 @@ in QgsLegendModel class.
Qt::AlignmentFlag titleAlignment() const; Qt::AlignmentFlag titleAlignment() const;
%Docstring %Docstring
Returns the alignment of the legend title Returns the alignment of the legend title.
:return: Qt.AlignmentFlag for the legend title
.. seealso:: :py:func:`setTitleAlignment` .. seealso:: :py:func:`setTitleAlignment`
%End %End
void setTitleAlignment( Qt::AlignmentFlag alignment ); void setTitleAlignment( Qt::AlignmentFlag alignment );
%Docstring %Docstring
Sets the alignment of the legend title Sets the ``alignment`` of the legend title.
:param alignment: Text alignment for drawing the legend title
.. seealso:: :py:func:`titleAlignment` .. seealso:: :py:func:`titleAlignment`
%End %End
@ -102,6 +98,28 @@ Overrides fontColor()
QSizeF symbolSize() const; QSizeF symbolSize() const;
void setSymbolSize( QSizeF s ); 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; bool drawRasterStroke() const;
%Docstring %Docstring
Returns whether a stroke will be drawn around raster symbol items. Returns whether a stroke will be drawn around raster symbol items.

View File

@ -58,6 +58,24 @@ The font for this style.
void setMargin( double margin ); void setMargin( double margin );
%Docstring %Docstring
Sets all margins 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 %End
void writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const; void writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const;

View File

@ -49,6 +49,18 @@ Sets the current ``alignment`` choice.
.. seealso:: :py:func:`currentAlignment` .. seealso:: :py:func:`currentAlignment`
%End %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: signals:
void changed(); void changed();

View File

@ -195,6 +195,16 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
// try to find a good map to link the legend with by default // try to find a good map to link the legend with by default
legend->setLinkedMap( findSensibleDefaultLinkedMapItem( legend ) ); 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(); legend->updateLegend();
} ); } );

View File

@ -70,7 +70,10 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
setupUi( this ); setupUi( this );
connect( mWrapCharLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged ); connect( mWrapCharLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged );
connect( mTitleLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mTitleLineEdit_textChanged ); connect( mTitleLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mTitleLineEdit_textChanged );
connect( mTitleAlignCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutLegendWidget::mTitleAlignCombo_currentIndexChanged ); connect( mTitleAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::titleAlignmentChanged );
connect( mGroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::groupAlignmentChanged );
connect( mSubgroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::subgroupAlignmentChanged );
connect( mItemAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::itemAlignmentChanged );
connect( mColumnCountSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged ); connect( mColumnCountSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged );
connect( mSplitLayerCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled ); connect( mSplitLayerCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled );
connect( mEqualColumnWidthCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled ); connect( mEqualColumnWidthCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled );
@ -111,6 +114,16 @@ QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend )
mLayerFontButton->setMode( QgsFontButton::ModeQFont ); mLayerFontButton->setMode( QgsFontButton::ModeQFont );
mItemFontButton->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 // setup icons
mAddToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) ); mAddToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
mEditPushButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) ); mEditPushButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
@ -168,11 +181,13 @@ void QgsLayoutLegendWidget::setGuiElements()
return; return;
} }
int alignment = mLegend->titleAlignment() == Qt::AlignLeft ? 0 : mLegend->titleAlignment() == Qt::AlignHCenter ? 1 : 2;
blockAllSignals( true ); blockAllSignals( true );
mTitleLineEdit->setText( mLegend->title() ); 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() ); mFilterByMapToolButton->setChecked( mLegend->legendFilterByMapEnabled() );
mColumnCountSpinBox->setValue( mLegend->columnCount() ); mColumnCountSpinBox->setValue( mLegend->columnCount() );
mSplitLayerCheckBox->setChecked( mLegend->splitLayer() ); 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 ) 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->beginCommand( tr( "Change Title Alignment" ) );
mLegend->setTitleAlignment( alignment ); mLegend->setTitleAlignment( alignment );
mLegend->update(); 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 ) void QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged( int c )
{ {
if ( mLegend ) if ( mLegend )
@ -1053,7 +1113,7 @@ void QgsLayoutLegendWidget::setCurrentNodeStyleFromAction()
if ( !a || !mItemTreeView->currentNode() ) if ( !a || !mItemTreeView->currentNode() )
return; return;
QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), ( QgsLegendStyle::Style ) a->data().toInt() ); QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), static_cast< QgsLegendStyle::Style >( a->data().toInt() ) );
mLegend->updateFilterByMap(); mLegend->updateFilterByMap();
} }
@ -1175,7 +1235,7 @@ QMenu *QgsLayoutLegendMenuProvider::createContextMenu()
QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction ); QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction );
action->setCheckable( true ); action->setCheckable( true );
action->setChecked( currentStyle == style ); action->setChecked( currentStyle == style );
action->setData( ( int ) style ); action->setData( static_cast< int >( style ) );
} }
return menu; return menu;

View File

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

View File

@ -84,8 +84,27 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &setting
return QSizeF(); return QSizeF();
if ( ctx && ctx->painter ) 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(); return settings.symbolSize();
} }
@ -117,13 +136,34 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings &set
labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * ( settings.lineSpacing() + textDescent ); 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 ) if ( ctx && ctx->painter )
{ {
ctx->painter->setPen( settings.fontColor() ); 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 ); case Qt::AlignRight:
labelY = ctx->point.y(); 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 // Vertical alignment of label with symbol
if ( labelSize.height() < symbolSize.height() ) 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 ) 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 ) 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 ) ) if ( itemPart != ( lines.end() - 1 ) )
labelY += textDescent + settings.lineSpacing() + textHeight; labelY += textDescent + settings.lineSpacing() + textHeight;
} }
@ -462,8 +518,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
if ( ctx && ctx->painter ) if ( ctx && ctx->painter )
{ {
double currentXPosition = ctx->point.x(); double currentYCoord = ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2;
double currentYCoord = ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2;
QPainter *p = ctx->painter; QPainter *p = ctx->painter;
//setup painter scaling to dots so that raster symbology is drawn to scale //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->save();
p->setRenderHint( QPainter::Antialiasing ); 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 ); p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
if ( opacity != 255 && settings.useAdvancedEffects() ) if ( opacity != 255 && settings.useAdvancedEffects() )
{ {
@ -659,8 +725,19 @@ QSizeF QgsImageLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemCo
if ( ctx && ctx->painter ) if ( ctx && ctx->painter )
{ {
ctx->painter->drawImage( QRectF( ctx->point.x(), ctx->point.y(), settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ), switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawImage( QRectF( ctx->columnLeft, ctx->top, settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) ); 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(); return settings.wmsLegendSize();
} }
@ -724,8 +801,19 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings,
ctx->painter->setPen( Qt::NoPen ); ctx->painter->setPen( Qt::NoPen );
} }
ctx->painter->drawRect( QRectF( ctx->point.x(), ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2, switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawRect( QRectF( ctx->columnLeft, ctx->top + ( itemHeight - settings.symbolSize().height() ) / 2,
settings.symbolSize().width(), settings.symbolSize().height() ) ); 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(); return settings.symbolSize();
} }
@ -829,9 +917,27 @@ QSizeF QgsWmsLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemCont
if ( ctx && ctx->painter ) if ( ctx && ctx->painter )
{ {
ctx->painter->drawImage( QRectF( ctx->point, settings.wmsLegendSize() ), switch ( settings.symbolAlignment() )
{
case Qt::AlignLeft:
default:
ctx->painter->drawImage( QRectF( ctx->columnLeft,
ctx->top,
settings.wmsLegendSize().width(),
settings.wmsLegendSize().height() ),
mImage, mImage,
QRectF( QPointF( 0, 0 ), mImage.size() ) ); 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(); return settings.wmsLegendSize();
} }
@ -958,7 +1064,7 @@ QgsLayerTreeModelLegendNode::ItemMetrics QgsDataDefinedSizeLegendNode::draw( con
context.setPainter( ctx->painter ); context.setPainter( ctx->painter );
ctx->painter->save(); ctx->painter->save();
ctx->painter->setRenderHint( QPainter::Antialiasing ); 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() ); ctx->painter->scale( 1 / context.scaleFactor(), 1 / context.scaleFactor() );
} }

View File

@ -86,14 +86,56 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
struct ItemContext struct ItemContext
{ {
Q_NOWARN_DEPRECATED_PUSH //because of deprecated members
ItemContext() = default;
Q_NOWARN_DEPRECATED_POP
//! Render context, if available //! Render context, if available
QgsRenderContext *context = nullptr; QgsRenderContext *context = nullptr;
//! Painter //! Painter
QPainter *painter = nullptr; QPainter *painter = nullptr;
//! Top-left corner of the legend item
QPointF point; /**
//! offset from the left side where label should start * Top-left corner of the legend item.
double labelXOffset; * \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 struct ItemMetrics

View File

@ -139,6 +139,7 @@ void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsIt
attemptResize( newSize ); attemptResize( newSize );
} }
} }
QgsLayoutItem::paint( painter, itemStyle, pWidget ); QgsLayoutItem::paint( painter, itemStyle, pWidget );
} }
@ -366,6 +367,16 @@ void QgsLayoutItemLegend::setSymbolWidth( double w )
mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) ); 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 double QgsLayoutItemLegend::symbolHeight() const
{ {
return mSettings.symbolSize().height(); 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( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
legendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) ); 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( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
legendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() ); 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.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.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.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() ); mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
@ -776,7 +793,7 @@ void QgsLayoutItemLegend::doUpdateFilterByMap()
if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) ) if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
{ {
int dpi = mLayout->renderContext().dpi(); double dpi = mLayout->renderContext().dpi();
QgsRectangle requestRectangle = mMap->requestedExtent(); QgsRectangle requestRectangle = mMap->requestedExtent();

View File

@ -266,6 +266,26 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
*/ */
void setSymbolWidth( double width ); 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. * Returns the legend symbol height.
* \see setSymbolHeight() * \see setSymbolHeight()

View File

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

View File

@ -119,17 +119,17 @@ class CORE_EXPORT QgsLegendRenderer
#ifndef SIP_RUN #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 * 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 nucleon is just * 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. * the group title, and does not include the actual content of that group.
*/ */
class Nucleon class LegendComponent
{ {
public: public:
//! Constructor for Nuclean
Nucleon() = default; LegendComponent() = default;
QObject *item = nullptr; QObject *item = nullptr;
@ -139,7 +139,7 @@ class CORE_EXPORT QgsLegendRenderer
//! Label size, not including any preset padding space around the label //! Label size, not including any preset padding space around the label
QSizeF labelSize; QSizeF labelSize;
//! Nucleon size //! Component size
QSizeF size; QSizeF size;
/** /**
@ -149,12 +149,18 @@ class CORE_EXPORT QgsLegendRenderer
* within the same legend column. * within the same legend column.
*/ */
double labelXOffset = 0.0; 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 ...] * 1) no layer split: [group_title ...] layer_title layer_item [layer_item ...]
* 2) layer split: [group_title ...] layer_title 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, * 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. * separated from the actual content of that group or layer.
*/ */
class Atom class LegendComponentGroup
{ {
public: public:
//! List of child Nucleons belonging to this Atom. //! List of child components belonging to this group.
QList<Nucleon> nucleons; QList<LegendComponent> components;
//! Atom size, including internal spacing between Nucleons, but excluding any padding space around the Atom itself. //! Group size, including internal spacing between components, but excluding any padding space around the group itself.
QSizeF size = QSizeF( 0, 0 ); QSizeF size = QSizeF( 0, 0 );
//! Corresponding column index //! Corresponding column index
int column = 0; 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. * Draws the legend and returns the actual size of the legend.
* *
@ -188,15 +212,15 @@ class CORE_EXPORT QgsLegendRenderer
QSizeF paintAndDetermineSize( QPainter *painter = nullptr ); 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. * splitting settings.
*/ */
QList<Atom> createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer ); QList<LegendComponentGroup> createComponentGroupList( QgsLayerTreeGroup *parentGroup, bool splitLayer );
/** /**
* Divides a list of Atoms into columns, and sets the column index for each atom in the list. * Divides a list of component groups into columns, and sets the column index for each group in the list.
*/ */
void setColumns( QList<Atom> &atomList ); void setColumns( QList<LegendComponentGroup> &groupList );
/** /**
* Draws a title in the legend using the title font and the specified alignment settings. * 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. * 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. * 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. * 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. * 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. * 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. * Draws a group item.
* Returns the size of the title. * 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. * 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. * 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. * 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. * 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. * 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. * 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. * 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. * Draws a group's title, using the specified render \a context.
* *
* Returns the size of the title. * 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. * Returns the style of the given \a node.
*/ */
QgsLegendStyle::Style nodeLegendStyle( QgsLayerTreeNode *node ); QgsLegendStyle::Style nodeLegendStyle( QgsLayerTreeNode *node );
private:
QgsLayerTreeModel *mLegendModel = nullptr; QgsLayerTreeModel *mLegendModel = nullptr;
QgsLegendSettings mSettings; QgsLegendSettings mSettings;
@ -308,12 +331,14 @@ class CORE_EXPORT QgsLegendRenderer
QSizeF mLegendSize; QSizeF mLegendSize;
#endif #endif
QSizeF drawTitleInternal( QgsRenderContext *context, QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth ); QSizeF drawTitleInternal( QgsRenderContext *context, QPainter *painter, double top, Qt::AlignmentFlag halignment, double legendWidth );
QSizeF drawAtomInternal( const Atom &atom, QgsRenderContext *context, QPainter *painter, QPointF point ); QSizeF drawGroupInternal( const LegendComponentGroup &group, QgsRenderContext *context, const ColumnContext &columnContext, QPainter *painter, double top );
QgsLegendRenderer::Nucleon drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, QPainter *painter, QPointF point, double labelXOffset ); QgsLegendRenderer::LegendComponent drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top, double maxSiblingSymbolWidth );
QSizeF drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, QPainter *painter, QPointF point ); QSizeF drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top );
QSizeF drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, QPainter *painter, QPointF point ); QSizeF drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, const ColumnContext &columnContext, QgsRenderContext *context, QPainter *painter, double top );
QSizeF paintAndDetermineSizeInternal( QgsRenderContext *context, QPainter *painter ); QSizeF paintAndDetermineSizeInternal( QgsRenderContext *context, QPainter *painter );
void widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, double legendWidth, double &width, double &offset );
}; };
#endif // QGSLEGENDRENDERER_H #endif // QGSLEGENDRENDERER_H

View File

@ -44,16 +44,14 @@ class CORE_EXPORT QgsLegendSettings
QString title() const { return mTitle; } QString title() const { return mTitle; }
/** /**
* Returns the alignment of the legend title * Returns the alignment of the legend title.
* \returns Qt::AlignmentFlag for the legend title * \see setTitleAlignment()
* \see setTitleAlignment
*/ */
Qt::AlignmentFlag titleAlignment() const { return mTitleAlignment; } Qt::AlignmentFlag titleAlignment() const { return mTitleAlignment; }
/** /**
* Sets the alignment of the legend title * Sets the \a alignment of the legend title.
* \param alignment Text alignment for drawing the legend title * \see titleAlignment()
* \see titleAlignment
*/ */
void setTitleAlignment( Qt::AlignmentFlag alignment ) { mTitleAlignment = alignment; } void setTitleAlignment( Qt::AlignmentFlag alignment ) { mTitleAlignment = alignment; }
@ -109,6 +107,26 @@ class CORE_EXPORT QgsLegendSettings
QSizeF symbolSize() const {return mSymbolSize;} QSizeF symbolSize() const {return mSymbolSize;}
void setSymbolSize( QSizeF s ) {mSymbolSize = s;} 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. * Returns whether a stroke will be drawn around raster symbol items.
* \see setDrawRasterStroke() * \see setDrawRasterStroke()
@ -327,6 +345,9 @@ class CORE_EXPORT QgsLegendSettings
//! Font color for layers, overrides font color //! Font color for layers, overrides font color
QColor mLayerFontColor; QColor mLayerFontColor;
//! Symbol alignment
Qt::AlignmentFlag mSymbolAlignment = Qt::AlignLeft;
}; };

View File

@ -48,16 +48,22 @@ void QgsLegendStyle::setMargin( double margin )
void QgsLegendStyle::writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const 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" ) ); QDomElement styleElem = doc.createElement( QStringLiteral( "style" ) );
styleElem.setAttribute( QStringLiteral( "name" ), name ); 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[Top], 0.0 ) )
if ( !qgsDoubleNear( mMarginMap[Bottom], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginBottom" ), QString::number( mMarginMap[Bottom] ) ); styleElem.setAttribute( QStringLiteral( "marginTop" ), QString::number( mMarginMap[Top] ) );
if ( !qgsDoubleNear( mMarginMap[Left], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginLeft" ), QString::number( mMarginMap[Left] ) ); if ( !qgsDoubleNear( mMarginMap[Bottom], 0.0 ) )
if ( !qgsDoubleNear( mMarginMap[Right], 0.0 ) ) styleElem.setAttribute( QStringLiteral( "marginRight" ), QString::number( mMarginMap[Right] ) ); 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" ) ) ); 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[Bottom] = elem.attribute( QStringLiteral( "marginBottom" ), QStringLiteral( "0" ) ).toDouble();
mMarginMap[Left] = elem.attribute( QStringLiteral( "marginLeft" ), QStringLiteral( "0" ) ).toDouble(); mMarginMap[Left] = elem.attribute( QStringLiteral( "marginLeft" ), QStringLiteral( "0" ) ).toDouble();
mMarginMap[Right] = elem.attribute( QStringLiteral( "marginRight" ), 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 ) QString QgsLegendStyle::styleName( Style s )

View File

@ -80,6 +80,22 @@ class CORE_EXPORT QgsLegendStyle
//! Sets all margins //! Sets all margins
void setMargin( double margin ); 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 writeXml( const QString &name, QDomElement &elem, QDomDocument &doc ) const;
void readXml( const QDomElement &elem, const QDomDocument &doc ); void readXml( const QDomElement &elem, const QDomDocument &doc );
@ -97,6 +113,8 @@ class CORE_EXPORT QgsLegendStyle
QFont mFont; QFont mFont;
//! Space around element //! Space around element
QMap<Side, double> mMarginMap; QMap<Side, double> mMarginMap;
Qt::Alignment mAlignment = Qt::AlignLeft;
}; };
#endif #endif

View File

@ -48,6 +48,18 @@ void QgsAlignmentComboBox::setCurrentAlignment( Qt::Alignment alignment )
setCurrentIndex( index ); 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() void QgsAlignmentComboBox::populate()
{ {
Qt::Alignment prevAlign = currentAlignment(); Qt::Alignment prevAlign = currentAlignment();

View File

@ -61,6 +61,16 @@ class GUI_EXPORT QgsAlignmentComboBox : public QComboBox
*/ */
void setCurrentAlignment( Qt::Alignment alignment ); 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: signals:
/** /**

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB