Skip to content

Commit

Permalink
[layouts] If a legend is linked to a map which is set to show a parti…
Browse files Browse the repository at this point in the history
…cular

theme, then correctly follow that same theme's styling when rendering
the legend

Fixes layout legends always render using the canvas' visible theme styling,
instead of that of the linked map.

Fixes #27542, #24694, #28919, #28925

Fix sponsored by the Victorian Planning Authority
  • Loading branch information
nyalldawson committed Apr 3, 2020
1 parent 6ddd88e commit d40114c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 6 deletions.
58 changes: 52 additions & 6 deletions src/core/layout/qgslayoutitemlegend.cpp
Expand Up @@ -31,6 +31,7 @@
#include "qgssymbollayerutils.h"
#include "qgslayertreeutils.h"
#include "qgslayoututils.h"
#include "qgsmapthemecollection.h"
#include <QDomDocument>
#include <QDomElement>
#include <QPainter>
Expand Down Expand Up @@ -702,7 +703,7 @@ void QgsLayoutItemLegend::setLinkedMap( QgsLayoutItemMap *map )
if ( mMap )
{
setupMapConnections( mMap, true );
mThemeName = mMap->themeToRender( mMap->createExpressionContext() );
mapThemeChanged( mMap->themeToRender( mMap->createExpressionContext() ) );
}

updateFilterByMap();
Expand Down Expand Up @@ -754,6 +755,15 @@ void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
updateFilterByMap( true );
}

void QgsLayoutItemLegend::setModelStyleOverrides( const QMap<QString, QString> &overrides )
{
mLegendModel->setLayerStyleOverrides( overrides );
const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
for ( QgsLayerTreeLayer *nodeLayer : layers )
mLegendModel->refreshLayerLegend( nodeLayer );

}

void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
{
if ( !mMap )
Expand All @@ -768,10 +778,7 @@ void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
}
else
{
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
for ( QgsLayerTreeLayer *nodeLayer : layers )
mLegendModel->refreshLayerLegend( nodeLayer );
setModelStyleOverrides( mMap->layerStyleOverrides() );
}

adjustBoxSize();
Expand All @@ -781,7 +788,35 @@ void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()

void QgsLayoutItemLegend::mapThemeChanged( const QString &theme )
{
if ( mThemeName == theme )
return;

mThemeName = theme;

// map's theme has been changed, so make sure to update the legend here
if ( mLegendFilterByMap )
{
// legend is being filtered by map, so we need to re run the hit test too
// as the style overrides may also have affected the visible symbols
updateFilterByMap( false );
}
else
{
if ( mThemeName.isEmpty() )
{
setModelStyleOverrides( QMap<QString, QString>() );
}
else
{
// get style overrides for theme
const QMap<QString, QString> overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
setModelStyleOverrides( overrides );
}
}

adjustBoxSize();

updateFilterByMap();
}

void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
Expand All @@ -798,7 +833,18 @@ void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
void QgsLayoutItemLegend::doUpdateFilterByMap()
{
if ( mMap )
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
{
if ( !mThemeName.isEmpty() )
{
// get style overrides for theme
const QMap<QString, QString> overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
mLegendModel->setLayerStyleOverrides( overrides );
}
else
{
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
}
}
else
mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );

Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutitemlegend.h
Expand Up @@ -545,6 +545,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem

void setupMapConnections( QgsLayoutItemMap *map, bool connect = true );

void setModelStyleOverrides( const QMap<QString, QString> &overrides );

std::unique_ptr< QgsLegendModel > mLegendModel;
std::unique_ptr< QgsLayerTreeGroup > mCustomLayerTree;

Expand Down
109 changes: 109 additions & 0 deletions tests/src/python/test_qgslayoutlegend.py
Expand Up @@ -33,6 +33,10 @@
QgsMapLayerLegendUtils,
QgsLegendStyle,
QgsFontUtils,
QgsLineSymbol,
QgsMapThemeCollection,
QgsCategorizedSymbolRenderer,
QgsRendererCategory,
QgsApplication)
from qgis.testing import (start_app,
unittest
Expand Down Expand Up @@ -514,6 +518,111 @@ def testThemes(self):
legend.setLinkedMap(map3)
self.assertFalse(legend.themeName())

def testLegendRenderWithMapTheme(self):
"""Test rendering legends linked to map themes"""
QgsProject.instance().removeAllMapLayers()

point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
line_path = os.path.join(TEST_DATA_DIR, 'lines.shp')
line_layer = QgsVectorLayer(line_path, 'lines', 'ogr')
QgsProject.instance().clear()
QgsProject.instance().addMapLayers([point_layer, line_layer])

marker_symbol = QgsMarkerSymbol.createSimple({'color': '#ff0000', 'outline_style': 'no', 'size': '5'})
point_layer.setRenderer(QgsSingleSymbolRenderer(marker_symbol))
point_layer.styleManager().addStyleFromLayer("red")

line_symbol = QgsLineSymbol.createSimple({'color': '#ff0000', 'line_width': '2'})
line_layer.setRenderer(QgsSingleSymbolRenderer(line_symbol))
line_layer.styleManager().addStyleFromLayer("red")

red_record = QgsMapThemeCollection.MapThemeRecord()
point_red_record = QgsMapThemeCollection.MapThemeLayerRecord(point_layer)
point_red_record.usingCurrentStyle = True
point_red_record.currentStyle = 'red'
red_record.addLayerRecord(point_red_record)
line_red_record = QgsMapThemeCollection.MapThemeLayerRecord(line_layer)
line_red_record.usingCurrentStyle = True
line_red_record.currentStyle = 'red'
red_record.addLayerRecord(line_red_record)
QgsProject.instance().mapThemeCollection().insert('red', red_record)

marker_symbol1 = QgsMarkerSymbol.createSimple({'color': '#0000ff', 'outline_style': 'no', 'size': '5'})
marker_symbol2 = QgsMarkerSymbol.createSimple({'color': '#0000ff', 'name': 'diamond', 'outline_style': 'no', 'size': '5'})
marker_symbol3 = QgsMarkerSymbol.createSimple({'color': '#0000ff', 'name': 'rectangle', 'outline_style': 'no', 'size': '5'})

point_layer.setRenderer(QgsCategorizedSymbolRenderer('Class', [QgsRendererCategory('B52', marker_symbol1, ''),
QgsRendererCategory('Biplane', marker_symbol2, ''),
QgsRendererCategory('Jet', marker_symbol3, ''),
]))
point_layer.styleManager().addStyleFromLayer("blue")

line_symbol = QgsLineSymbol.createSimple({'color': '#0000ff', 'line_width': '2'})
line_layer.setRenderer(QgsSingleSymbolRenderer(line_symbol))
line_layer.styleManager().addStyleFromLayer("blue")

blue_record = QgsMapThemeCollection.MapThemeRecord()
point_blue_record = QgsMapThemeCollection.MapThemeLayerRecord(point_layer)
point_blue_record.usingCurrentStyle = True
point_blue_record.currentStyle = 'blue'
blue_record.addLayerRecord(point_blue_record)
line_blue_record = QgsMapThemeCollection.MapThemeLayerRecord(line_layer)
line_blue_record.usingCurrentStyle = True
line_blue_record.currentStyle = 'blue'
blue_record.addLayerRecord(line_blue_record)
QgsProject.instance().mapThemeCollection().insert('blue', blue_record)

layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()

map1 = QgsLayoutItemMap(layout)
map1.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map1.setFrameEnabled(True)
map1.setLayers([point_layer, line_layer])
layout.addLayoutItem(map1)
map1.setExtent(point_layer.extent())
map1.setFollowVisibilityPreset(True)
map1.setFollowVisibilityPresetName('red')

map2 = QgsLayoutItemMap(layout)
map2.attemptSetSceneRect(QRectF(20, 120, 80, 80))
map2.setFrameEnabled(True)
map2.setLayers([point_layer, line_layer])
layout.addLayoutItem(map2)
map2.setExtent(point_layer.extent())
map2.setFollowVisibilityPreset(True)
map2.setFollowVisibilityPresetName('blue')

legend = QgsLayoutItemLegend(layout)
legend.setTitle("Legend")
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
layout.addLayoutItem(legend)
legend.setLinkedMap(map1)

legend2 = QgsLayoutItemLegend(layout)
legend2.setTitle("Legend")
legend2.attemptSetSceneRect(QRectF(120, 120, 80, 80))
legend2.setFrameEnabled(True)
legend2.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend2.setBackgroundColor(QColor(200, 200, 200))
legend2.setTitle('')
layout.addLayoutItem(legend2)
legend2.setLinkedMap(map2)

checker = QgsLayoutChecker(
'composer_legend_theme', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)

QgsProject.instance().clear()


if __name__ == '__main__':
unittest.main()
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d40114c

Please sign in to comment.