Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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
  • Loading branch information
nyalldawson committed Mar 10, 2018
1 parent 9732466 commit 0c1ceb3
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 28 deletions.
3 changes: 3 additions & 0 deletions python/core/layout/qgslayoutitemattributetable.sip.in
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions python/core/layout/qgslayoutobject.sip.in
Expand Up @@ -71,6 +71,8 @@ A base class for objects which belong to a layout.
ScalebarFillColor2,
ScalebarLineColor,
ScalebarLineWidth,
//table
AttributeTableSourceLayer,
};

enum PropertyValueType
Expand Down
7 changes: 7 additions & 0 deletions src/app/layout/qgslayoutattributetablewidget.cpp
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand All @@ -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 );
Expand Down
29 changes: 28 additions & 1 deletion src/core/layout/qgslayoutitemattributetable.cpp
Expand Up @@ -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)
Expand All @@ -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 );
Expand Down
6 changes: 6 additions & 0 deletions src/core/layout/qgslayoutitemattributetable.h
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/core/layout/qgslayoutobject.cpp
Expand Up @@ -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 ) },
};
}

Expand Down
4 changes: 3 additions & 1 deletion src/core/layout/qgslayoutobject.h
Expand Up @@ -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
};

/**
Expand Down
71 changes: 45 additions & 26 deletions src/ui/layout/qgslayoutattributetablewidgetbase.ui
Expand Up @@ -55,8 +55,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>689</width>
<height>2574</height>
<width>394</width>
<height>1487</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
Expand Down Expand Up @@ -85,50 +85,61 @@
<item row="0" column="1">
<widget class="QComboBox" name="mSourceComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mLayerLabel">
<property name="text">
<string>Layer</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsMapLayerComboBox" name="mLayerComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="5" column="0">
<widget class="QLabel" name="mRelationLabel">
<property name="text">
<string>Relation</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="mRelationsComboBox"/>
</item>
<item row="3" column="0" colspan="2">
<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="4" column="0" colspan="2">
<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">
<widget class="QLabel" name="mLayerLabel">
<property name="text">
<string>Layer</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QgsMapLayerComboBox" name="mLayerComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QgsPropertyOverrideButton" name="mLayerSourceDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
Expand Down Expand Up @@ -832,12 +843,18 @@
<extends>QComboBox</extends>
<header>qgslayoutitemcombobox.h</header>
</customwidget>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>scrollArea</tabstop>
<tabstop>groupBox</tabstop>
<tabstop>mSourceComboBox</tabstop>
<tabstop>mLayerComboBox</tabstop>
<tabstop>mLayerSourceDDBtn</tabstop>
<tabstop>mRelationsComboBox</tabstop>
<tabstop>mRefreshPushButton</tabstop>
<tabstop>mAttributesPushButton</tabstop>
Expand Down Expand Up @@ -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"/>
</resources>
<connections/>
</ui>
65 changes: 65 additions & 0 deletions tests/src/core/testqgslayouttable.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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"

0 comments on commit 0c1ceb3

Please sign in to comment.