Skip to content

Commit 0c1ceb3

Browse files
committed
[FEATURE][layouts] Data defined table source for attribute table items
When an attribute table is set to a "Layer features" source, this allows the underlying vector layer from which to source features to be data defined. (All existing table attributes (column settings) are left intact, so setting a data defined table to a layer with different fields will result in empty columns in the table.) Sponsored by Kartoza/Inasafe
1 parent 9732466 commit 0c1ceb3

9 files changed

+160
-28
lines changed

python/core/layout/qgslayoutitemattributetable.sip.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ be replaced by a line break.
295295
virtual void finalizeRestoreFromXml();
296296

297297

298+
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
299+
300+
298301
protected:
299302

300303
virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const;

python/core/layout/qgslayoutobject.sip.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ A base class for objects which belong to a layout.
7171
ScalebarFillColor2,
7272
ScalebarLineColor,
7373
ScalebarLineWidth,
74+
//table
75+
AttributeTableSourceLayer,
7476
};
7577

7678
enum PropertyValueType

src/app/layout/qgslayoutattributetablewidget.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,12 @@ QgsLayoutAttributeTableWidget::QgsLayoutAttributeTableWidget( QgsLayoutFrame *fr
131131
{
132132
connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutAttributeTableWidget::atlasToggled );
133133
}
134+
135+
mLayerSourceDDBtn->registerExpressionContextGenerator( mTable );
134136
}
135137

138+
registerDataDefinedButton( mLayerSourceDDBtn, QgsLayoutObject::AttributeTableSourceLayer );
139+
136140
//embed widget for general options
137141
if ( mFrame )
138142
{
@@ -925,6 +929,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
925929
case QgsLayoutItemAttributeTable::LayerAttributes:
926930
mLayerComboBox->setEnabled( true );
927931
mLayerComboBox->setVisible( true );
932+
mLayerSourceDDBtn->setVisible( true );
928933
mLayerLabel->setVisible( true );
929934
mRelationsComboBox->setEnabled( false );
930935
mRelationsComboBox->setVisible( false );
@@ -938,6 +943,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
938943
case QgsLayoutItemAttributeTable::AtlasFeature:
939944
mLayerComboBox->setEnabled( false );
940945
mLayerComboBox->setVisible( false );
946+
mLayerSourceDDBtn->setVisible( false );
941947
mLayerLabel->setVisible( false );
942948
mRelationsComboBox->setEnabled( false );
943949
mRelationsComboBox->setVisible( false );
@@ -952,6 +958,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
952958
mLayerComboBox->setEnabled( false );
953959
mLayerComboBox->setVisible( false );
954960
mLayerLabel->setVisible( false );
961+
mLayerSourceDDBtn->setVisible( false );
955962
mRelationsComboBox->setEnabled( true );
956963
mRelationsComboBox->setVisible( true );
957964
mRelationLabel->setVisible( true );

src/core/layout/qgslayoutitemattributetable.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,28 @@ void QgsLayoutItemAttributeTable::finalizeRestoreFromXml()
552552
}
553553
}
554554

555+
void QgsLayoutItemAttributeTable::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
556+
{
557+
QgsExpressionContext context = createExpressionContext();
558+
559+
if ( mSource == QgsLayoutItemAttributeTable::LayerAttributes &&
560+
( property == QgsLayoutObject::AttributeTableSourceLayer || property == QgsLayoutObject::AllProperties ) )
561+
{
562+
mDataDefinedVectorLayer = nullptr;
563+
564+
QString currentLayerIdentifier;
565+
if ( QgsVectorLayer *currentLayer = mVectorLayer.get() )
566+
currentLayerIdentifier = currentLayer->id();
567+
568+
const QString layerIdentifier = mDataDefinedProperties.valueAsString( QgsLayoutObject::AttributeTableSourceLayer, context, currentLayerIdentifier );
569+
QgsVectorLayer *ddLayer = qobject_cast< QgsVectorLayer * >( QgsLayoutUtils::mapLayerFromString( layerIdentifier, mLayout->project() ) );
570+
if ( ddLayer )
571+
mDataDefinedVectorLayer = ddLayer;
572+
}
573+
574+
QgsLayoutMultiFrame::refreshDataDefinedProperty( property );
575+
}
576+
555577
QVariant QgsLayoutItemAttributeTable::replaceWrapChar( const QVariant &variant ) const
556578
{
557579
//avoid converting variants to string if not required (try to maintain original type for sorting)
@@ -570,7 +592,12 @@ QgsVectorLayer *QgsLayoutItemAttributeTable::sourceLayer() const
570592
case QgsLayoutItemAttributeTable::AtlasFeature:
571593
return mLayout->reportContext().layer();
572594
case QgsLayoutItemAttributeTable::LayerAttributes:
573-
return mVectorLayer.get();
595+
{
596+
if ( mDataDefinedVectorLayer )
597+
return mDataDefinedVectorLayer;
598+
else
599+
return mVectorLayer.get();
600+
}
574601
case QgsLayoutItemAttributeTable::RelationChildren:
575602
{
576603
QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );

src/core/layout/qgslayoutitemattributetable.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
292292
QgsExpressionContext createExpressionContext() const override;
293293
void finalizeRestoreFromXml() override;
294294

295+
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
296+
295297
protected:
296298

297299
bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
@@ -303,6 +305,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
303305
ContentSource mSource = LayerAttributes;
304306
//! Associated vector layer
305307
QgsVectorLayerRef mVectorLayer;
308+
309+
//! Data defined vector layer - only
310+
QPointer< QgsVectorLayer > mDataDefinedVectorLayer;
311+
306312
//! Relation id, if in relation children mode
307313
QString mRelationId;
308314

src/core/layout/qgslayoutobject.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ void QgsLayoutObject::initPropertyDefinitions()
7676
{ QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
7777
{ QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) },
7878
{ QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) },
79+
{ QgsLayoutObject::AttributeTableSourceLayer, QgsPropertyDefinition( "dataDefinedAttributeTableSourceLayer", QObject::tr( "Table source layer" ), QgsPropertyDefinition::String ) },
7980
};
8081
}
8182

src/core/layout/qgslayoutobject.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
9292
ScalebarFillColor, //!< Scalebar fill color
9393
ScalebarFillColor2, //!< Scalebar secondary fill color
9494
ScalebarLineColor, //!< Scalebar line color
95-
ScalebarLineWidth, //!< Scalebar line width
95+
ScalebarLineWidth, //!< Scalebar line width,
96+
//table item
97+
AttributeTableSourceLayer, //!< Attribute table source layer
9698
};
9799

98100
/**

src/ui/layout/qgslayoutattributetablewidgetbase.ui

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
<rect>
5656
<x>0</x>
5757
<y>0</y>
58-
<width>689</width>
59-
<height>2574</height>
58+
<width>394</width>
59+
<height>1487</height>
6060
</rect>
6161
</property>
6262
<layout class="QVBoxLayout" name="mainLayout">
@@ -85,50 +85,61 @@
8585
<item row="0" column="1">
8686
<widget class="QComboBox" name="mSourceComboBox"/>
8787
</item>
88-
<item row="1" column="0">
89-
<widget class="QLabel" name="mLayerLabel">
90-
<property name="text">
91-
<string>Layer</string>
92-
</property>
93-
<property name="wordWrap">
94-
<bool>true</bool>
95-
</property>
96-
</widget>
97-
</item>
98-
<item row="1" column="1">
99-
<widget class="QgsMapLayerComboBox" name="mLayerComboBox">
100-
<property name="sizePolicy">
101-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
102-
<horstretch>0</horstretch>
103-
<verstretch>0</verstretch>
104-
</sizepolicy>
105-
</property>
106-
</widget>
107-
</item>
108-
<item row="2" column="0">
88+
<item row="5" column="0">
10989
<widget class="QLabel" name="mRelationLabel">
11090
<property name="text">
11191
<string>Relation</string>
11292
</property>
11393
</widget>
11494
</item>
115-
<item row="2" column="1">
95+
<item row="5" column="1">
11696
<widget class="QComboBox" name="mRelationsComboBox"/>
11797
</item>
118-
<item row="3" column="0" colspan="2">
98+
<item row="6" column="0" colspan="2">
11999
<widget class="QPushButton" name="mRefreshPushButton">
120100
<property name="text">
121101
<string>Refresh table data</string>
122102
</property>
123103
</widget>
124104
</item>
125-
<item row="4" column="0" colspan="2">
105+
<item row="7" column="0" colspan="2">
126106
<widget class="QPushButton" name="mAttributesPushButton">
127107
<property name="text">
128108
<string>Attributes...</string>
129109
</property>
130110
</widget>
131111
</item>
112+
<item row="1" column="0">
113+
<widget class="QLabel" name="mLayerLabel">
114+
<property name="text">
115+
<string>Layer</string>
116+
</property>
117+
<property name="wordWrap">
118+
<bool>true</bool>
119+
</property>
120+
</widget>
121+
</item>
122+
<item row="1" column="1">
123+
<layout class="QHBoxLayout" name="horizontalLayout_6">
124+
<item>
125+
<widget class="QgsMapLayerComboBox" name="mLayerComboBox">
126+
<property name="sizePolicy">
127+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
128+
<horstretch>0</horstretch>
129+
<verstretch>0</verstretch>
130+
</sizepolicy>
131+
</property>
132+
</widget>
133+
</item>
134+
<item>
135+
<widget class="QgsPropertyOverrideButton" name="mLayerSourceDDBtn">
136+
<property name="text">
137+
<string>…</string>
138+
</property>
139+
</widget>
140+
</item>
141+
</layout>
142+
</item>
132143
</layout>
133144
</widget>
134145
</item>
@@ -832,12 +843,18 @@
832843
<extends>QComboBox</extends>
833844
<header>qgslayoutitemcombobox.h</header>
834845
</customwidget>
846+
<customwidget>
847+
<class>QgsPropertyOverrideButton</class>
848+
<extends>QToolButton</extends>
849+
<header>qgspropertyoverridebutton.h</header>
850+
</customwidget>
835851
</customwidgets>
836852
<tabstops>
837853
<tabstop>scrollArea</tabstop>
838854
<tabstop>groupBox</tabstop>
839855
<tabstop>mSourceComboBox</tabstop>
840856
<tabstop>mLayerComboBox</tabstop>
857+
<tabstop>mLayerSourceDDBtn</tabstop>
841858
<tabstop>mRelationsComboBox</tabstop>
842859
<tabstop>mRefreshPushButton</tabstop>
843860
<tabstop>mAttributesPushButton</tabstop>
@@ -905,6 +922,8 @@
905922
<include location="../../../images/images.qrc"/>
906923
<include location="../../../images/images.qrc"/>
907924
<include location="../../../images/images.qrc"/>
925+
<include location="../../../images/images.qrc"/>
926+
<include location="../../../images/images.qrc"/>
908927
</resources>
909928
<connections/>
910929
</ui>

tests/src/core/testqgslayouttable.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class TestQgsLayoutTable : public QObject
7070
void autoWrap(); //test auto word wrap
7171
void cellStyles(); //test cell styles
7272
void cellStylesRender(); //test rendering cell styles
73+
void dataDefinedSource();
7374

7475
private:
7576
QgsVectorLayer *mVectorLayer = nullptr;
@@ -1323,5 +1324,69 @@ void TestQgsLayoutTable::cellStylesRender()
13231324
QVERIFY( checker.testLayout( mReport, 0 ) );
13241325
}
13251326

1327+
void TestQgsLayoutTable::dataDefinedSource()
1328+
{
1329+
// add a couple of layers
1330+
QgsVectorLayer *layer1 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
1331+
QVERIFY( layer1->isValid() );
1332+
QgsFeature f( layer1->fields() );
1333+
f.setAttributes( QgsAttributes() << 1 << 2 << 3 );
1334+
layer1->dataProvider()->addFeature( f );
1335+
1336+
// different field order
1337+
QgsVectorLayer *layer2 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer&field=col2:integer" ), QStringLiteral( "l2" ), QStringLiteral( "memory" ) );
1338+
QVERIFY( layer2->isValid() );
1339+
QgsFeature f2( layer2->fields() );
1340+
f2.setAttributes( QgsAttributes() << 11 << 13 << 12 );
1341+
layer2->dataProvider()->addFeature( f2 );
1342+
1343+
// missing fields
1344+
QgsVectorLayer *layer3 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer" ), QStringLiteral( "l3" ), QStringLiteral( "memory" ) );
1345+
QVERIFY( layer3->isValid() );
1346+
QgsFeature f3( layer3->fields() );
1347+
f3.setAttributes( QgsAttributes() << 21 << 23 );
1348+
layer3->dataProvider()->addFeature( f3 );
1349+
1350+
QgsProject p;
1351+
p.addMapLayer( layer1 );
1352+
p.addMapLayer( layer2 );
1353+
p.addMapLayer( layer3 );
1354+
1355+
QgsLayout l( &p );
1356+
l.initializeDefaults();
1357+
QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l );
1358+
table->setSource( QgsLayoutItemAttributeTable::LayerAttributes );
1359+
table->setVectorLayer( layer1 );
1360+
table->setMaximumNumberOfFeatures( 50 );
1361+
QCOMPARE( table->contents().length(), 1 );
1362+
QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
1363+
1364+
// data defined table name, by layer id
1365+
table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, layer1->id() );
1366+
table->refresh();
1367+
QCOMPARE( table->contents().length(), 1 );
1368+
QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
1369+
1370+
// by layer name
1371+
table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "l2" ) );
1372+
table->refresh();
1373+
QCOMPARE( table->contents().length(), 1 );
1374+
QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 11 << 12 << 13 );
1375+
1376+
// by layer name (case insensitive)
1377+
table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "L3" ) );
1378+
table->refresh();
1379+
QCOMPARE( table->contents().length(), 1 );
1380+
QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 21 << QVariant() << 23 );
1381+
1382+
// delete current data defined layer match
1383+
p.removeMapLayer( layer3->id() );
1384+
QApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1385+
// expect table to return to preset layer
1386+
table->refreshAttributes();
1387+
QCOMPARE( table->contents().length(), 1 );
1388+
QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
1389+
}
1390+
13261391
QGSTEST_MAIN( TestQgsLayoutTable )
13271392
#include "testqgslayouttable.moc"

0 commit comments

Comments
 (0)