Skip to content

Commit 2d7addc

Browse files
authoredApr 19, 2018
Merge pull request #6817 from wonder-sk/legend-text-on-symbols
Legend: optional text on top of symbols for vector layers
2 parents 16465cb + f1a31d0 commit 2d7addc

17 files changed

+642
-3
lines changed
 

‎python/core/layertree/qgslayertreemodellegendnode.sip.in

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,34 @@ to the associated vector layer's renderer.
231231
.. seealso:: :py:func:`symbol`
232232

233233
.. versionadded:: 2.14
234+
%End
235+
236+
QString textOnSymbolLabel() const;
237+
%Docstring
238+
Returns label of text to be shown on top of the symbol.
239+
240+
.. versionadded:: 3.2
241+
%End
242+
243+
void setTextOnSymbolLabel( const QString &label );
244+
%Docstring
245+
Sets label of text to be shown on top of the symbol.
246+
247+
.. versionadded:: 3.2
248+
%End
249+
250+
QgsTextFormat textOnSymbolTextFormat() const;
251+
%Docstring
252+
Returns text format of the label to be shown on top of the symbol.
253+
254+
.. versionadded:: 3.2
255+
%End
256+
257+
void setTextOnSymbolTextFormat( const QgsTextFormat &format );
258+
%Docstring
259+
Sets format of text to be shown on top of the symbol.
260+
261+
.. versionadded:: 3.2
234262
%End
235263

236264
public slots:

‎python/core/qgsmaplayerlegend.sip.in

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212

1313

14+
1415
class QgsMapLayerLegend : QObject
1516
{
1617
%Docstring
@@ -31,6 +32,20 @@ Constructor for QgsMapLayerLegend
3132
%End
3233

3334

35+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
36+
%Docstring
37+
Reads configuration from a DOM element previously written by writeXml()
38+
39+
.. versionadded:: 3.2
40+
%End
41+
42+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
43+
%Docstring
44+
Writes configuration to a DOM element, to be used later with readXml()
45+
46+
.. versionadded:: 3.2
47+
%End
48+
3449
virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) = 0 /Factory/;
3550
%Docstring
3651
Return list of legend nodes to be used for a particular layer tree layer node.
@@ -84,6 +99,7 @@ update according to layer node's custom properties (order of items, user labels
8499

85100

86101

102+
87103
class QgsDefaultVectorLayerLegend : QgsMapLayerLegend
88104
{
89105
%Docstring
@@ -98,8 +114,58 @@ Default legend implementation for vector layers
98114
public:
99115
explicit QgsDefaultVectorLayerLegend( QgsVectorLayer *vl );
100116

117+
bool textOnSymbolEnabled() const;
118+
%Docstring
119+
Returns whether the "text on symbol" functionality is enabled. When enabled, legend symbols
120+
may have extra text rendered on top. The content of labels and their style is controlled
121+
by textOnSymbolContent() and textOnSymbolTextFormat().
122+
123+
.. versionadded:: 3.2
124+
%End
125+
126+
void setTextOnSymbolEnabled( bool enabled );
127+
%Docstring
128+
Sets whether the "text on symbol" functionality is enabled. When enabled, legend symbols
129+
may have extra text rendered on top. The content of labels and their style is controlled
130+
by textOnSymbolContent() and textOnSymbolTextFormat().
131+
132+
.. versionadded:: 3.2
133+
%End
134+
135+
QgsTextFormat textOnSymbolTextFormat() const;
136+
%Docstring
137+
Returns text format of symbol labels for "text on symbol" functionality.
138+
139+
.. versionadded:: 3.2
140+
%End
141+
142+
void setTextOnSymbolTextFormat( const QgsTextFormat &format );
143+
%Docstring
144+
Sets text format of symbol labels for "text on symbol" functionality.
145+
146+
.. versionadded:: 3.2
147+
%End
148+
149+
QHash<QString, QString> textOnSymbolContent() const;
150+
%Docstring
151+
Returns per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
152+
the keys are rule keys of legend items, the values are labels to be shown.
153+
154+
.. versionadded:: 3.2
155+
%End
156+
157+
void setTextOnSymbolContent( const QHash<QString, QString> &content );
158+
%Docstring
159+
Sets per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
160+
the keys are rule keys of legend items, the values are labels to be shown.
161+
162+
.. versionadded:: 3.2
163+
%End
164+
101165
virtual QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/;
102166

167+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
168+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
103169

104170
};
105171

‎python/gui/qgstextformatwidget.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ Constructor for QgsTextFormatWidget.
4848
QgsTextFormat format() const;
4949
%Docstring
5050
Returns the current formatting settings defined by the widget.
51+
%End
52+
53+
void setFormat( const QgsTextFormat &format );
54+
%Docstring
55+
Sets the current formatting settings
56+
57+
.. versionadded:: 3.2
5158
%End
5259

5360
public slots:

‎src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ SET(QGIS_APP_SRCS
133133
qgstextannotationdialog.cpp
134134
qgssvgannotationdialog.cpp
135135
qgsundowidget.cpp
136+
qgsvectorlayerlegendwidget.cpp
136137
qgsvectorlayerproperties.cpp
137138
qgsmapthemes.cpp
138139
qgshandlebadlayers.cpp
@@ -361,6 +362,7 @@ SET (QGIS_APP_MOC_HDRS
361362
qgssvgannotationdialog.h
362363
qgstextannotationdialog.h
363364
qgsundowidget.h
365+
qgsvectorlayerlegendwidget.h
364366
qgsvectorlayerproperties.h
365367
qgsmapthemes.h
366368
qgshandlebadlayers.h
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/***************************************************************************
2+
qgsvectorlayerlegendwidget.cpp
3+
---------------------
4+
Date : April 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsvectorlayerlegendwidget.h"
17+
18+
#include <QBoxLayout>
19+
#include <QStandardItemModel>
20+
#include <QTreeView>
21+
22+
#include "qgsexpressionbuilderdialog.h"
23+
#include "qgsmapcanvas.h"
24+
#include "qgsmaplayerlegend.h"
25+
#include "qgsrenderer.h"
26+
#include "qgssymbollayerutils.h"
27+
#include "qgstextformatwidget.h"
28+
#include "qgsvectorlayer.h"
29+
30+
31+
QgsVectorLayerLegendWidget::QgsVectorLayerLegendWidget( QWidget *parent )
32+
: QWidget( parent )
33+
{
34+
mLegendTreeView = new QTreeView;
35+
mLegendTreeView->setRootIsDecorated( false );
36+
37+
mTextOnSymbolFormatButton = new QPushButton( tr( "Set Text Format…" ) );
38+
connect( mTextOnSymbolFormatButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::openTextFormatWidget );
39+
40+
mTextOnSymbolFromExpressionButton = new QPushButton( tr( "Set Labels from Expression…" ) );
41+
connect( mTextOnSymbolFromExpressionButton, &QPushButton::clicked, this, &QgsVectorLayerLegendWidget::labelsFromExpression );
42+
43+
mTextOnSymbolGroupBox = new QgsCollapsibleGroupBox;
44+
45+
QHBoxLayout *buttonsLayout = new QHBoxLayout;
46+
buttonsLayout->addWidget( mTextOnSymbolFormatButton );
47+
buttonsLayout->addWidget( mTextOnSymbolFromExpressionButton );
48+
49+
QVBoxLayout *groupLayout = new QVBoxLayout;
50+
groupLayout->addWidget( mLegendTreeView );
51+
groupLayout->addLayout( buttonsLayout );
52+
53+
mTextOnSymbolGroupBox->setTitle( tr( "Text on Symbols" ) );
54+
mTextOnSymbolGroupBox->setCheckable( true );
55+
mTextOnSymbolGroupBox->setLayout( groupLayout );
56+
mTextOnSymbolGroupBox->setCollapsed( true );
57+
58+
QVBoxLayout *layout = new QVBoxLayout;
59+
layout->addWidget( mTextOnSymbolGroupBox );
60+
setLayout( layout );
61+
}
62+
63+
64+
void QgsVectorLayerLegendWidget::setLayer( QgsVectorLayer *layer )
65+
{
66+
mLayer = layer;
67+
68+
QgsDefaultVectorLayerLegend *legend = qobject_cast<QgsDefaultVectorLayerLegend *>( layer->legend() );
69+
if ( !legend )
70+
return;
71+
72+
mTextOnSymbolGroupBox->setChecked( legend->textOnSymbolEnabled() );
73+
mTextOnSymbolTextFormat = legend->textOnSymbolTextFormat();
74+
populateLegendTreeView( legend->textOnSymbolContent() );
75+
}
76+
77+
78+
void QgsVectorLayerLegendWidget::populateLegendTreeView( const QHash<QString, QString> &content )
79+
{
80+
QStandardItemModel *model = new QStandardItemModel;
81+
model->setColumnCount( 2 );
82+
model->setHorizontalHeaderLabels( QStringList() << tr( "Symbol" ) << tr( "Text" ) );
83+
84+
const QgsLegendSymbolList lst = mLayer->renderer()->legendSymbolItems();
85+
for ( const QgsLegendSymbolItem &symbolItem : lst )
86+
{
87+
if ( !symbolItem.symbol() )
88+
continue;
89+
90+
QgsRenderContext context;
91+
QSize iconSize( 16, 16 );
92+
QIcon icon = QgsSymbolLayerUtils::symbolPreviewPixmap( symbolItem.symbol(), iconSize, 0, &context );
93+
94+
QStandardItem *item1 = new QStandardItem( icon, symbolItem.label() );
95+
item1->setEditable( false );
96+
QStandardItem *item2 = new QStandardItem;
97+
if ( symbolItem.ruleKey().isEmpty() )
98+
{
99+
item1->setEnabled( false );
100+
item2->setEnabled( true );
101+
}
102+
else
103+
{
104+
item1->setData( symbolItem.ruleKey() );
105+
if ( content.contains( symbolItem.ruleKey() ) )
106+
item2->setText( content.value( symbolItem.ruleKey() ) );
107+
}
108+
model->appendRow( QList<QStandardItem *>() << item1 << item2 );
109+
}
110+
mLegendTreeView->setModel( model );
111+
mLegendTreeView->resizeColumnToContents( 0 );
112+
}
113+
114+
115+
void QgsVectorLayerLegendWidget::applyToLayer()
116+
{
117+
QgsDefaultVectorLayerLegend *legend = new QgsDefaultVectorLayerLegend( mLayer );
118+
legend->setTextOnSymbolEnabled( mTextOnSymbolGroupBox->isChecked() );
119+
legend->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat );
120+
121+
QHash<QString, QString> content;
122+
if ( QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mLegendTreeView->model() ) )
123+
{
124+
for ( int i = 0; i < model->rowCount(); ++i )
125+
{
126+
QString ruleKey = model->item( i, 0 )->data().toString();
127+
QString label = model->item( i, 1 )->text();
128+
if ( !label.isEmpty() )
129+
content[ruleKey] = label;
130+
}
131+
}
132+
legend->setTextOnSymbolContent( content );
133+
134+
mLayer->setLegend( legend );
135+
}
136+
137+
138+
void QgsVectorLayerLegendWidget::openTextFormatWidget()
139+
{
140+
QgsTextFormatWidget *textOnSymbolFormatWidget = new QgsTextFormatWidget( mTextOnSymbolTextFormat );
141+
QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
142+
QVBoxLayout *layout = new QVBoxLayout;
143+
layout->addWidget( textOnSymbolFormatWidget );
144+
layout->addWidget( dialogButtonBox );
145+
QDialog dlg;
146+
connect( dialogButtonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
147+
connect( dialogButtonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
148+
dlg.setLayout( layout );
149+
if ( !dlg.exec() )
150+
return;
151+
152+
mTextOnSymbolTextFormat = textOnSymbolFormatWidget->format();
153+
}
154+
155+
156+
void QgsVectorLayerLegendWidget::labelsFromExpression()
157+
{
158+
QHash<QString, QString> content;
159+
QgsRenderContext context( QgsRenderContext::fromMapSettings( mCanvas->mapSettings() ) );
160+
161+
QgsExpressionBuilderDialog dlgExpression( mLayer );
162+
dlgExpression.setExpressionContext( context.expressionContext() );
163+
if ( !dlgExpression.exec() )
164+
return;
165+
166+
QgsExpression expr( dlgExpression.expressionText() );
167+
expr.prepare( &context.expressionContext() );
168+
169+
std::unique_ptr< QgsFeatureRenderer > r( mLayer->renderer()->clone() );
170+
171+
QgsFeature f;
172+
QgsFeatureRequest request;
173+
request.setSubsetOfAttributes( r->usedAttributes( context ), mLayer->fields() );
174+
QgsFeatureIterator fi = mLayer->getFeatures();
175+
176+
r->startRender( context, mLayer->fields() );
177+
while ( fi.nextFeature( f ) )
178+
{
179+
context.expressionContext().setFeature( f );
180+
const QSet<QString> keys = r->legendKeysForFeature( f, context );
181+
for ( const QString &key : keys )
182+
{
183+
if ( content.contains( key ) )
184+
continue;
185+
186+
QString label = expr.evaluate( &context.expressionContext() ).toString();
187+
if ( !label.isEmpty() )
188+
content[key] = label;
189+
}
190+
}
191+
r->stopRender( context );
192+
193+
populateLegendTreeView( content );
194+
}

‎src/app/qgsvectorlayerlegendwidget.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/***************************************************************************
2+
qgsvectorlayerlegendwidget.h
3+
---------------------
4+
Date : April 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSVECTORLAYERLEGENDWIDGET_H
17+
#define QGSVECTORLAYERLEGENDWIDGET_H
18+
19+
#include <QWidget>
20+
21+
#include "qgstextrenderer.h"
22+
23+
class QLabel;
24+
class QPushButton;
25+
class QTreeView;
26+
27+
class QgsCollapsibleGroupBox;
28+
class QgsMapCanvas;
29+
class QgsVectorLayer;
30+
31+
/**
32+
* A widget for configuration of options specific to vector layer's legend.
33+
*/
34+
class QgsVectorLayerLegendWidget : public QWidget
35+
{
36+
Q_OBJECT
37+
public:
38+
explicit QgsVectorLayerLegendWidget( QWidget *parent = nullptr );
39+
40+
//! Sets pointer to map canvas
41+
void setMapCanvas( QgsMapCanvas *canvas ) { mCanvas = canvas; }
42+
43+
//! Returns pointer to map canvas
44+
QgsMapCanvas *mapCanvas() const { return mCanvas; }
45+
46+
//! Initialize widget with a map layer
47+
void setLayer( QgsVectorLayer *layer );
48+
49+
//! Store changes made in the widget to the layer
50+
void applyToLayer();
51+
52+
private slots:
53+
void openTextFormatWidget();
54+
void labelsFromExpression();
55+
56+
private:
57+
void populateLegendTreeView( const QHash<QString, QString> &content );
58+
59+
private:
60+
QTreeView *mLegendTreeView = nullptr;
61+
QPushButton *mTextOnSymbolFormatButton = nullptr;
62+
QPushButton *mTextOnSymbolFromExpressionButton = nullptr;
63+
QgsCollapsibleGroupBox *mTextOnSymbolGroupBox = nullptr;
64+
QLabel *mTextOnSymbolLabel = nullptr;
65+
66+
QgsMapCanvas *mCanvas = nullptr;
67+
QgsVectorLayer *mLayer = nullptr;
68+
QgsTextFormat mTextOnSymbolTextFormat;
69+
};
70+
71+
#endif // QGSVECTORLAYERLEGENDWIDGET_H

‎src/app/qgsvectorlayerproperties.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
302302
mDiagramFrame->setLayout( diagLayout );
303303

304304
// Legend tab
305+
mLegendWidget->setMapCanvas( QgisApp::instance()->mapCanvas() );
306+
mLegendWidget->setLayer( mLayer );
305307
mLegendConfigEmbeddedWidget->setLayer( mLayer );
306308

307309
// WMS Name as layer short name
@@ -572,6 +574,7 @@ void QgsVectorLayerProperties::apply()
572574
}
573575

574576
// apply legend settings
577+
mLegendWidget->applyToLayer();
575578
mLegendConfigEmbeddedWidget->applyToLayer();
576579

577580
// save metadata

‎src/core/layertree/qgslayertreemodellegendnode.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ QSize QgsSymbolLegendNode::minimumIconSize( QgsRenderContext *context ) const
178178
true ).size();
179179
}
180180

181+
if ( !mTextOnSymbolLabel.isEmpty() && context )
182+
{
183+
double w = QgsTextRenderer::textWidth( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel );
184+
double h = QgsTextRenderer::textHeight( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel, QgsTextRenderer::Point );
185+
int wInt = ceil( w ), hInt = ceil( h );
186+
if ( wInt > minSz.width() ) minSz.setWidth( wInt );
187+
if ( hInt > minSz.height() ) minSz.setHeight( hInt );
188+
}
189+
181190
if ( mItem.level() != 0 && !( model() && model()->testFlag( QgsLayerTreeModel::ShowLegendAsTree ) ) )
182191
minSz.setWidth( mItem.level() * INDENT_SIZE + minSz.width() );
183192

@@ -271,6 +280,17 @@ QVariant QgsSymbolLegendNode::data( int role ) const
271280
{
272281
std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
273282
pix = QgsSymbolLayerUtils::symbolPreviewPixmap( mItem.symbol(), mIconSize, 0, context.get() );
283+
284+
if ( !mTextOnSymbolLabel.isEmpty() && context )
285+
{
286+
QPainter painter( &pix );
287+
painter.setRenderHint( QPainter::Antialiasing );
288+
context->setPainter( &painter );
289+
QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( *context.get() ) );
290+
qreal yBaselineVCenter = ( mIconSize.height() + fm.ascent() - fm.descent() ) / 2;
291+
QgsTextRenderer::drawText( QPointF( mIconSize.width() / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
292+
QStringList() << mTextOnSymbolLabel, *context.get(), mTextOnSymbolTextFormat );
293+
}
274294
}
275295
else
276296
{
@@ -418,6 +438,15 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
418438
{
419439
s->drawPreviewIcon( p, QSize( width * dotsPerMM, height * dotsPerMM ), &context );
420440
}
441+
442+
if ( !mTextOnSymbolLabel.isEmpty() )
443+
{
444+
QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( context ) );
445+
qreal yBaselineVCenter = ( height * dotsPerMM + fm.ascent() - fm.descent() ) / 2;
446+
QgsTextRenderer::drawText( QPointF( width * dotsPerMM / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
447+
QStringList() << mTextOnSymbolLabel, context, mTextOnSymbolTextFormat );
448+
}
449+
421450
p->restore();
422451
}
423452

‎src/core/layertree/qgslayertreemodellegendnode.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
144144
};
145145

146146
#include "qgslegendsymbolitem.h"
147+
#include "qgstextrenderer.h"
147148

148149
/**
149150
* \ingroup core
@@ -221,6 +222,30 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
221222
*/
222223
void setSymbol( QgsSymbol *symbol );
223224

225+
/**
226+
* Returns label of text to be shown on top of the symbol.
227+
* \since QGIS 3.2
228+
*/
229+
QString textOnSymbolLabel() const { return mTextOnSymbolLabel; }
230+
231+
/**
232+
* Sets label of text to be shown on top of the symbol.
233+
* \since QGIS 3.2
234+
*/
235+
void setTextOnSymbolLabel( const QString &label ) { mTextOnSymbolLabel = label; }
236+
237+
/**
238+
* Returns text format of the label to be shown on top of the symbol.
239+
* \since QGIS 3.2
240+
*/
241+
QgsTextFormat textOnSymbolTextFormat() const { return mTextOnSymbolTextFormat; }
242+
243+
/**
244+
* Sets format of text to be shown on top of the symbol.
245+
* \since QGIS 3.2
246+
*/
247+
void setTextOnSymbolTextFormat( const QgsTextFormat &format ) { mTextOnSymbolTextFormat = format; }
248+
224249
public slots:
225250

226251
/**
@@ -247,6 +272,9 @@ class CORE_EXPORT QgsSymbolLegendNode : public QgsLayerTreeModelLegendNode
247272
bool mSymbolUsesMapUnits;
248273
QSize mIconSize;
249274

275+
QString mTextOnSymbolLabel;
276+
QgsTextFormat mTextOnSymbolTextFormat;
277+
250278
// ident the symbol icon to make it look like a tree structure
251279
static const int INDENT_SIZE = 20;
252280

‎src/core/qgsmaplayerlegend.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ QgsMapLayerLegend::QgsMapLayerLegend( QObject *parent )
3030
{
3131
}
3232

33+
void QgsMapLayerLegend::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
34+
{
35+
Q_UNUSED( elem );
36+
Q_UNUSED( context );
37+
}
38+
39+
QDomElement QgsMapLayerLegend::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
40+
{
41+
Q_UNUSED( doc );
42+
Q_UNUSED( context );
43+
return QDomElement();
44+
}
45+
3346
QgsMapLayerLegend *QgsMapLayerLegend::defaultVectorLegend( QgsVectorLayer *vl )
3447
{
3548
return new QgsDefaultVectorLayerLegend( vl );
@@ -199,7 +212,15 @@ QList<QgsLayerTreeModelLegendNode *> QgsDefaultVectorLayerLegend::createLayerTre
199212
if ( i.dataDefinedSizeLegendSettings() )
200213
nodes << new QgsDataDefinedSizeLegendNode( nodeLayer, *i.dataDefinedSizeLegendSettings() );
201214
else
202-
nodes << new QgsSymbolLegendNode( nodeLayer, i );
215+
{
216+
QgsSymbolLegendNode *legendNode = new QgsSymbolLegendNode( nodeLayer, i );
217+
if ( mTextOnSymbolEnabled && mTextOnSymbolContent.contains( i.ruleKey() ) )
218+
{
219+
legendNode->setTextOnSymbolLabel( mTextOnSymbolContent.value( i.ruleKey() ) );
220+
legendNode->setTextOnSymbolTextFormat( mTextOnSymbolTextFormat );
221+
}
222+
nodes << legendNode;
223+
}
203224
}
204225

205226
if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() )
@@ -218,6 +239,52 @@ QList<QgsLayerTreeModelLegendNode *> QgsDefaultVectorLayerLegend::createLayerTre
218239
return nodes;
219240
}
220241

242+
void QgsDefaultVectorLayerLegend::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
243+
{
244+
mTextOnSymbolEnabled = false;
245+
mTextOnSymbolTextFormat = QgsTextFormat();
246+
mTextOnSymbolContent.clear();
247+
248+
QDomElement tosElem = elem.firstChildElement( QStringLiteral( "text-on-symbol" ) );
249+
if ( !tosElem.isNull() )
250+
{
251+
mTextOnSymbolEnabled = true;
252+
QDomElement tosFormatElem = tosElem.firstChildElement( QStringLiteral( "text-style" ) );
253+
mTextOnSymbolTextFormat.readXml( tosFormatElem, context );
254+
QDomElement tosContentElem = tosElem.firstChildElement( QStringLiteral( "content" ) );
255+
QDomElement tosContentItemElem = tosContentElem.firstChildElement( QStringLiteral( "item" ) );
256+
while ( !tosContentItemElem.isNull() )
257+
{
258+
mTextOnSymbolContent.insert( tosContentItemElem.attribute( QStringLiteral( "key" ) ), tosContentItemElem.attribute( QStringLiteral( "value" ) ) );
259+
tosContentItemElem = tosContentItemElem.nextSiblingElement( QStringLiteral( "item" ) );
260+
}
261+
}
262+
}
263+
264+
QDomElement QgsDefaultVectorLayerLegend::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
265+
{
266+
QDomElement elem = doc.createElement( QStringLiteral( "legend" ) );
267+
elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "default-vector" ) );
268+
269+
if ( mTextOnSymbolEnabled )
270+
{
271+
QDomElement tosElem = doc.createElement( QStringLiteral( "text-on-symbol" ) );
272+
QDomElement tosFormatElem = mTextOnSymbolTextFormat.writeXml( doc, context );
273+
tosElem.appendChild( tosFormatElem );
274+
QDomElement tosContentElem = doc.createElement( QStringLiteral( "content" ) );
275+
for ( auto it = mTextOnSymbolContent.constBegin(); it != mTextOnSymbolContent.constEnd(); ++it )
276+
{
277+
QDomElement tosContentItemElem = doc.createElement( QStringLiteral( "item" ) );
278+
tosContentItemElem.setAttribute( QStringLiteral( "key" ), it.key() );
279+
tosContentItemElem.setAttribute( QStringLiteral( "value" ), it.value() );
280+
tosContentElem.appendChild( tosContentItemElem );
281+
}
282+
tosElem.appendChild( tosContentElem );
283+
elem.appendChild( tosElem );
284+
}
285+
286+
return elem;
287+
}
221288

222289

223290
// -------------------------------------------------------------------------

‎src/core/qgsmaplayerlegend.h

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
#include <QObject>
2020
#include "qgis.h"
2121

22+
class QDomDocument;
23+
class QDomElement;
24+
2225
class QgsLayerTreeLayer;
2326
class QgsLayerTreeModelLegendNode;
2427
class QgsPluginLayer;
2528
class QgsRasterLayer;
29+
class QgsReadWriteContext;
2630
class QgsVectorLayer;
2731

2832
#include "qgis_core.h"
@@ -43,7 +47,19 @@ class CORE_EXPORT QgsMapLayerLegend : public QObject
4347
//! Constructor for QgsMapLayerLegend
4448
explicit QgsMapLayerLegend( QObject *parent SIP_TRANSFERTHIS = nullptr );
4549

46-
// TODO: type, load/save settings
50+
// TODO: type
51+
52+
/**
53+
* Reads configuration from a DOM element previously written by writeXml()
54+
* \since QGIS 3.2
55+
*/
56+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
57+
58+
/**
59+
* Writes configuration to a DOM element, to be used later with readXml()
60+
* \since QGIS 3.2
61+
*/
62+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const;
4763

4864
/**
4965
* Return list of legend nodes to be used for a particular layer tree layer node.
@@ -89,6 +105,8 @@ class CORE_EXPORT QgsMapLayerLegendUtils
89105

90106
#include <QHash>
91107

108+
#include "qgstextrenderer.h"
109+
92110
/**
93111
* \ingroup core
94112
* Default legend implementation for vector layers
@@ -101,10 +119,59 @@ class CORE_EXPORT QgsDefaultVectorLayerLegend : public QgsMapLayerLegend
101119
public:
102120
explicit QgsDefaultVectorLayerLegend( QgsVectorLayer *vl );
103121

122+
/**
123+
* Returns whether the "text on symbol" functionality is enabled. When enabled, legend symbols
124+
* may have extra text rendered on top. The content of labels and their style is controlled
125+
* by textOnSymbolContent() and textOnSymbolTextFormat().
126+
* \since QGIS 3.2
127+
*/
128+
bool textOnSymbolEnabled() const { return mTextOnSymbolEnabled; }
129+
130+
/**
131+
* Sets whether the "text on symbol" functionality is enabled. When enabled, legend symbols
132+
* may have extra text rendered on top. The content of labels and their style is controlled
133+
* by textOnSymbolContent() and textOnSymbolTextFormat().
134+
* \since QGIS 3.2
135+
*/
136+
void setTextOnSymbolEnabled( bool enabled ) { mTextOnSymbolEnabled = enabled; }
137+
138+
/**
139+
* Returns text format of symbol labels for "text on symbol" functionality.
140+
* \since QGIS 3.2
141+
*/
142+
QgsTextFormat textOnSymbolTextFormat() const { return mTextOnSymbolTextFormat; }
143+
144+
/**
145+
* Sets text format of symbol labels for "text on symbol" functionality.
146+
* \since QGIS 3.2
147+
*/
148+
void setTextOnSymbolTextFormat( const QgsTextFormat &format ) { mTextOnSymbolTextFormat = format; }
149+
150+
/**
151+
* Returns per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
152+
* the keys are rule keys of legend items, the values are labels to be shown.
153+
* \since QGIS 3.2
154+
*/
155+
QHash<QString, QString> textOnSymbolContent() const { return mTextOnSymbolContent; }
156+
157+
/**
158+
* Sets per-symbol content of labels for "text on symbol" functionality. In the passed dictionary
159+
* the keys are rule keys of legend items, the values are labels to be shown.
160+
* \since QGIS 3.2
161+
*/
162+
void setTextOnSymbolContent( const QHash<QString, QString> &content ) { mTextOnSymbolContent = content; }
163+
104164
QList<QgsLayerTreeModelLegendNode *> createLayerTreeModelLegendNodes( QgsLayerTreeLayer *nodeLayer ) SIP_FACTORY override;
165+
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
166+
virtual QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const override;
105167

106168
private:
107169
QgsVectorLayer *mLayer = nullptr;
170+
171+
// text on symbol
172+
bool mTextOnSymbolEnabled = false;
173+
QgsTextFormat mTextOnSymbolTextFormat;
174+
QHash<QString, QString> mTextOnSymbolContent;
108175
};
109176

110177

‎src/core/qgsvectorlayer.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,11 @@ bool QgsVectorLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &c
14231423
}
14241424
setDependencies( sources );
14251425

1426-
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
1426+
QgsMapLayerLegend *legend = QgsMapLayerLegend::defaultVectorLegend( this );
1427+
QDomElement legendElem = layer_node.firstChildElement( QStringLiteral( "legend" ) );
1428+
if ( !legendElem.isNull() )
1429+
legend->readXml( legendElem, context );
1430+
setLegend( legend );
14271431

14281432
// read extent
14291433
if ( mReadExtentFromXml )
@@ -1688,6 +1692,14 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node,
16881692
}
16891693
layer_node.appendChild( dataDependenciesElement );
16901694

1695+
// legend
1696+
if ( legend() )
1697+
{
1698+
QDomElement legendElement = legend()->writeXml( document, context );
1699+
if ( !legendElement.isNull() )
1700+
layer_node.appendChild( legendElement );
1701+
}
1702+
16911703
// save expression fields
16921704
mExpressionFieldBuffer->writeXml( layer_node, document );
16931705

‎src/gui/qgstextformatwidget.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,11 @@ QgsTextFormat QgsTextFormatWidget::format() const
828828
return format;
829829
}
830830

831+
void QgsTextFormatWidget::setFormat( const QgsTextFormat &format )
832+
{
833+
updateWidgetForFormat( format );
834+
}
835+
831836
void QgsTextFormatWidget::optionsStackedWidget_CurrentChanged( int indx )
832837
{
833838
mLabelingOptionsListWidget->blockSignals( true );

‎src/gui/qgstextformatwidget.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
6868
*/
6969
QgsTextFormat format() const;
7070

71+
/**
72+
* Sets the current formatting settings
73+
* \since QGIS 3.2
74+
*/
75+
void setFormat( const QgsTextFormat &format );
76+
7177
public slots:
7278

7379
/**

‎src/ui/qgsvectorlayerpropertiesbase.ui

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1878,6 +1878,9 @@ border-radius: 2px;</string>
18781878
<number>0</number>
18791879
</property>
18801880
<item>
1881+
<widget class="QgsVectorLayerLegendWidget" name="mLegendWidget" native="true"/>
1882+
</item>
1883+
<item>
18811884
<widget class="QGroupBox" name="groupBox_3">
18821885
<property name="title">
18831886
<string>Embedded widgets in legend</string>
@@ -2416,6 +2419,12 @@ border-radius: 2px;</string>
24162419
<extends>QWidget</extends>
24172420
<header>qgscodeeditorhtml.h</header>
24182421
</customwidget>
2422+
<customwidget>
2423+
<class>QgsVectorLayerLegendWidget</class>
2424+
<extends>QWidget</extends>
2425+
<header>qgsvectorlayerlegendwidget.h</header>
2426+
<container>1</container>
2427+
</customwidget>
24192428
</customwidgets>
24202429
<tabstops>
24212430
<tabstop>mSearchLineEdit</tabstop>

‎tests/src/core/testqgslegendrenderer.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class TestQgsLegendRenderer : public QObject
122122
void testDiagramAttributeLegend();
123123
void testDiagramSizeLegend();
124124
void testDataDefinedSizeCollapsed();
125+
void testTextOnSymbol();
125126

126127
private:
127128
QgsLayerTree *mRoot = nullptr;
@@ -783,6 +784,50 @@ void TestQgsLegendRenderer::testDataDefinedSizeCollapsed()
783784
delete root;
784785
}
785786

787+
void TestQgsLegendRenderer::testTextOnSymbol()
788+
{
789+
QString testName = QStringLiteral( "legend_text_on_symbol" );
790+
791+
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "Polygon Layer" ), QStringLiteral( "memory" ) );
792+
793+
QgsCategoryList cats;
794+
QgsFillSymbol *sym_1 = new QgsFillSymbol();
795+
sym_1->setColor( Qt::red );
796+
cats << QgsRendererCategory( 1, sym_1, QStringLiteral( "Red" ) );
797+
QgsFillSymbol *sym_2 = new QgsFillSymbol();
798+
sym_2->setColor( Qt::green );
799+
cats << QgsRendererCategory( 2, sym_2, QStringLiteral( "Green" ) );
800+
QgsFillSymbol *sym_3 = new QgsFillSymbol();
801+
sym_3->setColor( Qt::blue );
802+
cats << QgsRendererCategory( 3, sym_3, QStringLiteral( "Blue" ) );
803+
QgsCategorizedSymbolRenderer *r = new QgsCategorizedSymbolRenderer( QStringLiteral( "test_attr" ), cats );
804+
vl->setRenderer( r );
805+
806+
QgsDefaultVectorLayerLegend *legend = new QgsDefaultVectorLayerLegend( vl );
807+
legend->setTextOnSymbolEnabled( true );
808+
QHash<QString, QString> content;
809+
content["0"] = "Rd";
810+
content["2"] = "Bl";
811+
legend->setTextOnSymbolContent( content );
812+
QgsTextFormat textFormat;
813+
textFormat.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Roman" ) ) );
814+
textFormat.setSize( 9 );
815+
legend->setTextOnSymbolTextFormat( textFormat );
816+
vl->setLegend( legend );
817+
818+
QgsLayerTree *root = new QgsLayerTree();
819+
root->addLayer( vl );
820+
821+
QgsLayerTreeModel legendModel( root );
822+
823+
QgsLegendSettings settings;
824+
_setStandardTestFont( settings );
825+
_renderLegend( testName, &legendModel, settings );
826+
QVERIFY( _verifyImage( testName, mReport ) );
827+
828+
delete root;
829+
}
830+
786831

787832
QGSTEST_MAIN( TestQgsLegendRenderer )
788833
#include "testqgslegendrenderer.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.