From 016cfe60a0411761bbc0f0971b7cd22d88820689 Mon Sep 17 00:00:00 2001 From: uclaros Date: Thu, 16 May 2024 13:40:22 +0300 Subject: [PATCH 1/6] handle conditional visibility of data defined size legend nodes --- src/core/layertree/qgslayertreemodel.cpp | 13 +++++++++++-- src/core/qgsdatadefinedsizelegend.cpp | 6 ++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index fa19011575c7..647cf90eb894 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -1296,7 +1296,8 @@ QList QgsLayerTreeModel::filterLegendNodes( const { for ( QgsLayerTreeModelLegendNode *node : std::as_const( nodes ) ) { - switch ( node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).value() ) + const QgsLayerTreeModelLegendNode::NodeTypes nodeType = node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).value(); + switch ( nodeType ) { case QgsLayerTreeModelLegendNode::EmbeddedWidget: filtered << node; @@ -1319,7 +1320,15 @@ QList QgsLayerTreeModel::filterLegendNodes( const if ( QgsVectorLayer *vl = qobject_cast( node->layerNode()->layer() ) ) { auto it = mHitTestResults.constFind( vl->id() ); - if ( it != mHitTestResults.constEnd() && it->contains( ruleKey ) ) + if ( it != mHitTestResults.constEnd() && + ( it->contains( ruleKey ) || + ( !it->isEmpty() && // If there is at least one hit for this layer... + ( nodeType == QgsLayerTreeModelLegendNode::DataDefinedSizeLegend || // Show Collapsed data defined size legend... + ruleKey == QLatin1String( "data-defined-size" ) // and data defined size title or dds separated legend items + ) + ) + ) + ) { filtered << node; } diff --git a/src/core/qgsdatadefinedsizelegend.cpp b/src/core/qgsdatadefinedsizelegend.cpp index d9420824c772..b34ffa46d110 100644 --- a/src/core/qgsdatadefinedsizelegend.cpp +++ b/src/core/qgsdatadefinedsizelegend.cpp @@ -126,7 +126,8 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const QgsLegendSymbolList lst; if ( !mTitleLabel.isEmpty() ) { - QgsLegendSymbolItem title( nullptr, mTitleLabel, QString() ); + // we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related + QgsLegendSymbolItem title( nullptr, mTitleLabel, QStringLiteral( "data-defined-size" ) ); lst << title; } @@ -145,7 +146,8 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const lst.reserve( mSizeClasses.size() ); for ( const SizeClass &cl : mSizeClasses ) { - QgsLegendSymbolItem si( mSymbol.get(), cl.label, QString() ); + // we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related + QgsLegendSymbolItem si( mSymbol.get(), cl.label, QStringLiteral( "data-defined-size" ) ); QgsMarkerSymbol *s = static_cast( si.symbol() ); double size = cl.size; if ( mSizeScaleTransformer ) From 108aa23f67b9f43a77fffaf3ce46e6806c9c9d87 Mon Sep 17 00:00:00 2001 From: uclaros Date: Tue, 21 May 2024 09:42:00 +0300 Subject: [PATCH 2/6] tests added --- tests/src/core/testqgslegendrenderer.cpp | 206 ++++++++++++++++++ ...legend_data_defined_size_filter_by_map.png | Bin 0 -> 1597 bytes ...ted_legend_data_defined_size_separated.png | Bin 0 -> 19650 bytes 3 files changed, 206 insertions(+) create mode 100644 tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map.png create mode 100644 tests/testdata/control_images/legend/expected_legend_data_defined_size_separated/expected_legend_data_defined_size_separated.png diff --git a/tests/src/core/testqgslegendrenderer.cpp b/tests/src/core/testqgslegendrenderer.cpp index 788de1810fb2..ad957a612ffd 100644 --- a/tests/src/core/testqgslegendrenderer.cpp +++ b/tests/src/core/testqgslegendrenderer.cpp @@ -199,6 +199,9 @@ class TestQgsLegendRenderer : public QgsTest void testDiagramAttributeLegend(); void testDiagramSizeLegend(); void testDataDefinedSizeCollapsed(); + void testDataDefinedSizeSeparated(); + void testDataDefinedSizeCollapsedFilterByMap(); + void testDataDefinedSizeSeparatedFilterByMap(); void testTextOnSymbol(); void testColumnsMixedSymbolSize(); @@ -1544,6 +1547,209 @@ void TestQgsLegendRenderer::testDataDefinedSizeCollapsed() delete root; } +void TestQgsLegendRenderer::testDataDefinedSizeSeparated() +{ + const QString testName = QStringLiteral( "legend_data_defined_size_separated" ); + + QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) ); + { + QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); + QList attrs; + attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + pr->addAttributes( attrs ); + + QgsFields fields; + fields.append( attrs.back() ); + + const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) ); + + QList features; + QgsFeature f1( fields, 1 ); + f1.setAttribute( 0, 100 ); + f1.setGeometry( g ); + QgsFeature f2( fields, 2 ); + f2.setAttribute( 0, 200 ); + f2.setGeometry( g ); + QgsFeature f3( fields, 3 ); + f3.setAttribute( 0, 300 ); + f3.setGeometry( g ); + features << f1 << f2 << f3; + pr->addFeatures( features ); + vlDataDefinedSize->updateFields(); + } + + QVariantMap props; + props[QStringLiteral( "name" )] = QStringLiteral( "circle" ); + props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" ); + props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" ); + QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props ); + QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) ); + ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership + symbol->setDataDefinedSize( ddsProperty ); + + QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend(); + ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendSeparated ); + ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); + + QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership + r->setDataDefinedSizeLegend( ddsLegend ); + vlDataDefinedSize->setRenderer( r ); + + QgsLayerTree *root = new QgsLayerTree(); + root->addLayer( vlDataDefinedSize ); + + QgsLayerTreeModel legendModel( root ); + + QgsLegendSettings settings; + _setStandardTestFont( settings, QStringLiteral( "Bold" ) ); + QImage res = _renderLegend( &legendModel, settings ); + QVERIFY( _verifyImage( res, testName ) ); + + delete root; +} + +void TestQgsLegendRenderer::testDataDefinedSizeCollapsedFilterByMap() +{ + const QString testName = QStringLiteral( "legend_data_defined_size_filter_by_map" ); + + QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) ); + { + QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); + QList attrs; + attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + pr->addAttributes( attrs ); + + QgsFields fields; + fields.append( attrs.back() ); + + const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) ); + + QList features; + QgsFeature f1( fields, 1 ); + f1.setAttribute( 0, 100 ); + f1.setGeometry( g ); + QgsFeature f2( fields, 2 ); + f2.setAttribute( 0, 200 ); + f2.setGeometry( g ); + QgsFeature f3( fields, 3 ); + f3.setAttribute( 0, 300 ); + f3.setGeometry( g ); + features << f1 << f2 << f3; + pr->addFeatures( features ); + vlDataDefinedSize->updateFields(); + } + + QVariantMap props; + props[QStringLiteral( "name" )] = QStringLiteral( "circle" ); + props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" ); + props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" ); + QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props ); + QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) ); + ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership + symbol->setDataDefinedSize( ddsProperty ); + + QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend(); + ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendCollapsed ); + ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); + + QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership + r->setDataDefinedSizeLegend( ddsLegend ); + vlDataDefinedSize->setRenderer( r ); + + QgsLayerTree *root = new QgsLayerTree(); + root->addLayer( vlDataDefinedSize ); + + QgsLayerTreeModel legendModel( root ); + + QgsMapSettings mapSettings; + // extent and size to include only the red and green points + mapSettings.setExtent( QgsRectangle( 10, 10, 20, 20 ) ); + mapSettings.setOutputSize( QSize( 400, 100 ) ); + mapSettings.setOutputDpi( 96 ); + mapSettings.setLayers( QgsProject::instance()->mapLayers().values() ); + + QgsLayerTreeFilterSettings filterSettings( mapSettings ); + legendModel.setFilterSettings( &filterSettings ); + + QgsLegendSettings settings; + _setStandardTestFont( settings, QStringLiteral( "Bold" ) ); + QImage res = _renderLegend( &legendModel, settings ); + QVERIFY( _verifyImage( res, testName ) ); + + delete root; +} + +void TestQgsLegendRenderer::testDataDefinedSizeSeparatedFilterByMap() +{ + const QString testName = QStringLiteral( "legend_data_defined_size_filter_by_map" ); + + QgsVectorLayer *vlDataDefinedSize = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "Point Layer" ), QStringLiteral( "memory" ) ); + { + QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); + QList attrs; + attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + pr->addAttributes( attrs ); + + QgsFields fields; + fields.append( attrs.back() ); + + const QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( 1.0, 1.0 ) ); + + QList features; + QgsFeature f1( fields, 1 ); + f1.setAttribute( 0, 100 ); + f1.setGeometry( g ); + QgsFeature f2( fields, 2 ); + f2.setAttribute( 0, 200 ); + f2.setGeometry( g ); + QgsFeature f3( fields, 3 ); + f3.setAttribute( 0, 300 ); + f3.setGeometry( g ); + features << f1 << f2 << f3; + pr->addFeatures( features ); + vlDataDefinedSize->updateFields(); + } + + QVariantMap props; + props[QStringLiteral( "name" )] = QStringLiteral( "circle" ); + props[QStringLiteral( "color" )] = QStringLiteral( "200,200,200" ); + props[QStringLiteral( "outline_color" )] = QStringLiteral( "0,0,0" ); + QgsMarkerSymbol *symbol = QgsMarkerSymbol::createSimple( props ); + QgsProperty ddsProperty = QgsProperty::fromField( QStringLiteral( "test_attr" ) ); + ddsProperty.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear, 100, 300, 10, 30 ) ); // takes ownership + symbol->setDataDefinedSize( ddsProperty ); + + QgsDataDefinedSizeLegend *ddsLegend = new QgsDataDefinedSizeLegend(); + ddsLegend->setLegendType( QgsDataDefinedSizeLegend::LegendSeparated ); + ddsLegend->setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); + + QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( symbol ); // takes ownership + r->setDataDefinedSizeLegend( ddsLegend ); + vlDataDefinedSize->setRenderer( r ); + + QgsLayerTree *root = new QgsLayerTree(); + root->addLayer( vlDataDefinedSize ); + + QgsLayerTreeModel legendModel( root ); + + QgsMapSettings mapSettings; + // extent and size to include only the red and green points + mapSettings.setExtent( QgsRectangle( 10, 10, 20, 20 ) ); + mapSettings.setOutputSize( QSize( 400, 100 ) ); + mapSettings.setOutputDpi( 96 ); + mapSettings.setLayers( QgsProject::instance()->mapLayers().values() ); + + QgsLayerTreeFilterSettings filterSettings( mapSettings ); + legendModel.setFilterSettings( &filterSettings ); + + QgsLegendSettings settings; + _setStandardTestFont( settings, QStringLiteral( "Bold" ) ); + QImage res = _renderLegend( &legendModel, settings ); + QVERIFY( _verifyImage( res, testName ) ); + + delete root; +} + void TestQgsLegendRenderer::testTextOnSymbol() { const QString testName = QStringLiteral( "legend_text_on_symbol" ); diff --git a/tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map.png b/tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map.png new file mode 100644 index 0000000000000000000000000000000000000000..a8408d321447ab867913e9b82ff1b4f63903e036 GIT binary patch literal 1597 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 literal 0 HcmV?d00001 From cb491ea9baa466a260943edfb0135ad932f180c0 Mon Sep 17 00:00:00 2001 From: uclaros Date: Tue, 21 May 2024 16:13:07 +0300 Subject: [PATCH 3/6] update test masks --- ...end_data_defined_size_filter_by_map_mask.png | Bin 0 -> 724 bytes ..._legend_data_defined_size_separated_mask.png | Bin 0 -> 11352 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map_mask.png create mode 100644 tests/testdata/control_images/legend/expected_legend_data_defined_size_separated/expected_legend_data_defined_size_separated_mask.png diff --git a/tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map_mask.png b/tests/testdata/control_images/legend/expected_legend_data_defined_size_filter_by_map/expected_legend_data_defined_size_filter_by_map_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..3c7b5ca0f8fe2d50370c629332a43aa4f790e45e GIT binary patch literal 724 zcmeAS@N?(olHy`uVBq!ia0vp^=|F7C!3HE_*uSy@Db50q$YKTt{zMRFTw%XFlYxP$ z&C|s(q+-t7SvM!ODDb%6{P({*@|k9=qLNa_+_TVD7 zHO-|4YczpcHpljD{q|Y?`{{?9O6RP5d|UbPcb_}gYEz}x-FiFcx6Jb5>gV4%g2Q_5^bNDY+w>A|31%s&2X3 zZvBi&%HD2#`68hjCKEq*wbm<~>YR4JIX-*s+hcK^yPN*}(^@)XN|5MxQ%mI|601K2 zU;DM??yH?^J?Bqfx_8OCBe4rZ3xj$j4Ugr>T}$e@`?P!W!d5xAI{6FJ?eD9fzA<%& z_6M`m5%+$de!p4l+MX}(clfJ1>+Rn1-&;dpTK7)j?sLgM`=)b;e_`02 zJE^Y^J=eK*E3H%e_&e*VxAhkVofF+6q8~o>zx4XEmgSkcMxT-%*n2BpImP&6W|S!? hA>c?Kc!Zzv|Bu}lIpf*VSHNV-;OXk;vd$@?2>>-*QW*dM literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/legend/expected_legend_data_defined_size_separated/expected_legend_data_defined_size_separated_mask.png b/tests/testdata/control_images/legend/expected_legend_data_defined_size_separated/expected_legend_data_defined_size_separated_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..fdc0b947f40db12856761cf3d3ce213abbf672c5 GIT binary patch literal 11352 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 NP?S@XEtNJ6_+L{P`1Jq) literal 0 HcmV?d00001 From 3bb06c107118f62812404bce1390da43877de10d Mon Sep 17 00:00:00 2001 From: uclaros Date: Mon, 17 Jun 2024 17:12:25 +0300 Subject: [PATCH 4/6] add userData property to --- .../qgslayertreemodellegendnode.py | 6 +++++- .../qgslayertreemodellegendnode.sip.in | 1 + .../symbology/qgslegendsymbolitem.sip.in | 18 ++++++++++++++++++ .../qgslayertreemodellegendnode.py | 6 +++++- .../qgslayertreemodellegendnode.sip.in | 1 + .../symbology/qgslegendsymbolitem.sip.in | 18 ++++++++++++++++++ src/core/layertree/qgslayertreemodel.cpp | 7 ++----- .../layertree/qgslayertreemodellegendnode.h | 1 + src/core/qgsdatadefinedsizelegend.cpp | 11 +++++++---- src/core/symbology/qgslegendsymbolitem.cpp | 10 ++++++++++ src/core/symbology/qgslegendsymbolitem.h | 18 ++++++++++++++++++ 11 files changed, 86 insertions(+), 11 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgslayertreemodellegendnode.py b/python/PyQt6/core/auto_additions/qgslayertreemodellegendnode.py index 876670e4ec60..3f60080a2d42 100644 --- a/python/PyQt6/core/auto_additions/qgslayertreemodellegendnode.py +++ b/python/PyQt6/core/auto_additions/qgslayertreemodellegendnode.py @@ -13,7 +13,11 @@ QgsLayerTreeModelLegendNode.LegendNodeRoles.NodeTypeRole = QgsLayerTreeModelLegendNode.CustomRole.NodeType QgsLayerTreeModelLegendNode.NodeTypeRole.is_monkey_patched = True QgsLayerTreeModelLegendNode.NodeTypeRole.__doc__ = "Type of node. Added in 3.16" -QgsLayerTreeModelLegendNode.CustomRole.__doc__ = "Legend node data roles\n\n.. note::\n\n Prior to QGIS 3.36 this was available as QgsLayerTreeModelLegendNode.LegendNodeRoles\n\n.. versionadded:: 3.36\n\n" + '* ``RuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.RuleKey.__doc__ + '\n' + '* ``ParentRuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.ParentRuleKey.__doc__ + '\n' + '* ``NodeTypeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.NodeType.__doc__ +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole = QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize +QgsLayerTreeModelLegendNode.LegendNodeRoles.IsDataDefinedSizeRole = QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole.is_monkey_patched = True +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole.__doc__ = "Set when a node is related to data defined size (title or separated legend items). Added in 3.38" +QgsLayerTreeModelLegendNode.CustomRole.__doc__ = "Legend node data roles\n\n.. note::\n\n Prior to QGIS 3.36 this was available as QgsLayerTreeModelLegendNode.LegendNodeRoles\n\n.. versionadded:: 3.36\n\n" + '* ``RuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.RuleKey.__doc__ + '\n' + '* ``ParentRuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.ParentRuleKey.__doc__ + '\n' + '* ``NodeTypeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.NodeType.__doc__ + '\n' + '* ``IsDataDefinedSizeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize.__doc__ # -- QgsLayerTreeModelLegendNode.CustomRole.baseClass = QgsLayerTreeModelLegendNode QgsLayerTreeModelLegendNode.SimpleLegend = QgsLayerTreeModelLegendNode.NodeTypes.SimpleLegend diff --git a/python/PyQt6/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in b/python/PyQt6/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in index 22ce85d34b1c..4b4f0cc98dde 100644 --- a/python/PyQt6/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in +++ b/python/PyQt6/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in @@ -51,6 +51,7 @@ and customized look. RuleKey, ParentRuleKey, NodeType, + IsDataDefinedSize, }; enum NodeTypes /BaseType=IntEnum/ diff --git a/python/PyQt6/core/auto_generated/symbology/qgslegendsymbolitem.sip.in b/python/PyQt6/core/auto_generated/symbology/qgslegendsymbolitem.sip.in index 9df88ad6acdd..5228b39f8f23 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgslegendsymbolitem.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgslegendsymbolitem.sip.in @@ -110,6 +110,24 @@ Takes ownership of the settings object. QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings() const; %Docstring Returns extra information for data-defined size legend rendering. Normally it returns ``None``. +%End + + void setUserData( int key, QVariant &value ); +%Docstring +Adds a ``key`` - ``value`` pair to the item's user data. + +.. seealso:: :py:func:`userData` + +.. versionadded:: 3.38 +%End + + QVariant userData( int key ) const; +%Docstring +Retrieves the item's user data with the specified ``key``. + +.. seealso:: :py:func:`setUserData` + +.. versionadded:: 3.38 %End }; diff --git a/python/core/auto_additions/qgslayertreemodellegendnode.py b/python/core/auto_additions/qgslayertreemodellegendnode.py index 53d16ec17ec8..c165ff7ca078 100644 --- a/python/core/auto_additions/qgslayertreemodellegendnode.py +++ b/python/core/auto_additions/qgslayertreemodellegendnode.py @@ -13,6 +13,10 @@ QgsLayerTreeModelLegendNode.LegendNodeRoles.NodeTypeRole = QgsLayerTreeModelLegendNode.CustomRole.NodeType QgsLayerTreeModelLegendNode.NodeTypeRole.is_monkey_patched = True QgsLayerTreeModelLegendNode.NodeTypeRole.__doc__ = "Type of node. Added in 3.16" -QgsLayerTreeModelLegendNode.CustomRole.__doc__ = "Legend node data roles\n\n.. note::\n\n Prior to QGIS 3.36 this was available as QgsLayerTreeModelLegendNode.LegendNodeRoles\n\n.. versionadded:: 3.36\n\n" + '* ``RuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.RuleKey.__doc__ + '\n' + '* ``ParentRuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.ParentRuleKey.__doc__ + '\n' + '* ``NodeTypeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.NodeType.__doc__ +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole = QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize +QgsLayerTreeModelLegendNode.LegendNodeRoles.IsDataDefinedSizeRole = QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole.is_monkey_patched = True +QgsLayerTreeModelLegendNode.IsDataDefinedSizeRole.__doc__ = "Set when a node is related to data defined size (title or separated legend items). Added in 3.38" +QgsLayerTreeModelLegendNode.CustomRole.__doc__ = "Legend node data roles\n\n.. note::\n\n Prior to QGIS 3.36 this was available as QgsLayerTreeModelLegendNode.LegendNodeRoles\n\n.. versionadded:: 3.36\n\n" + '* ``RuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.RuleKey.__doc__ + '\n' + '* ``ParentRuleKeyRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.ParentRuleKey.__doc__ + '\n' + '* ``NodeTypeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.NodeType.__doc__ + '\n' + '* ``IsDataDefinedSizeRole``: ' + QgsLayerTreeModelLegendNode.CustomRole.IsDataDefinedSize.__doc__ # -- QgsLayerTreeModelLegendNode.CustomRole.baseClass = QgsLayerTreeModelLegendNode diff --git a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in index 7b09fee9f685..2fc28ca3d363 100644 --- a/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in +++ b/python/core/auto_generated/layertree/qgslayertreemodellegendnode.sip.in @@ -51,6 +51,7 @@ and customized look. RuleKey, ParentRuleKey, NodeType, + IsDataDefinedSize, }; enum NodeTypes diff --git a/python/core/auto_generated/symbology/qgslegendsymbolitem.sip.in b/python/core/auto_generated/symbology/qgslegendsymbolitem.sip.in index 9df88ad6acdd..5228b39f8f23 100644 --- a/python/core/auto_generated/symbology/qgslegendsymbolitem.sip.in +++ b/python/core/auto_generated/symbology/qgslegendsymbolitem.sip.in @@ -110,6 +110,24 @@ Takes ownership of the settings object. QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings() const; %Docstring Returns extra information for data-defined size legend rendering. Normally it returns ``None``. +%End + + void setUserData( int key, QVariant &value ); +%Docstring +Adds a ``key`` - ``value`` pair to the item's user data. + +.. seealso:: :py:func:`userData` + +.. versionadded:: 3.38 +%End + + QVariant userData( int key ) const; +%Docstring +Retrieves the item's user data with the specified ``key``. + +.. seealso:: :py:func:`setUserData` + +.. versionadded:: 3.38 %End }; diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index 647cf90eb894..85c918ba204d 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -1312,6 +1312,7 @@ QList QgsLayerTreeModel::filterLegendNodes( const case QgsLayerTreeModelLegendNode::ColorRampLegend: { const QString ruleKey = node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString(); + const bool isDataDefinedSize = node->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::IsDataDefinedSize ) ).toBool(); const bool checked = ( mFilterSettings && !( mFilterSettings->flags() & Qgis::LayerTreeFilterFlag::SkipVisibilityCheck ) ) || node->data( Qt::CheckStateRole ).toInt() == Qt::Checked; @@ -1322,11 +1323,7 @@ QList QgsLayerTreeModel::filterLegendNodes( const auto it = mHitTestResults.constFind( vl->id() ); if ( it != mHitTestResults.constEnd() && ( it->contains( ruleKey ) || - ( !it->isEmpty() && // If there is at least one hit for this layer... - ( nodeType == QgsLayerTreeModelLegendNode::DataDefinedSizeLegend || // Show Collapsed data defined size legend... - ruleKey == QLatin1String( "data-defined-size" ) // and data defined size title or dds separated legend items - ) - ) + ( !it->isEmpty() && isDataDefinedSize ) ) ) { diff --git a/src/core/layertree/qgslayertreemodellegendnode.h b/src/core/layertree/qgslayertreemodellegendnode.h index 39385684af37..7f3bed0e3856 100644 --- a/src/core/layertree/qgslayertreemodellegendnode.h +++ b/src/core/layertree/qgslayertreemodellegendnode.h @@ -92,6 +92,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject RuleKey SIP_MONKEYPATCH_COMPAT_NAME( RuleKeyRole ) = Qt::UserRole, //!< Rule key of the node (QString) ParentRuleKey SIP_MONKEYPATCH_COMPAT_NAME( ParentRuleKeyRole ), //!< Rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2.8 NodeType SIP_MONKEYPATCH_COMPAT_NAME( NodeTypeRole ), //!< Type of node. Added in 3.16 + IsDataDefinedSize SIP_MONKEYPATCH_COMPAT_NAME( IsDataDefinedSizeRole ), //!< Set when a node is related to data defined size (title or separated legend items). Added in 3.38 }; Q_ENUM( CustomRole ) // *INDENT-ON* diff --git a/src/core/qgsdatadefinedsizelegend.cpp b/src/core/qgsdatadefinedsizelegend.cpp index b34ffa46d110..c8b2119a6d8e 100644 --- a/src/core/qgsdatadefinedsizelegend.cpp +++ b/src/core/qgsdatadefinedsizelegend.cpp @@ -15,6 +15,7 @@ #include "qgsdatadefinedsizelegend.h" +#include "qgslayertreemodellegendnode.h" #include "qgsproperty.h" #include "qgspropertytransformer.h" #include "qgssymbollayerutils.h" @@ -124,10 +125,11 @@ void QgsDataDefinedSizeLegend::updateFromSymbolAndProperty( const QgsMarkerSymbo QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const { QgsLegendSymbolList lst; + QVariant isDataDefinedSize( true ); if ( !mTitleLabel.isEmpty() ) { - // we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related - QgsLegendSymbolItem title( nullptr, mTitleLabel, QStringLiteral( "data-defined-size" ) ); + QgsLegendSymbolItem title( nullptr, mTitleLabel, QString() ); + title.setUserData( static_cast( QgsLayerTreeModelLegendNode::CustomRole::IsDataDefinedSize ), isDataDefinedSize ); lst << title; } @@ -137,6 +139,7 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const { QgsLegendSymbolItem i; i.setDataDefinedSizeLegendSettings( new QgsDataDefinedSizeLegend( *this ) ); + i.setUserData( static_cast( QgsLayerTreeModelLegendNode::CustomRole::IsDataDefinedSize ), isDataDefinedSize ); lst << i; break; } @@ -146,8 +149,8 @@ QgsLegendSymbolList QgsDataDefinedSizeLegend::legendSymbolList() const lst.reserve( mSizeClasses.size() ); for ( const SizeClass &cl : mSizeClasses ) { - // we're abusing the ruleKey field here so we can later identify the resulting legend nodes as data defined size related - QgsLegendSymbolItem si( mSymbol.get(), cl.label, QStringLiteral( "data-defined-size" ) ); + QgsLegendSymbolItem si( mSymbol.get(), cl.label, QString() ); + si.setUserData( static_cast( QgsLayerTreeModelLegendNode::CustomRole::IsDataDefinedSize ), isDataDefinedSize ); QgsMarkerSymbol *s = static_cast( si.symbol() ); double size = cl.size; if ( mSizeScaleTransformer ) diff --git a/src/core/symbology/qgslegendsymbolitem.cpp b/src/core/symbology/qgslegendsymbolitem.cpp index d213cc396a4c..2abc74905839 100644 --- a/src/core/symbology/qgslegendsymbolitem.cpp +++ b/src/core/symbology/qgslegendsymbolitem.cpp @@ -93,3 +93,13 @@ QgsDataDefinedSizeLegend *QgsLegendSymbolItem::dataDefinedSizeLegendSettings() c { return mDataDefinedSizeLegendSettings; } + +void QgsLegendSymbolItem::setUserData( int key, QVariant &value ) +{ + mUserData.insert( key, value ); +} + +QVariant QgsLegendSymbolItem::userData( int key ) const +{ + return mUserData.value( key, QVariant() ); +} diff --git a/src/core/symbology/qgslegendsymbolitem.h b/src/core/symbology/qgslegendsymbolitem.h index 9a3225f19438..59084878a565 100644 --- a/src/core/symbology/qgslegendsymbolitem.h +++ b/src/core/symbology/qgslegendsymbolitem.h @@ -108,6 +108,22 @@ class CORE_EXPORT QgsLegendSymbolItem */ QgsDataDefinedSizeLegend *dataDefinedSizeLegendSettings() const; + /** + * Adds a \a key - \a value pair to the item's user data. + * + * \see userData() + * \since QGIS 3.38 + */ + void setUserData( int key, QVariant &value ); + + /** + * Retrieves the item's user data with the specified \a key. + * + * \see setUserData() + * \since QGIS 3.38 + */ + QVariant userData( int key ) const; + private: //! Legend symbol -- may be NULLPTR. QgsSymbol *mSymbol = nullptr; @@ -135,6 +151,8 @@ class CORE_EXPORT QgsLegendSymbolItem int mLevel = 0; //! Key of the parent legend node. For legends with tree hierarchy QString mParentKey; + + QMap< int, QVariant > mUserData; }; typedef QList< QgsLegendSymbolItem > QgsLegendSymbolList; From 2bc7bc57547215da53a7e4dab7884057e060bb40 Mon Sep 17 00:00:00 2001 From: uclaros Date: Tue, 18 Jun 2024 12:50:21 +0300 Subject: [PATCH 5/6] don't use deprecated QgsField constructor --- tests/src/core/testqgslegendrenderer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/core/testqgslegendrenderer.cpp b/tests/src/core/testqgslegendrenderer.cpp index 54e583fa2592..86f4e01c6bec 100644 --- a/tests/src/core/testqgslegendrenderer.cpp +++ b/tests/src/core/testqgslegendrenderer.cpp @@ -1555,7 +1555,7 @@ void TestQgsLegendRenderer::testDataDefinedSizeSeparated() { QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); QList attrs; - attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + attrs << QgsField( QStringLiteral( "test_attr" ), QMetaType::Type::Int ); pr->addAttributes( attrs ); QgsFields fields; @@ -1616,7 +1616,7 @@ void TestQgsLegendRenderer::testDataDefinedSizeCollapsedFilterByMap() { QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); QList attrs; - attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + attrs << QgsField( QStringLiteral( "test_attr" ), QMetaType::Type::Int ); pr->addAttributes( attrs ); QgsFields fields; @@ -1687,7 +1687,7 @@ void TestQgsLegendRenderer::testDataDefinedSizeSeparatedFilterByMap() { QgsVectorDataProvider *pr = vlDataDefinedSize->dataProvider(); QList attrs; - attrs << QgsField( QStringLiteral( "test_attr" ), QVariant::Int ); + attrs << QgsField( QStringLiteral( "test_attr" ), QMetaType::Type::Int ); pr->addAttributes( attrs ); QgsFields fields; From a31dfb5c2be8af26cf173286fd183c746fd557f4 Mon Sep 17 00:00:00 2001 From: uclaros Date: Thu, 20 Jun 2024 23:54:19 +0300 Subject: [PATCH 6/6] add mUserData to copy constructor --- src/core/symbology/qgslegendsymbolitem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/symbology/qgslegendsymbolitem.cpp b/src/core/symbology/qgslegendsymbolitem.cpp index 2abc74905839..ef545020fef5 100644 --- a/src/core/symbology/qgslegendsymbolitem.cpp +++ b/src/core/symbology/qgslegendsymbolitem.cpp @@ -59,6 +59,7 @@ QgsLegendSymbolItem &QgsLegendSymbolItem::operator=( const QgsLegendSymbolItem & mScaleMaxDenom = other.mScaleMaxDenom; mLevel = other.mLevel; mParentKey = other.mParentKey; + mUserData = other.mUserData; return *this; }