[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
This commit is contained in:
Nyall Dawson 2018-03-05 16:35:44 +10:00
parent 97324661c7
commit 0c1ceb3900
9 changed files with 168 additions and 36 deletions

View File

@ -295,6 +295,9 @@ be replaced by a line break.
virtual void finalizeRestoreFromXml(); virtual void finalizeRestoreFromXml();
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
protected: protected:
virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const; virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const;

View File

@ -71,6 +71,8 @@ A base class for objects which belong to a layout.
ScalebarFillColor2, ScalebarFillColor2,
ScalebarLineColor, ScalebarLineColor,
ScalebarLineWidth, ScalebarLineWidth,
//table
AttributeTableSourceLayer,
}; };
enum PropertyValueType enum PropertyValueType

View File

@ -131,8 +131,12 @@ QgsLayoutAttributeTableWidget::QgsLayoutAttributeTableWidget( QgsLayoutFrame *fr
{ {
connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutAttributeTableWidget::atlasToggled ); connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutAttributeTableWidget::atlasToggled );
} }
mLayerSourceDDBtn->registerExpressionContextGenerator( mTable );
} }
registerDataDefinedButton( mLayerSourceDDBtn, QgsLayoutObject::AttributeTableSourceLayer );
//embed widget for general options //embed widget for general options
if ( mFrame ) if ( mFrame )
{ {
@ -925,6 +929,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
case QgsLayoutItemAttributeTable::LayerAttributes: case QgsLayoutItemAttributeTable::LayerAttributes:
mLayerComboBox->setEnabled( true ); mLayerComboBox->setEnabled( true );
mLayerComboBox->setVisible( true ); mLayerComboBox->setVisible( true );
mLayerSourceDDBtn->setVisible( true );
mLayerLabel->setVisible( true ); mLayerLabel->setVisible( true );
mRelationsComboBox->setEnabled( false ); mRelationsComboBox->setEnabled( false );
mRelationsComboBox->setVisible( false ); mRelationsComboBox->setVisible( false );
@ -938,6 +943,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
case QgsLayoutItemAttributeTable::AtlasFeature: case QgsLayoutItemAttributeTable::AtlasFeature:
mLayerComboBox->setEnabled( false ); mLayerComboBox->setEnabled( false );
mLayerComboBox->setVisible( false ); mLayerComboBox->setVisible( false );
mLayerSourceDDBtn->setVisible( false );
mLayerLabel->setVisible( false ); mLayerLabel->setVisible( false );
mRelationsComboBox->setEnabled( false ); mRelationsComboBox->setEnabled( false );
mRelationsComboBox->setVisible( false ); mRelationsComboBox->setVisible( false );
@ -952,6 +958,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
mLayerComboBox->setEnabled( false ); mLayerComboBox->setEnabled( false );
mLayerComboBox->setVisible( false ); mLayerComboBox->setVisible( false );
mLayerLabel->setVisible( false ); mLayerLabel->setVisible( false );
mLayerSourceDDBtn->setVisible( false );
mRelationsComboBox->setEnabled( true ); mRelationsComboBox->setEnabled( true );
mRelationsComboBox->setVisible( true ); mRelationsComboBox->setVisible( true );
mRelationLabel->setVisible( true ); mRelationLabel->setVisible( true );

View File

@ -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 QVariant QgsLayoutItemAttributeTable::replaceWrapChar( const QVariant &variant ) const
{ {
//avoid converting variants to string if not required (try to maintain original type for sorting) //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: case QgsLayoutItemAttributeTable::AtlasFeature:
return mLayout->reportContext().layer(); return mLayout->reportContext().layer();
case QgsLayoutItemAttributeTable::LayerAttributes: case QgsLayoutItemAttributeTable::LayerAttributes:
return mVectorLayer.get(); {
if ( mDataDefinedVectorLayer )
return mDataDefinedVectorLayer;
else
return mVectorLayer.get();
}
case QgsLayoutItemAttributeTable::RelationChildren: case QgsLayoutItemAttributeTable::RelationChildren:
{ {
QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId ); QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );

View File

@ -292,6 +292,8 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
QgsExpressionContext createExpressionContext() const override; QgsExpressionContext createExpressionContext() const override;
void finalizeRestoreFromXml() override; void finalizeRestoreFromXml() override;
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
protected: protected:
bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override; bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
@ -303,6 +305,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
ContentSource mSource = LayerAttributes; ContentSource mSource = LayerAttributes;
//! Associated vector layer //! Associated vector layer
QgsVectorLayerRef mVectorLayer; QgsVectorLayerRef mVectorLayer;
//! Data defined vector layer - only
QPointer< QgsVectorLayer > mDataDefinedVectorLayer;
//! Relation id, if in relation children mode //! Relation id, if in relation children mode
QString mRelationId; QString mRelationId;

View File

@ -76,6 +76,7 @@ void QgsLayoutObject::initPropertyDefinitions()
{ QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) }, { QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
{ QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) }, { QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) },
{ QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) }, { QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) },
{ QgsLayoutObject::AttributeTableSourceLayer, QgsPropertyDefinition( "dataDefinedAttributeTableSourceLayer", QObject::tr( "Table source layer" ), QgsPropertyDefinition::String ) },
}; };
} }

View File

@ -92,7 +92,9 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
ScalebarFillColor, //!< Scalebar fill color ScalebarFillColor, //!< Scalebar fill color
ScalebarFillColor2, //!< Scalebar secondary fill color ScalebarFillColor2, //!< Scalebar secondary fill color
ScalebarLineColor, //!< Scalebar line color ScalebarLineColor, //!< Scalebar line color
ScalebarLineWidth, //!< Scalebar line width ScalebarLineWidth, //!< Scalebar line width,
//table item
AttributeTableSourceLayer, //!< Attribute table source layer
}; };
/** /**

View File

@ -55,8 +55,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>689</width> <width>394</width>
<height>2574</height> <height>1487</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="mainLayout"> <layout class="QVBoxLayout" name="mainLayout">
@ -85,6 +85,30 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="mSourceComboBox"/> <widget class="QComboBox" name="mSourceComboBox"/>
</item> </item>
<item row="5" column="0">
<widget class="QLabel" name="mRelationLabel">
<property name="text">
<string>Relation</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="mRelationsComboBox"/>
</item>
<item row="6" column="0" colspan="2">
<widget class="QPushButton" name="mRefreshPushButton">
<property name="text">
<string>Refresh table data</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QPushButton" name="mAttributesPushButton">
<property name="text">
<string>Attributes...</string>
</property>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="mLayerLabel"> <widget class="QLabel" name="mLayerLabel">
<property name="text"> <property name="text">
@ -96,38 +120,25 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QgsMapLayerComboBox" name="mLayerComboBox"> <layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="sizePolicy"> <item>
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <widget class="QgsMapLayerComboBox" name="mLayerComboBox">
<horstretch>0</horstretch> <property name="sizePolicy">
<verstretch>0</verstretch> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
</sizepolicy> <horstretch>0</horstretch>
</property> <verstretch>0</verstretch>
</widget> </sizepolicy>
</item> </property>
<item row="2" column="0"> </widget>
<widget class="QLabel" name="mRelationLabel"> </item>
<property name="text"> <item>
<string>Relation</string> <widget class="QgsPropertyOverrideButton" name="mLayerSourceDDBtn">
</property> <property name="text">
</widget> <string>…</string>
</item> </property>
<item row="2" column="1"> </widget>
<widget class="QComboBox" name="mRelationsComboBox"/> </item>
</item> </layout>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="mRefreshPushButton">
<property name="text">
<string>Refresh table data</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="mAttributesPushButton">
<property name="text">
<string>Attributes...</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -832,12 +843,18 @@
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>qgslayoutitemcombobox.h</header> <header>qgslayoutitemcombobox.h</header>
</customwidget> </customwidget>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>scrollArea</tabstop> <tabstop>scrollArea</tabstop>
<tabstop>groupBox</tabstop> <tabstop>groupBox</tabstop>
<tabstop>mSourceComboBox</tabstop> <tabstop>mSourceComboBox</tabstop>
<tabstop>mLayerComboBox</tabstop> <tabstop>mLayerComboBox</tabstop>
<tabstop>mLayerSourceDDBtn</tabstop>
<tabstop>mRelationsComboBox</tabstop> <tabstop>mRelationsComboBox</tabstop>
<tabstop>mRefreshPushButton</tabstop> <tabstop>mRefreshPushButton</tabstop>
<tabstop>mAttributesPushButton</tabstop> <tabstop>mAttributesPushButton</tabstop>
@ -905,6 +922,8 @@
<include location="../../../images/images.qrc"/> <include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/> <include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/> <include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -70,6 +70,7 @@ class TestQgsLayoutTable : public QObject
void autoWrap(); //test auto word wrap void autoWrap(); //test auto word wrap
void cellStyles(); //test cell styles void cellStyles(); //test cell styles
void cellStylesRender(); //test rendering cell styles void cellStylesRender(); //test rendering cell styles
void dataDefinedSource();
private: private:
QgsVectorLayer *mVectorLayer = nullptr; QgsVectorLayer *mVectorLayer = nullptr;
@ -1323,5 +1324,69 @@ void TestQgsLayoutTable::cellStylesRender()
QVERIFY( checker.testLayout( mReport, 0 ) ); 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 ) QGSTEST_MAIN( TestQgsLayoutTable )
#include "testqgslayouttable.moc" #include "testqgslayouttable.moc"