Skip to content
Permalink
Browse files

[FEATURE] allow customization of line spacing for legend item labels (#…

…3632)

* [FEATURE] allow customization of line spacing for legend item labels
* add test for legend line spacing and update control images
  • Loading branch information
nirvn committed Dec 13, 2016
1 parent dc509f4 commit a8b9723cbc64e911a7702aca41b8dfbb84f8ffc0
Showing with 96 additions and 11 deletions.
  1. +12 −0 python/core/composer/qgscomposerlegend.sip
  2. +12 −0 src/app/composer/qgscomposerlegendwidget.cpp
  3. +1 −0 src/app/composer/qgscomposerlegendwidget.h
  4. +1 −0 src/core/composer/qgscomposeritemcommand.h
  5. +5 −0 src/core/composer/qgscomposerlegend.cpp
  6. +13 −0 src/core/composer/qgscomposerlegend.h
  7. +4 −3 src/core/layertree/qgslayertreemodellegendnode.cpp
  8. +3 −3 src/core/qgslegendrenderer.cpp
  9. +5 −5 src/core/qgslegendsettings.cpp
  10. +17 −0 src/ui/composer/qgscomposerlegendwidgetbase.ui
  11. +23 −0 tests/src/core/testqgslegendrenderer.cpp
  12. BIN ...ages/composer_legend/expected_composer_legend_mapunits/expected_composer_legend_mapunits_mask.png
  13. BIN ...ol_images/composer_legend/expected_composer_legend_noresize/expected_composer_legend_noresize.png
  14. BIN ...ser_legend/expected_composer_legend_noresize_crop/expected_composer_legend_noresize_crop_mask.png
  15. BIN ...poser_legend/expected_composer_legend_size_content/expected_composer_legend_size_content_mask.png
  16. BIN tests/testdata/control_images/expected_atlas_legend/expected_atlas_legend_mask.png
  17. BIN tests/testdata/control_images/legend/expected_legend_3_by_2/expected_legend_3_by_2_mask.png
  18. BIN tests/testdata/control_images/legend/expected_legend_3_by_3/expected_legend_3_by_3_mask.png
  19. BIN tests/testdata/control_images/legend/expected_legend_4_by_2/expected_legend_4_by_2_mask.png
  20. BIN tests/testdata/control_images/legend/expected_legend_4_by_3/expected_legend_4_by_3_mask.png
  21. BIN tests/testdata/control_images/legend/expected_legend_5_by_2/expected_legend_5_by_2.png
  22. BIN tests/testdata/control_images/legend/expected_legend_5_by_2/expected_legend_5_by_2_mask.png
  23. BIN tests/testdata/control_images/legend/expected_legend_5_by_3/expected_legend_5_by_3_mask.png
  24. BIN tests/testdata/control_images/legend/expected_legend_6_by_3/expected_legend_6_by_3_mask.png
  25. BIN tests/testdata/control_images/legend/expected_legend_7_by_3/expected_legend_7_by_3.png
  26. BIN tests/testdata/control_images/legend/expected_legend_7_by_3/expected_legend_7_by_3_mask.png
  27. BIN tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic.png
  28. BIN tests/testdata/control_images/legend/expected_legend_basic/expected_legend_basic_mask.png
  29. BIN tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker.png
  30. BIN tests/testdata/control_images/legend/expected_legend_big_marker/expected_legend_big_marker_mask.png
  31. BIN ...a/control_images/legend/expected_legend_diagram_attributes/expected_legend_diagram_attributes.png
  32. BIN ...trol_images/legend/expected_legend_diagram_attributes/expected_legend_diagram_attributes_mask.png
  33. BIN tests/testdata/control_images/legend/expected_legend_diagram_size/expected_legend_diagram_size.png
  34. BIN ...testdata/control_images/legend/expected_legend_diagram_size/expected_legend_diagram_size_mask.png
  35. BIN ...ntrol_images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression.png
  36. BIN ..._images/legend/expected_legend_filter_by_expression/expected_legend_filter_by_expression_mask.png
  37. BIN ...rol_images/legend/expected_legend_filter_by_expression2/expected_legend_filter_by_expression2.png
  38. BIN ...mages/legend/expected_legend_filter_by_expression2/expected_legend_filter_by_expression2_mask.png
  39. BIN tests/testdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map.png
  40. BIN ...stdata/control_images/legend/expected_legend_filter_by_map/expected_legend_filter_by_map_mask.png
  41. BIN ...trol_images/legend/expected_legend_filter_by_map_dupe/expected_legend_filter_by_map_dupe_mask.png
  42. BIN ...ata/control_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon.png
  43. BIN ...ontrol_images/legend/expected_legend_filter_by_polygon/expected_legend_filter_by_polygon_mask.png
  44. BIN ...a/control_images/legend/expected_legend_filter_by_polygon2/expected_legend_filter_by_polygon2.png
  45. BIN ...trol_images/legend/expected_legend_filter_by_polygon2/expected_legend_filter_by_polygon2_mask.png
  46. BIN tests/testdata/control_images/legend/expected_legend_line_spacing/expected_legend_line_spacing.png
  47. BIN ...tdata/control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text.png
  48. BIN .../control_images/legend/expected_legend_long_symbol_text/expected_legend_long_symbol_text_mask.png
  49. BIN tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits.png
  50. BIN tests/testdata/control_images/legend/expected_legend_mapunits/expected_legend_mapunits_mask.png
  51. BIN tests/testdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border.png
  52. BIN ...stdata/control_images/legend/expected_legend_raster_border/expected_legend_raster_border_mask.png
  53. BIN tests/testdata/control_images/legend/expected_legend_tall_symbol/expected_legend_tall_symbol.png
  54. BIN ...s/testdata/control_images/legend/expected_legend_tall_symbol/expected_legend_tall_symbol_mask.png
  55. BIN ...stdata/control_images/legend/expected_legend_three_columns/expected_legend_three_columns_mask.png
  56. BIN tests/testdata/control_images/qgis_server/WMS_GetLegendGraphic_BBox/WMS_GetLegendGraphic_BBox.png
  57. BIN tests/testdata/control_images/qgis_server/WMS_GetLegendGraphic_BBox2/WMS_GetLegendGraphic_BBox2.png
  58. BIN tests/testdata/control_images/qgis_server/WMS_GetLegendGraphic_Basic/WMS_GetLegendGraphic_Basic.png
  59. BIN ...stdata/control_images/qgis_server/WMS_GetLegendGraphic_BoxSpace/WMS_GetLegendGraphic_BoxSpace.png
  60. BIN ...ol_images/qgis_server/WMS_GetLegendGraphic_IconLabelSpace/WMS_GetLegendGraphic_IconLabelSpace.png
  61. BIN ...ta/control_images/qgis_server/WMS_GetLegendGraphic_SymbolSize/WMS_GetLegendGraphic_SymbolSize.png
  62. BIN .../control_images/qgis_server/WMS_GetLegendGraphic_SymbolSpace/WMS_GetLegendGraphic_SymbolSpace.png
  63. BIN tests/testdata/control_images/qgis_server/WMS_GetLegendGraphic_test/WMS_GetLegendGraphic_test.png
  64. BIN .../testdata/control_images/qgis_server/WMS_GetLegendGraphic_test/WMS_GetLegendGraphic_test_mask.png
  65. BIN ..._server/WMS_GetLegendGraphic_test_layertitle_false/WMS_GetLegendGraphic_test_layertitle_false.png
  66. BIN ...er/WMS_GetLegendGraphic_test_layertitle_false/WMS_GetLegendGraphic_test_layertitle_false_mask.png
@@ -121,6 +121,18 @@ class QgsComposerLegend : QgsComposerItem
void setStyleMargin( QgsLegendStyle::Style s, double margin );
void setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin );

/** Returns the spacing in-between lines in mm
* @note added in 3.0
* @see setLineSpacing
*/
double lineSpacing() const;
/** Sets the spacing in-between multiple lines
* @param spacing Double value to use as spacing in between multiple lines
* @note added in 3.0
* @see lineSpacing
*/
void setLineSpacing( double spacing );

double boxSpace() const;
void setBoxSpace( double s );

@@ -144,6 +144,7 @@ void QgsComposerLegendWidget::setGuiElements()
mIconLabelSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left ) );
mBoxSpaceSpinBox->setValue( mLegend->boxSpace() );
mColumnSpaceSpinBox->setValue( mLegend->columnSpace() );
mLineSpacingSpinBox->setValue( mLegend->lineSpacing() );

mRasterBorderGroupBox->setChecked( mLegend->drawRasterBorder() );
mRasterBorderWidthSpinBox->setValue( mLegend->rasterBorderWidth() );
@@ -450,6 +451,17 @@ void QgsComposerLegendWidget::on_mColumnSpaceSpinBox_valueChanged( double d )
}
}

void QgsComposerLegendWidget::on_mLineSpacingSpinBox_valueChanged( double d )
{
if ( mLegend )
{
mLegend->beginCommand( tr( "Legend line space" ), QgsComposerMergeCommand::LegendLineSpacing );
mLegend->setLineSpacing( d );
mLegend->adjustBoxSize();
mLegend->update();
mLegend->endCommand();
}
}

static void _moveLegendNode( QgsLayerTreeLayer* nodeLayer, int legendNodeIndex, int offset )
{
@@ -66,6 +66,7 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
void on_mFontColorButton_colorChanged( const QColor& newFontColor );
void on_mBoxSpaceSpinBox_valueChanged( double d );
void on_mColumnSpaceSpinBox_valueChanged( double d );
void on_mLineSpacingSpinBox_valueChanged( double d );
void on_mCheckBoxAutoUpdate_stateChanged( int state );
void composerMapChanged( QgsComposerItem* item );
void on_mCheckboxResizeContents_toggled( bool checked );
@@ -111,6 +111,7 @@ class CORE_EXPORT QgsComposerMergeCommand: public QgsComposerItemCommand
LegendIconSymbolSpace,
LegendBoxSpace,
LegendColumnSpace,
LegendLineSpacing,
LegendRasterBorderWidth,
LegendFontColor,
LegendRasterBorderColor,
@@ -288,6 +288,9 @@ void QgsComposerLegend::setStyleFont( QgsLegendStyle::Style s, const QFont& f )
void QgsComposerLegend::setStyleMargin( QgsLegendStyle::Style s, double margin ) { rstyle( s ).setMargin( margin ); }
void QgsComposerLegend::setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin ) { rstyle( s ).setMargin( side, margin ); }

double QgsComposerLegend::lineSpacing() const { return mSettings.lineSpacing(); }
void QgsComposerLegend::setLineSpacing( double spacing ) { mSettings.setLineSpacing( spacing ); }

double QgsComposerLegend::boxSpace() const { return mSettings.boxSpace(); }
void QgsComposerLegend::setBoxSpace( double s ) { mSettings.setBoxSpace( s ); }

@@ -370,6 +373,7 @@ bool QgsComposerLegend::writeXml( QDomElement& elem, QDomDocument & doc ) const

composerLegendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
composerLegendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
composerLegendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );

composerLegendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterBorder() );
composerLegendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterBorderColor() ) );
@@ -501,6 +505,7 @@ bool QgsComposerLegend::readXml( const QDomElement& itemElem, const QDomDocument

mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), "1.0" ).toDouble() );

mSettings.setDrawRasterBorder( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
mSettings.setRasterBorderColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
@@ -149,6 +149,19 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
void setStyleMargin( QgsLegendStyle::Style s, double margin );
void setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin );

/** Returns the spacing in-between lines in mm
* @note added in 3.0
* @see setLineSpacing
*/
double lineSpacing() const;

/** Sets the spacing in-between multiple lines
* @param spacing Double value to use as spacing in between multiple lines
* @note added in 3.0
* @see lineSpacing
*/
void setLineSpacing( double spacing );

double boxSpace() const;
void setBoxSpace( double s );

@@ -93,10 +93,11 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings& set

QFont symbolLabelFont = settings.style( QgsLegendStyle::SymbolLabel ).font();
double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
double textDescent = settings.fontDescentMillimeters( symbolLabelFont );

QStringList lines = settings.splitStringForWrapping( data( Qt::DisplayRole ).toString() );

labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * settings.lineSpacing();
labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * ( settings.lineSpacing() + textDescent );

double labelX = 0.0, labelY = 0.0;
if ( ctx )
@@ -120,8 +121,8 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings& set
if ( ctx )
{
settings.drawText( ctx->painter, labelX, labelY, *itemPart, symbolLabelFont );
if ( itemPart != lines.end() )
labelY += settings.lineSpacing() + textHeight;
if ( itemPart != ( lines.end() - 1 ) )
labelY += textDescent + settings.lineSpacing() + textHeight;
}
}

@@ -411,7 +411,7 @@ QSizeF QgsLegendRenderer::drawTitle( QPainter* painter, QPointF point, Qt::Align
size.rwidth() = qMax( width, size.rwidth() );

y += height;
if ( titlePart != lines.end() )
if ( titlePart != ( lines.end() - 1 ) )
{
y += mSettings.lineSpacing();
}
@@ -537,7 +537,7 @@ QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer* nodeLayer, QPainter
if ( painter ) mSettings.drawText( painter, point.x(), y, *layerItemPart, layerFont );
qreal width = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
size.rwidth() = qMax( width, size.width() );
if ( layerItemPart != lines.end() )
if ( layerItemPart != ( lines.end() - 1 ) )
{
y += mSettings.lineSpacing();
}
@@ -566,7 +566,7 @@ QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup* nodeGroup, QPainter
if ( painter ) mSettings.drawText( painter, point.x(), y, *groupPart, groupFont );
qreal width = mSettings.textWidthMillimeters( groupFont, *groupPart );
size.rwidth() = qMax( width, size.width() );
if ( groupPart != lines.end() )
if ( groupPart != ( lines.end() - 1 ) )
{
y += mSettings.lineSpacing();
}
@@ -25,7 +25,7 @@ QgsLegendSettings::QgsLegendSettings()
, mBoxSpace( 2 )
, mSymbolSize( 7, 4 )
, mWmsLegendSize( 50, 25 )
, mLineSpacing( 1.5 )
, mLineSpacing( 1 )
, mColumnSpace( 2 )
, mColumnCount( 1 )
, mSplitLayer( false )
@@ -38,10 +38,10 @@ QgsLegendSettings::QgsLegendSettings()
, mMapScale( 1 )
, mDpi( 96 ) // based on QImage's default DPI
{
rstyle( QgsLegendStyle::Title ).setMargin( QgsLegendStyle::Bottom, 2 );
rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Top, 2 );
rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Top, 2 );
rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Top, 2 );
rstyle( QgsLegendStyle::Title ).setMargin( QgsLegendStyle::Bottom, 3.5 );
rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Top, 3 );
rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Top, 3 );
rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Top, 2.5 );
rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Top, 2 );
rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Left, 2 );
rstyle( QgsLegendStyle::Title ).rfont().setPointSizeF( 16.0 );
@@ -965,6 +965,23 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Line space</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QgsDoubleSpinBox" name="mLineSpacingSpinBox">
<property name="prefix">
<string/>
</property>
<property name="suffix">
<string> mm</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="toolTip">
@@ -114,6 +114,7 @@ class TestQgsLegendRenderer : public QObject
void testBigMarker();
void testMapUnits();
void testTallSymbol();
void testLineSpacing();
void testLongSymbolText();
void testThreeColumns();
void testFilterByMap();
@@ -360,6 +361,28 @@ void TestQgsLegendRenderer::testTallSymbol()
mVL2->setName( QStringLiteral( "Polygon Layer" ) );
}

void TestQgsLegendRenderer::testLineSpacing()
{
QString testName = "legend_line_spacing";

QgsCategorizedSymbolRenderer* catRenderer = dynamic_cast<QgsCategorizedSymbolRenderer*>( mVL3->renderer() );
QVERIFY( catRenderer );
catRenderer->updateCategoryLabel( 1, "This is\nthree lines\nlong label" );

mVL2->setName( "This is a two lines\nlong label" );

QgsLayerTreeModel legendModel( mRoot );

QgsLegendSettings settings;
settings.setWrapChar( "\n" );
settings.setLineSpacing( 3 );
_setStandardTestFont( settings );
_renderLegend( testName, &legendModel, settings );
QVERIFY( _verifyImage( testName, mReport ) );

mVL2->setName( "Polygon Layer" );
}

void TestQgsLegendRenderer::testLongSymbolText()
{
QString testName = QStringLiteral( "legend_long_symbol_text" );
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.

0 comments on commit a8b9723

Please sign in to comment.
You can’t perform that action at this time.