diff --git a/python/core/layout/qgslayoutitemattributetable.sip.in b/python/core/layout/qgslayoutitemattributetable.sip.in index 10a7f29f2be..d9235ae5556 100644 --- a/python/core/layout/qgslayoutitemattributetable.sip.in +++ b/python/core/layout/qgslayoutitemattributetable.sip.in @@ -295,6 +295,9 @@ be replaced by a line break. virtual void finalizeRestoreFromXml(); + virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ); + + protected: virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const; diff --git a/python/core/layout/qgslayoutobject.sip.in b/python/core/layout/qgslayoutobject.sip.in index 1905b3b6048..788c8333e64 100644 --- a/python/core/layout/qgslayoutobject.sip.in +++ b/python/core/layout/qgslayoutobject.sip.in @@ -71,6 +71,8 @@ A base class for objects which belong to a layout. ScalebarFillColor2, ScalebarLineColor, ScalebarLineWidth, + //table + AttributeTableSourceLayer, }; enum PropertyValueType diff --git a/src/app/layout/qgslayoutattributetablewidget.cpp b/src/app/layout/qgslayoutattributetablewidget.cpp index 1cd843be903..1570ab8b87c 100644 --- a/src/app/layout/qgslayoutattributetablewidget.cpp +++ b/src/app/layout/qgslayoutattributetablewidget.cpp @@ -131,8 +131,12 @@ QgsLayoutAttributeTableWidget::QgsLayoutAttributeTableWidget( QgsLayoutFrame *fr { connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutAttributeTableWidget::atlasToggled ); } + + mLayerSourceDDBtn->registerExpressionContextGenerator( mTable ); } + registerDataDefinedButton( mLayerSourceDDBtn, QgsLayoutObject::AttributeTableSourceLayer ); + //embed widget for general options if ( mFrame ) { @@ -925,6 +929,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls() case QgsLayoutItemAttributeTable::LayerAttributes: mLayerComboBox->setEnabled( true ); mLayerComboBox->setVisible( true ); + mLayerSourceDDBtn->setVisible( true ); mLayerLabel->setVisible( true ); mRelationsComboBox->setEnabled( false ); mRelationsComboBox->setVisible( false ); @@ -938,6 +943,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls() case QgsLayoutItemAttributeTable::AtlasFeature: mLayerComboBox->setEnabled( false ); mLayerComboBox->setVisible( false ); + mLayerSourceDDBtn->setVisible( false ); mLayerLabel->setVisible( false ); mRelationsComboBox->setEnabled( false ); mRelationsComboBox->setVisible( false ); @@ -952,6 +958,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls() mLayerComboBox->setEnabled( false ); mLayerComboBox->setVisible( false ); mLayerLabel->setVisible( false ); + mLayerSourceDDBtn->setVisible( false ); mRelationsComboBox->setEnabled( true ); mRelationsComboBox->setVisible( true ); mRelationLabel->setVisible( true ); diff --git a/src/core/layout/qgslayoutitemattributetable.cpp b/src/core/layout/qgslayoutitemattributetable.cpp index 6ad26f63356..a0825bfddc9 100644 --- a/src/core/layout/qgslayoutitemattributetable.cpp +++ b/src/core/layout/qgslayoutitemattributetable.cpp @@ -552,6 +552,28 @@ void QgsLayoutItemAttributeTable::finalizeRestoreFromXml() } } +void QgsLayoutItemAttributeTable::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property ) +{ + QgsExpressionContext context = createExpressionContext(); + + if ( mSource == QgsLayoutItemAttributeTable::LayerAttributes && + ( property == QgsLayoutObject::AttributeTableSourceLayer || property == QgsLayoutObject::AllProperties ) ) + { + mDataDefinedVectorLayer = nullptr; + + QString currentLayerIdentifier; + if ( QgsVectorLayer *currentLayer = mVectorLayer.get() ) + currentLayerIdentifier = currentLayer->id(); + + const QString layerIdentifier = mDataDefinedProperties.valueAsString( QgsLayoutObject::AttributeTableSourceLayer, context, currentLayerIdentifier ); + QgsVectorLayer *ddLayer = qobject_cast< QgsVectorLayer * >( QgsLayoutUtils::mapLayerFromString( layerIdentifier, mLayout->project() ) ); + if ( ddLayer ) + mDataDefinedVectorLayer = ddLayer; + } + + QgsLayoutMultiFrame::refreshDataDefinedProperty( property ); +} + QVariant QgsLayoutItemAttributeTable::replaceWrapChar( const QVariant &variant ) const { //avoid converting variants to string if not required (try to maintain original type for sorting) @@ -570,7 +592,12 @@ QgsVectorLayer *QgsLayoutItemAttributeTable::sourceLayer() const case QgsLayoutItemAttributeTable::AtlasFeature: return mLayout->reportContext().layer(); case QgsLayoutItemAttributeTable::LayerAttributes: - return mVectorLayer.get(); + { + if ( mDataDefinedVectorLayer ) + return mDataDefinedVectorLayer; + else + return mVectorLayer.get(); + } case QgsLayoutItemAttributeTable::RelationChildren: { QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId ); diff --git a/src/core/layout/qgslayoutitemattributetable.h b/src/core/layout/qgslayoutitemattributetable.h index ec2d761affa..d2dd9b1909f 100644 --- a/src/core/layout/qgslayoutitemattributetable.h +++ b/src/core/layout/qgslayoutitemattributetable.h @@ -292,6 +292,8 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable QgsExpressionContext createExpressionContext() const override; void finalizeRestoreFromXml() override; + void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override; + protected: bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override; @@ -303,6 +305,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable ContentSource mSource = LayerAttributes; //! Associated vector layer QgsVectorLayerRef mVectorLayer; + + //! Data defined vector layer - only + QPointer< QgsVectorLayer > mDataDefinedVectorLayer; + //! Relation id, if in relation children mode QString mRelationId; diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index 255b1c27158..637866632f2 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -76,6 +76,7 @@ void QgsLayoutObject::initPropertyDefinitions() { QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) }, { QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) }, { QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) }, + { QgsLayoutObject::AttributeTableSourceLayer, QgsPropertyDefinition( "dataDefinedAttributeTableSourceLayer", QObject::tr( "Table source layer" ), QgsPropertyDefinition::String ) }, }; } diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index bfc04d0b7d5..08bc21ff4cb 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -92,7 +92,9 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe ScalebarFillColor, //!< Scalebar fill color ScalebarFillColor2, //!< Scalebar secondary fill color ScalebarLineColor, //!< Scalebar line color - ScalebarLineWidth, //!< Scalebar line width + ScalebarLineWidth, //!< Scalebar line width, + //table item + AttributeTableSourceLayer, //!< Attribute table source layer }; /** diff --git a/src/ui/layout/qgslayoutattributetablewidgetbase.ui b/src/ui/layout/qgslayoutattributetablewidgetbase.ui index a9ac77b7dc2..8f58f20760e 100644 --- a/src/ui/layout/qgslayoutattributetablewidgetbase.ui +++ b/src/ui/layout/qgslayoutattributetablewidgetbase.ui @@ -55,8 +55,8 @@ 0 0 - 689 - 2574 + 394 + 1487 @@ -85,6 +85,30 @@ + + + + Relation + + + + + + + + + + Refresh table data + + + + + + + Attributes... + + + @@ -96,38 +120,25 @@ - - - - 0 - 0 - - - - - - - - Relation - - - - - - - - - - Refresh table data - - - - - - - Attributes... - - + + + + + + 0 + 0 + + + + + + + + + + + + @@ -832,12 +843,18 @@ QComboBox
qgslayoutitemcombobox.h
+ + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
scrollArea groupBox mSourceComboBox mLayerComboBox + mLayerSourceDDBtn mRelationsComboBox mRefreshPushButton mAttributesPushButton @@ -905,6 +922,8 @@ + + diff --git a/tests/src/core/testqgslayouttable.cpp b/tests/src/core/testqgslayouttable.cpp index af5b188b71f..961aa2f8f59 100644 --- a/tests/src/core/testqgslayouttable.cpp +++ b/tests/src/core/testqgslayouttable.cpp @@ -70,6 +70,7 @@ class TestQgsLayoutTable : public QObject void autoWrap(); //test auto word wrap void cellStyles(); //test cell styles void cellStylesRender(); //test rendering cell styles + void dataDefinedSource(); private: QgsVectorLayer *mVectorLayer = nullptr; @@ -1323,5 +1324,69 @@ void TestQgsLayoutTable::cellStylesRender() QVERIFY( checker.testLayout( mReport, 0 ) ); } +void TestQgsLayoutTable::dataDefinedSource() +{ + // add a couple of layers + QgsVectorLayer *layer1 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) ); + QVERIFY( layer1->isValid() ); + QgsFeature f( layer1->fields() ); + f.setAttributes( QgsAttributes() << 1 << 2 << 3 ); + layer1->dataProvider()->addFeature( f ); + + // different field order + QgsVectorLayer *layer2 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer&field=col2:integer" ), QStringLiteral( "l2" ), QStringLiteral( "memory" ) ); + QVERIFY( layer2->isValid() ); + QgsFeature f2( layer2->fields() ); + f2.setAttributes( QgsAttributes() << 11 << 13 << 12 ); + layer2->dataProvider()->addFeature( f2 ); + + // missing fields + QgsVectorLayer *layer3 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer" ), QStringLiteral( "l3" ), QStringLiteral( "memory" ) ); + QVERIFY( layer3->isValid() ); + QgsFeature f3( layer3->fields() ); + f3.setAttributes( QgsAttributes() << 21 << 23 ); + layer3->dataProvider()->addFeature( f3 ); + + QgsProject p; + p.addMapLayer( layer1 ); + p.addMapLayer( layer2 ); + p.addMapLayer( layer3 ); + + QgsLayout l( &p ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setSource( QgsLayoutItemAttributeTable::LayerAttributes ); + table->setVectorLayer( layer1 ); + table->setMaximumNumberOfFeatures( 50 ); + QCOMPARE( table->contents().length(), 1 ); + QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 ); + + // data defined table name, by layer id + table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, layer1->id() ); + table->refresh(); + QCOMPARE( table->contents().length(), 1 ); + QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 ); + + // by layer name + table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "l2" ) ); + table->refresh(); + QCOMPARE( table->contents().length(), 1 ); + QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 11 << 12 << 13 ); + + // by layer name (case insensitive) + table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "L3" ) ); + table->refresh(); + QCOMPARE( table->contents().length(), 1 ); + QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 21 << QVariant() << 23 ); + + // delete current data defined layer match + p.removeMapLayer( layer3->id() ); + QApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete ); + // expect table to return to preset layer + table->refreshAttributes(); + QCOMPARE( table->contents().length(), 1 ); + QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 ); +} + QGSTEST_MAIN( TestQgsLayoutTable ) #include "testqgslayouttable.moc"