Skip to content

Commit c3ec4b9

Browse files
committedSep 22, 2014
[FEATURE][composer] Add option for showing child features from a relation
for attribute tables in the composer. If selected, the attribute table will show all related features to the current atlas feature within the table body. Sponsored by City of Uster, Switzerland.
1 parent f58768c commit c3ec4b9

15 files changed

+374
-133
lines changed
 

‎python/core/composer/qgscomposerattributetablev2.sip

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ class QgsComposerAttributeTableV2 : QgsComposerTableV2
8484
* @see setVectorLayer
8585
*/
8686
QgsVectorLayer* vectorLayer() const;
87+
88+
/**Sets the relation id from which to display child features
89+
* @param relationId id for relation to display child features from
90+
* @see relationId
91+
* @see setSource
92+
* @note only used if table source is set to RelationChildren
93+
*/
94+
void setRelationId( const QString relationId );
95+
96+
/**Returns the relation id which the table displays child features from
97+
* @returns relation id
98+
* @see setRelationId
99+
* @see source
100+
* @note only used if table source is set to RelationChildren
101+
*/
102+
QString relationId() const;
87103

88104
/**Resets the attribute table's columns to match the vector layer's fields
89105
* @see setVectorLayer

‎python/core/qgsrelation.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class QgsRelation
2222
/**
2323
* Defines a relation between matchin fields of the two involved tables of a relation.
2424
* Often, a relation is only defined by just one FieldPair with the name of the foreign key
25-
* column of the referencing table as first element and the name of the primkary key column
25+
* column of the referencing table as first element and the name of the primary key column
2626
* of the referenced table as the second element.
2727
*
2828
*/

‎src/app/composer/qgscomposerattributetablewidget.cpp

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include "qgsmaplayerregistry.h"
2727
#include "qgsvectorlayer.h"
2828
#include "qgsexpressionbuilderdialog.h"
29+
#include "qgsproject.h"
30+
#include "qgsrelationmanager.h"
2931
#include <QColorDialog>
3032
#include <QFontDialog>
3133

@@ -47,10 +49,10 @@ QgsComposerAttributeTableWidget::QgsComposerAttributeTableWidget( QgsComposerAtt
4749

4850
bool atlasEnabled = atlasComposition() && atlasComposition()->enabled();
4951
mSourceComboBox->addItem( tr( "Layer features" ), QgsComposerAttributeTableV2::LayerAttributes );
50-
if ( atlasEnabled )
51-
{
52-
mSourceComboBox->addItem( tr( "Current atlas feature" ), QgsComposerAttributeTableV2::AtlasFeature );
53-
}
52+
toggleAtlasSpecificControls( atlasEnabled );
53+
54+
//update relations combo when relations modified in project
55+
connect( QgsProject::instance()->relationManager(), SIGNAL( changed() ), this, SLOT( updateRelationsCombo() ) );
5456

5557
mLayerComboBox->setFilters( QgsMapLayerProxyModel::VectorLayer );
5658
connect( mLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( changeLayer( QgsMapLayer* ) ) );
@@ -77,9 +79,9 @@ QgsComposerAttributeTableWidget::QgsComposerAttributeTableWidget( QgsComposerAtt
7779
QgsAtlasComposition* atlas = atlasComposition();
7880
if ( atlas )
7981
{
80-
// repopulate table source combo box if atlas properties change
81-
// connect( atlas, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ),
82-
// this, SLOT( populateDataDefinedButtons() ) );
82+
// repopulate relations combo box if atlas layer changes
83+
connect( atlas, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ),
84+
this, SLOT( updateRelationsCombo() ) );
8385
connect( atlas, SIGNAL( toggled( bool ) ), this, SLOT( atlasToggled() ) );
8486
}
8587
}
@@ -162,7 +164,7 @@ void QgsComposerAttributeTableWidget::on_mAttributesPushButton_clicked()
162164
composition->beginMultiFrameCommand( mComposerTable, tr( "Table attribute settings" ) );
163165
}
164166

165-
QgsAttributeSelectionDialog d( mComposerTable, mComposerTable->vectorLayer(), 0 );
167+
QgsAttributeSelectionDialog d( mComposerTable, mComposerTable->sourceLayer(), 0 );
166168
if ( d.exec() == QDialog::Accepted )
167169
{
168170
mComposerTable->refreshAttributes();
@@ -427,6 +429,7 @@ void QgsComposerAttributeTableWidget::updateGuiElements()
427429
blockAllSignals( true );
428430

429431
mSourceComboBox->setCurrentIndex( mSourceComboBox->findData( mComposerTable->source() ) );
432+
mRelationsComboBox->setCurrentIndex( mRelationsComboBox->findData( mComposerTable->relationId() ) );
430433

431434
//layer combo box
432435
if ( mComposerTable->vectorLayer() )
@@ -503,13 +506,46 @@ void QgsComposerAttributeTableWidget::atlasToggled()
503506
{
504507
//display/hide atlas options in source combobox depending on atlas status
505508
bool atlasEnabled = atlasComposition() && atlasComposition()->enabled();
509+
toggleAtlasSpecificControls( atlasEnabled );
510+
511+
mSourceComboBox->blockSignals( true );
512+
mSourceComboBox->setCurrentIndex( mSourceComboBox->findData( mComposerTable->source() ) );
513+
mSourceComboBox->blockSignals( false );
514+
}
515+
516+
void QgsComposerAttributeTableWidget::updateRelationsCombo()
517+
{
518+
mRelationsComboBox->blockSignals( true );
519+
mRelationsComboBox->clear();
520+
521+
QgsVectorLayer* atlasLayer = atlasCoverageLayer();
522+
if ( atlasLayer )
523+
{
524+
QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mComposerTable->composition()->atlasComposition().coverageLayer() );
525+
Q_FOREACH( const QgsRelation& relation, relations )
526+
{
527+
mRelationsComboBox->addItem( relation.name(), relation.id() );
528+
}
529+
}
530+
531+
mRelationsComboBox->setCurrentIndex( mRelationsComboBox->findData( mComposerTable->relationId() ) );
532+
mRelationsComboBox->blockSignals( false );
533+
}
534+
535+
void QgsComposerAttributeTableWidget::toggleAtlasSpecificControls( const bool atlasEnabled )
536+
{
506537
if ( !atlasEnabled )
507538
{
508539
if ( mComposerTable->source() == QgsComposerAttributeTableV2::AtlasFeature )
509540
{
510541
mComposerTable->setSource( QgsComposerAttributeTableV2::LayerAttributes );
511542
}
512543
mSourceComboBox->removeItem( mSourceComboBox->findData( QgsComposerAttributeTableV2::AtlasFeature ) );
544+
mSourceComboBox->removeItem( mSourceComboBox->findData( QgsComposerAttributeTableV2::RelationChildren ) );
545+
mRelationsComboBox->blockSignals( true );
546+
mRelationsComboBox->setEnabled( false );
547+
mRelationsComboBox->clear();
548+
mRelationsComboBox->blockSignals( false );
513549
}
514550
else
515551
{
@@ -518,10 +554,16 @@ void QgsComposerAttributeTableWidget::atlasToggled()
518554
//add missing atlasfeature option to combobox
519555
mSourceComboBox->addItem( tr( "Current atlas feature" ), QgsComposerAttributeTableV2::AtlasFeature );
520556
}
557+
if ( mSourceComboBox->findData( QgsComposerAttributeTableV2::RelationChildren ) == -1 )
558+
{
559+
//add missing relation children option to combobox
560+
mSourceComboBox->addItem( tr( "Relation children" ), QgsComposerAttributeTableV2::RelationChildren );
561+
}
562+
563+
//add relations for coverage layer
564+
updateRelationsCombo();
565+
mRelationsComboBox->setEnabled( true );
521566
}
522-
mSourceComboBox->blockSignals( true );
523-
mSourceComboBox->setCurrentIndex( mSourceComboBox->findData( mComposerTable->source() ) );
524-
mSourceComboBox->blockSignals( false );
525567
}
526568

527569
void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
@@ -542,6 +584,7 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
542584
mHeaderFontColorButton->blockSignals( b );
543585
mContentFontColorButton->blockSignals( b );
544586
mResizeModeComboBox->blockSignals( b );
587+
mRelationsComboBox->blockSignals( b );
545588
}
546589

547590
void QgsComposerAttributeTableWidget::setMaximumNumberOfFeatures( int n )
@@ -634,7 +677,7 @@ void QgsComposerAttributeTableWidget::on_mFeatureFilterButton_clicked()
634677
return;
635678
}
636679

637-
QgsExpressionBuilderDialog exprDlg( mComposerTable->vectorLayer(), mFeatureFilterEdit->text(), this );
680+
QgsExpressionBuilderDialog exprDlg( mComposerTable->sourceLayer(), mFeatureFilterEdit->text(), this );
638681
exprDlg.setWindowTitle( tr( "Expression based filter" ) );
639682
if ( exprDlg.exec() == QDialog::Accepted )
640683
{
@@ -791,12 +834,33 @@ void QgsComposerAttributeTableWidget::on_mSourceComboBox_currentIndexChanged( in
791834
toggleSourceControls();
792835
}
793836

837+
void QgsComposerAttributeTableWidget::on_mRelationsComboBox_currentIndexChanged( int index )
838+
{
839+
if ( !mComposerTable )
840+
{
841+
return;
842+
}
843+
844+
QgsComposition* composition = mComposerTable->composition();
845+
if ( composition )
846+
{
847+
composition->beginMultiFrameCommand( mComposerTable, tr( "Change table source relation" ) );
848+
mComposerTable->setRelationId( mRelationsComboBox->itemData( index ).toString() );
849+
composition->endMultiFrameCommand();
850+
}
851+
}
852+
794853
void QgsComposerAttributeTableWidget::toggleSourceControls()
795854
{
796855
switch ( mComposerTable->source() )
797856
{
798857
case QgsComposerAttributeTableV2::LayerAttributes:
799858
mLayerComboBox->setEnabled( true );
859+
mLayerComboBox->setVisible( true );
860+
mLayerLabel->setVisible( true );
861+
mRelationsComboBox->setEnabled( false );
862+
mRelationsComboBox->setVisible( false );
863+
mRelationLabel->setVisible( false );
800864
mMaximumRowsSpinBox->setEnabled( true );
801865
mMaxNumFeaturesLabel->setEnabled( true );
802866
mShowOnlyVisibleFeaturesCheckBox->setEnabled( true );
@@ -805,13 +869,29 @@ void QgsComposerAttributeTableWidget::toggleSourceControls()
805869
break;
806870
case QgsComposerAttributeTableV2::AtlasFeature:
807871
mLayerComboBox->setEnabled( false );
872+
mLayerComboBox->setVisible( false );
873+
mLayerLabel->setVisible( false );
874+
mRelationsComboBox->setEnabled( false );
875+
mRelationsComboBox->setVisible( false );
876+
mRelationLabel->setVisible( false );
808877
mMaximumRowsSpinBox->setEnabled( false );
809878
mMaxNumFeaturesLabel->setEnabled( false );
810879
mShowOnlyVisibleFeaturesCheckBox->setEnabled( false );
811880
mComposerMapComboBox->setEnabled( false );
812881
mComposerMapLabel->setEnabled( false );
813882
break;
814-
default:
883+
case QgsComposerAttributeTableV2::RelationChildren:
884+
mLayerComboBox->setEnabled( false );
885+
mLayerComboBox->setVisible( false );
886+
mLayerLabel->setVisible( false );
887+
mRelationsComboBox->setEnabled( true );
888+
mRelationsComboBox->setVisible( true );
889+
mRelationLabel->setVisible( true );
890+
mMaximumRowsSpinBox->setEnabled( true );
891+
mMaxNumFeaturesLabel->setEnabled( true );
892+
mShowOnlyVisibleFeaturesCheckBox->setEnabled( true );
893+
mComposerMapComboBox->setEnabled( true );
894+
mComposerMapLabel->setEnabled( true );
815895
break;
816896
}
817897
}

‎src/app/composer/qgscomposerattributetablewidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
4444

4545
void toggleSourceControls();
4646

47+
void toggleAtlasSpecificControls( const bool atlasEnabled );
48+
4749
private slots:
4850
void on_mRefreshPushButton_clicked();
4951
void on_mAttributesPushButton_clicked();
@@ -67,6 +69,7 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
6769
void on_mAddFramePushButton_clicked();
6870
void on_mResizeModeComboBox_currentIndexChanged( int index );
6971
void on_mSourceComboBox_currentIndexChanged( int index );
72+
void on_mRelationsComboBox_currentIndexChanged( int index );
7073

7174
/**Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)*/
7275
void setMaximumNumberOfFeatures( int n );
@@ -76,6 +79,7 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
7679

7780
void atlasToggled();
7881

82+
void updateRelationsCombo();
7983
};
8084

8185
#endif // QGSCOMPOSERATTRIBUTETABLEWIDGET_H

‎src/core/composer/qgscomposerattributetablev2.cpp

Lines changed: 124 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "qgsvectorlayer.h"
2424
#include "qgscomposerframe.h"
2525
#include "qgsatlascomposition.h"
26+
#include "qgsproject.h"
27+
#include "qgsrelationmanager.h"
2628

2729
//QgsComposerAttributeTableCompareV2
2830

@@ -120,6 +122,8 @@ QgsComposerAttributeTableV2::QgsComposerAttributeTableV2( QgsComposition* compos
120122
if ( mVectorLayer )
121123
{
122124
resetColumns();
125+
//listen for modifications to layer and refresh table when they occur
126+
connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
123127
}
124128
connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( removeLayer( const QString& ) ) );
125129

@@ -149,22 +153,83 @@ void QgsComposerAttributeTableV2::setVectorLayer( QgsVectorLayer* layer )
149153
return;
150154
}
151155

152-
if ( mVectorLayer )
156+
QgsVectorLayer* prevLayer = sourceLayer();
157+
mVectorLayer = layer;
158+
159+
if ( mSource == QgsComposerAttributeTableV2::LayerAttributes && layer != prevLayer )
160+
{
161+
if ( prevLayer )
162+
{
163+
//disconnect from previous layer
164+
disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
165+
}
166+
167+
//rebuild column list to match all columns from layer
168+
resetColumns();
169+
170+
//listen for modifications to layer and refresh table when they occur
171+
connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
172+
}
173+
174+
refreshAttributes();
175+
emit changed();
176+
}
177+
178+
void QgsComposerAttributeTableV2::setRelationId( const QString relationId )
179+
{
180+
if ( relationId == mRelationId )
181+
{
182+
//no change
183+
return;
184+
}
185+
186+
QgsVectorLayer* prevLayer = sourceLayer();
187+
mRelationId = relationId;
188+
QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
189+
QgsVectorLayer* newLayer = relation.referencingLayer();
190+
191+
if ( mSource == QgsComposerAttributeTableV2::RelationChildren && newLayer != prevLayer )
192+
{
193+
if ( prevLayer )
194+
{
195+
//disconnect from previous layer
196+
disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
197+
}
198+
199+
//rebuild column list to match all columns from layer
200+
resetColumns();
201+
202+
//listen for modifications to layer and refresh table when they occur
203+
connect( newLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
204+
}
205+
206+
refreshAttributes();
207+
emit changed();
208+
}
209+
210+
void QgsComposerAttributeTableV2::atlasLayerChanged( QgsVectorLayer *layer )
211+
{
212+
if ( mSource != QgsComposerAttributeTableV2::AtlasFeature || layer == mCurrentAtlasLayer )
213+
{
214+
//nothing to do
215+
return;
216+
}
217+
218+
//atlas feature mode, atlas layer changed, so we need to reset columns
219+
if ( mCurrentAtlasLayer )
153220
{
154221
//disconnect from previous layer
155-
QObject::disconnect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
222+
disconnect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
156223
}
157224

158-
mVectorLayer = layer;
225+
mCurrentAtlasLayer = layer;
159226

160227
//rebuild column list to match all columns from layer
161228
resetColumns();
162229
refreshAttributes();
163230

164231
//listen for modifications to layer and refresh table when they occur
165-
QObject::connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
166-
167-
emit changed();
232+
connect( layer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
168233
}
169234

170235
void QgsComposerAttributeTableV2::resetColumns()
@@ -202,13 +267,13 @@ void QgsComposerAttributeTableV2::setComposerMap( const QgsComposerMap* map )
202267
if ( mComposerMap )
203268
{
204269
//disconnect from previous map
205-
QObject::disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
270+
disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
206271
}
207272
mComposerMap = map;
208273
if ( mComposerMap )
209274
{
210275
//listen out for extent changes in linked map
211-
QObject::connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
276+
connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
212277
}
213278
refreshAttributes();
214279
emit changed();
@@ -339,10 +404,10 @@ bool QgsComposerAttributeTableV2::getTableContents( QgsComposerTableContents &co
339404
{
340405
contents.clear();
341406

342-
if ( mSource == QgsComposerAttributeTableV2::AtlasFeature &&
343-
!mComposition->atlasComposition().enabled() )
407+
if (( mSource == QgsComposerAttributeTableV2::AtlasFeature || mSource == QgsComposerAttributeTableV2::RelationChildren )
408+
&& !mComposition->atlasComposition().enabled() )
344409
{
345-
//atlas source mode, but atlas disabled
410+
//source mode requires atlas, but atlas disabled
346411
return false;
347412
}
348413

@@ -387,6 +452,22 @@ bool QgsComposerAttributeTableV2::getTableContents( QgsComposerTableContents &co
387452
}
388453

389454
QgsFeatureRequest req;
455+
456+
if ( mSource == QgsComposerAttributeTableV2::RelationChildren )
457+
{
458+
QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
459+
QgsFeature* atlasFeature = mComposition->atlasComposition().currentFeature();
460+
if ( atlasFeature )
461+
{
462+
req = relation.getRelatedFeaturesRequest( *atlasFeature );
463+
}
464+
else
465+
{
466+
//no atlas feature, so empty table
467+
return true;
468+
}
469+
}
470+
390471
if ( !selectionRect.isEmpty() )
391472
req.setFilterRect( selectionRect );
392473

@@ -471,14 +552,18 @@ QgsVectorLayer *QgsComposerAttributeTableV2::sourceLayer()
471552
return mComposition->atlasComposition().coverageLayer();
472553
case QgsComposerAttributeTableV2::LayerAttributes:
473554
return mVectorLayer;
474-
default:
475-
return 0;
555+
case QgsComposerAttributeTableV2::RelationChildren:
556+
{
557+
QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
558+
return relation.referencingLayer();
559+
}
476560
}
561+
return 0;
477562
}
478563

479564
void QgsComposerAttributeTableV2::removeLayer( QString layerId )
480565
{
481-
if ( mVectorLayer )
566+
if ( mVectorLayer && mSource == QgsComposerAttributeTableV2::LayerAttributes )
482567
{
483568
if ( layerId == mVectorLayer->id() )
484569
{
@@ -490,31 +575,6 @@ void QgsComposerAttributeTableV2::removeLayer( QString layerId )
490575
}
491576
}
492577

493-
void QgsComposerAttributeTableV2::atlasLayerChanged( QgsVectorLayer *layer )
494-
{
495-
if ( mSource != QgsComposerAttributeTableV2::AtlasFeature )
496-
{
497-
//nothing to do
498-
return;
499-
}
500-
501-
//atlas feature mode, atlas layer changed, so we need to reset columns
502-
if ( mCurrentAtlasLayer )
503-
{
504-
//disconnect from previous layer
505-
QObject::disconnect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
506-
}
507-
508-
mCurrentAtlasLayer = layer;
509-
510-
//rebuild column list to match all columns from layer
511-
resetColumns();
512-
refreshAttributes();
513-
514-
//listen for modifications to layer and refresh table when they occur
515-
QObject::connect( layer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
516-
}
517-
518578
static bool columnsBySortRank( QPair<int, QgsComposerTableColumn* > a, QPair<int, QgsComposerTableColumn* > b )
519579
{
520580
return a.second->sortByRank() < b.second->sortByRank();
@@ -554,6 +614,7 @@ bool QgsComposerAttributeTableV2::writeXML( QDomElement& elem, QDomDocument & do
554614
{
555615
QDomElement composerTableElem = doc.createElement( "ComposerAttributeTableV2" );
556616
composerTableElem.setAttribute( "source", QString::number(( int )mSource ) );
617+
composerTableElem.setAttribute( "relationId", mRelationId );
557618
composerTableElem.setAttribute( "showOnlyVisibleFeatures", mShowOnlyVisibleFeatures );
558619
composerTableElem.setAttribute( "maxFeatures", mMaximumNumberOfFeatures );
559620
composerTableElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
@@ -592,17 +653,19 @@ bool QgsComposerAttributeTableV2::readXML( const QDomElement& itemElem, const QD
592653
return false;
593654
}
594655

656+
QgsVectorLayer* prevLayer = sourceLayer();
657+
if ( prevLayer )
658+
{
659+
//disconnect from previous layer
660+
disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
661+
}
662+
595663
mSource = QgsComposerAttributeTableV2::ContentSource( itemElem.attribute( "source", "0" ).toInt() );
664+
mRelationId = itemElem.attribute( "relationId", "" );
665+
596666
if ( mSource == QgsComposerAttributeTableV2::AtlasFeature )
597667
{
598-
if ( mCurrentAtlasLayer )
599-
{
600-
//disconnect from previous layer
601-
QObject::disconnect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
602-
}
603668
mCurrentAtlasLayer = mComposition->atlasComposition().coverageLayer();
604-
//listen for modifications to layer and refresh table when they occur
605-
QObject::connect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
606669
}
607670

608671
mShowOnlyVisibleFeatures = itemElem.attribute( "showOnlyVisibleFeatures", "1" ).toInt();
@@ -629,7 +692,7 @@ bool QgsComposerAttributeTableV2::readXML( const QDomElement& itemElem, const QD
629692
if ( mComposerMap )
630693
{
631694
//if we have found a valid map item, listen out to extent changes on it and refresh the table
632-
QObject::connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
695+
connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
633696
}
634697

635698
//vector layer
@@ -644,14 +707,12 @@ bool QgsComposerAttributeTableV2::readXML( const QDomElement& itemElem, const QD
644707
if ( ml )
645708
{
646709
mVectorLayer = dynamic_cast<QgsVectorLayer*>( ml );
647-
if ( mVectorLayer && mSource == QgsComposerAttributeTableV2::LayerAttributes )
648-
{
649-
//if we have found a valid vector layer, listen for modifications on it and refresh the table
650-
QObject::connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
651-
}
652710
}
653711
}
654712

713+
//connect to new layer
714+
connect( sourceLayer(), SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
715+
655716
refreshAttributes();
656717

657718
emit changed();
@@ -681,64 +742,25 @@ void QgsComposerAttributeTableV2::setSource( const QgsComposerAttributeTableV2::
681742
return;
682743
}
683744

684-
QgsVectorLayer* prevLayer = 0;
685-
686-
//disconnect from previous layer
687-
switch ( mSource )
688-
{
689-
case QgsComposerAttributeTableV2::AtlasFeature:
690-
{
691-
prevLayer = mComposition->atlasComposition().coverageLayer();
692-
if ( mCurrentAtlasLayer )
693-
{
694-
QObject::disconnect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
695-
}
696-
break;
697-
}
698-
case QgsComposerAttributeTableV2::LayerAttributes:
699-
{
700-
if ( mVectorLayer )
701-
{
702-
prevLayer = mVectorLayer;
703-
QObject::disconnect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
704-
}
705-
break;
706-
}
707-
default:
708-
break;
709-
}
710-
745+
QgsVectorLayer* prevLayer = sourceLayer();
711746
mSource = source;
747+
QgsVectorLayer* newLayer = sourceLayer();
712748

713-
//connect to new layer
714-
QgsVectorLayer* newLayer = 0;
715-
switch ( mSource )
749+
if ( newLayer != prevLayer )
716750
{
717-
case QgsComposerAttributeTableV2::AtlasFeature:
751+
//disconnect from previous layer
752+
if ( prevLayer )
718753
{
719-
mCurrentAtlasLayer = mComposition->atlasComposition().coverageLayer();
720-
newLayer = mCurrentAtlasLayer;
721-
if ( mCurrentAtlasLayer )
722-
{
723-
QObject::connect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
724-
}
725-
break;
754+
disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
726755
}
727-
case QgsComposerAttributeTableV2::LayerAttributes:
756+
757+
//connect to new layer
758+
connect( newLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
759+
if ( mSource == QgsComposerAttributeTableV2::AtlasFeature )
728760
{
729-
if ( mVectorLayer )
730-
{
731-
newLayer = mVectorLayer;
732-
QObject::connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
733-
}
734-
break;
761+
mCurrentAtlasLayer = newLayer;
735762
}
736-
default:
737-
break;
738-
}
739763

740-
if ( newLayer != prevLayer )
741-
{
742764
//layer has changed as a result of the source change, so reset column list
743765
resetColumns();
744766
}

‎src/core/composer/qgscomposerattributetablev2.h

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
9696
*/
9797
ContentSource source() const { return mSource; }
9898

99+
/**Returns the source layer for the table, considering the table source mode. Eg,
100+
* if the table is set to atlas feature mode, then the source layer will be the
101+
* atlas coverage layer. If the table is set to layer attributes mode, then
102+
* the source layer will be the user specified vector layer.
103+
* @returns actual source layer
104+
*/
105+
QgsVectorLayer* sourceLayer();
106+
99107
/**Sets the vector layer from which to display feature attributes
100108
* @param layer Vector layer for attribute table
101109
* @see vectorLayer
@@ -108,6 +116,22 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
108116
*/
109117
QgsVectorLayer* vectorLayer() const { return mVectorLayer; }
110118

119+
/**Sets the relation id from which to display child features
120+
* @param relationId id for relation to display child features from
121+
* @see relationId
122+
* @see setSource
123+
* @note only used if table source is set to RelationChildren
124+
*/
125+
void setRelationId( const QString relationId );
126+
127+
/**Returns the relation id which the table displays child features from
128+
* @returns relation id
129+
* @see setRelationId
130+
* @see source
131+
* @note only used if table source is set to RelationChildren
132+
*/
133+
QString relationId() const { return mRelationId; }
134+
111135
/**Resets the attribute table's columns to match the vector layer's fields
112136
* @see setVectorLayer
113137
*/
@@ -228,6 +252,8 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
228252
ContentSource mSource;
229253
/**Associated vector layer*/
230254
QgsVectorLayer* mVectorLayer;
255+
/**Relation id, if in relation children mode*/
256+
QString mRelationId;
231257

232258
/**Current vector layer, if in atlas feature mode*/
233259
QgsVectorLayer* mCurrentAtlasLayer;
@@ -245,14 +271,6 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
245271
/**Feature filter expression*/
246272
QString mFeatureFilter;
247273

248-
/**Returns the source layer for the table, considering the table source mode. Eg,
249-
* if the table is set to atlas feature mode, then the source layer will be the
250-
* atlas coverage layer. If the table is set to layer attributes mode, then
251-
* the source layer will be the user specified vector layer.
252-
* @returns actual source layer
253-
*/
254-
QgsVectorLayer* sourceLayer();
255-
256274
/**Returns a list of attribute indices corresponding to displayed fields in the table.
257275
* @note kept for compatibility with 2.0 api only
258276
*/

‎src/core/qgsrelation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ void QgsRelation::updateRelationStatus()
241241
mValid = false;
242242
}
243243

244-
Q_FOREACH ( const FieldPair& fieldPair, mFieldPairs )
244+
Q_FOREACH( const FieldPair& fieldPair, mFieldPairs )
245245
{
246246
if ( -1 == mReferencingLayer->fieldNameIndex( fieldPair.first )
247247
|| -1 == mReferencedLayer->fieldNameIndex( fieldPair.second ) )

‎src/core/qgsrelation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class CORE_EXPORT QgsRelation
3131
/**
3232
* Defines a relation between matchin fields of the two involved tables of a relation.
3333
* Often, a relation is only defined by just one FieldPair with the name of the foreign key
34-
* column of the referencing table as first element and the name of the primkary key column
34+
* column of the referencing table as first element and the name of the primary key column
3535
* of the referenced table as the second element.
3636
*
3737
*/

‎src/ui/qgscomposerattributetablewidgetbase.ui

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<x>0</x>
4848
<y>0</y>
4949
<width>392</width>
50-
<height>879</height>
50+
<height>918</height>
5151
</rect>
5252
</property>
5353
<layout class="QVBoxLayout" name="mainLayout">
@@ -99,21 +99,21 @@
9999
</property>
100100
</widget>
101101
</item>
102-
<item row="2" column="0" colspan="2">
102+
<item row="4" column="0" colspan="2">
103103
<widget class="QPushButton" name="mRefreshPushButton">
104104
<property name="text">
105105
<string>Refresh table data</string>
106106
</property>
107107
</widget>
108108
</item>
109-
<item row="3" column="0" colspan="2">
109+
<item row="5" column="0" colspan="2">
110110
<widget class="QPushButton" name="mAttributesPushButton">
111111
<property name="text">
112112
<string>Attributes...</string>
113113
</property>
114114
</widget>
115115
</item>
116-
<item row="4" column="0">
116+
<item row="6" column="0">
117117
<widget class="QLabel" name="mMarginLabel">
118118
<property name="text">
119119
<string>Margin</string>
@@ -126,13 +126,23 @@
126126
</property>
127127
</widget>
128128
</item>
129-
<item row="4" column="1">
129+
<item row="6" column="1">
130130
<widget class="QDoubleSpinBox" name="mMarginSpinBox">
131131
<property name="suffix">
132132
<string> mm</string>
133133
</property>
134134
</widget>
135135
</item>
136+
<item row="2" column="1">
137+
<widget class="QComboBox" name="mRelationsComboBox"/>
138+
</item>
139+
<item row="2" column="0">
140+
<widget class="QLabel" name="mRelationLabel">
141+
<property name="text">
142+
<string>Relation</string>
143+
</property>
144+
</widget>
145+
</item>
136146
</layout>
137147
</widget>
138148
</item>

‎tests/src/core/testqgscomposertablev2.cpp

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "qgscompositionchecker.h"
2929
#include "qgsfontutils.h"
3030
#include "qgsmaplayerregistry.h"
31+
#include "qgsproject.h"
32+
#include "qgsrelationmanager.h"
3133

3234
#include <QObject>
3335
#include <QtTest>
@@ -54,8 +56,7 @@ class TestQgsComposerTableV2: public QObject
5456
void attributeTableRepeat();
5557

5658
void attributeTableAtlasSource(); //test attribute table in atlas feature mode
57-
58-
59+
void attributeTableRelationSource(); //test attribute table in relation mode
5960

6061
private:
6162
QgsComposition* mComposition;
@@ -82,6 +83,7 @@ void TestQgsComposerTableV2::initTestCase()
8283
mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(),
8384
vectorFileInfo.completeBaseName(),
8485
"ogr" );
86+
QgsMapLayerRegistry::instance()->addMapLayer( mVectorLayer );
8587
mVectorLayer2 = new QgsVectorLayer( vectorFileInfo.filePath(),
8688
vectorFileInfo.completeBaseName(),
8789
"ogr" );
@@ -421,6 +423,93 @@ void TestQgsComposerTableV2::attributeTableAtlasSource()
421423
delete table;
422424
}
423425

426+
427+
void TestQgsComposerTableV2::attributeTableRelationSource()
428+
{
429+
QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + QDir::separator() + "points_relations.shp" );
430+
QgsVectorLayer* atlasLayer = new QgsVectorLayer( vectorFileInfo.filePath(),
431+
vectorFileInfo.completeBaseName(),
432+
"ogr" );
433+
434+
QgsMapLayerRegistry::instance()->addMapLayer( atlasLayer );
435+
436+
//setup atlas
437+
mComposition->atlasComposition().setCoverageLayer( atlasLayer );
438+
mComposition->atlasComposition().setEnabled( true );
439+
440+
//create a relation
441+
QgsRelation relation;
442+
relation.setRelationId( "testrelation" );
443+
relation.setReferencedLayer( atlasLayer->id() );
444+
relation.setReferencingLayer( mVectorLayer->id() );
445+
relation.addFieldPair( "Class", "Class" );
446+
QgsProject::instance()->relationManager()->addRelation( relation );
447+
448+
QgsComposerAttributeTableV2* table = new QgsComposerAttributeTableV2( mComposition, false );
449+
table->setMaximumNumberOfFeatures( 50 );
450+
table->setSource( QgsComposerAttributeTableV2::RelationChildren );
451+
table->setRelationId( relation.id() );
452+
453+
QVERIFY( mComposition->atlasComposition().beginRender() );
454+
QVERIFY( mComposition->atlasComposition().prepareForFeature( 0 ) );
455+
456+
QCOMPARE( mComposition->atlasComposition().currentFeature()->attribute( "Class" ).toString(), QString( "Jet" ) );
457+
QCOMPARE( table->contents()->length(), 8 );
458+
459+
QgsComposerTableRow row = table->contents()->at( 0 );
460+
461+
//check a couple of results
462+
QCOMPARE( row.at( 0 ), QVariant( "Jet" ) );
463+
QCOMPARE( row.at( 1 ), QVariant( 90 ) );
464+
QCOMPARE( row.at( 2 ), QVariant( 3 ) );
465+
QCOMPARE( row.at( 3 ), QVariant( 2 ) );
466+
QCOMPARE( row.at( 4 ), QVariant( 0 ) );
467+
QCOMPARE( row.at( 5 ), QVariant( 2 ) );
468+
row = table->contents()->at( 1 );
469+
QCOMPARE( row.at( 0 ), QVariant( "Jet" ) );
470+
QCOMPARE( row.at( 1 ), QVariant( 85 ) );
471+
QCOMPARE( row.at( 2 ), QVariant( 3 ) );
472+
QCOMPARE( row.at( 3 ), QVariant( 1 ) );
473+
QCOMPARE( row.at( 4 ), QVariant( 1 ) );
474+
QCOMPARE( row.at( 5 ), QVariant( 2 ) );
475+
row = table->contents()->at( 2 );
476+
QCOMPARE( row.at( 0 ), QVariant( "Jet" ) );
477+
QCOMPARE( row.at( 1 ), QVariant( 95 ) );
478+
QCOMPARE( row.at( 2 ), QVariant( 3 ) );
479+
QCOMPARE( row.at( 3 ), QVariant( 1 ) );
480+
QCOMPARE( row.at( 4 ), QVariant( 1 ) );
481+
QCOMPARE( row.at( 5 ), QVariant( 2 ) );
482+
483+
//next atlas feature
484+
QVERIFY( mComposition->atlasComposition().prepareForFeature( 1 ) );
485+
QCOMPARE( mComposition->atlasComposition().currentFeature()->attribute( "Class" ).toString(), QString( "Biplane" ) );
486+
QCOMPARE( table->contents()->length(), 5 );
487+
row = table->contents()->at( 0 );
488+
QCOMPARE( row.at( 0 ), QVariant( "Biplane" ) );
489+
QCOMPARE( row.at( 1 ), QVariant( 0 ) );
490+
QCOMPARE( row.at( 2 ), QVariant( 1 ) );
491+
QCOMPARE( row.at( 3 ), QVariant( 3 ) );
492+
QCOMPARE( row.at( 4 ), QVariant( 3 ) );
493+
QCOMPARE( row.at( 5 ), QVariant( 6 ) );
494+
row = table->contents()->at( 1 );
495+
QCOMPARE( row.at( 0 ), QVariant( "Biplane" ) );
496+
QCOMPARE( row.at( 1 ), QVariant( 340 ) );
497+
QCOMPARE( row.at( 2 ), QVariant( 1 ) );
498+
QCOMPARE( row.at( 3 ), QVariant( 3 ) );
499+
QCOMPARE( row.at( 4 ), QVariant( 3 ) );
500+
QCOMPARE( row.at( 5 ), QVariant( 6 ) );
501+
502+
mComposition->atlasComposition().endRender();
503+
504+
//try for a crash when removing current atlas layer
505+
QgsMapLayerRegistry::instance()->removeMapLayer( atlasLayer->id() );
506+
507+
table->refreshAttributes();
508+
509+
mComposition->removeMultiFrame( table );
510+
delete table;
511+
}
512+
424513
QTEST_MAIN( TestQgsComposerTableV2 )
425514
#include "moc_testqgscomposertablev2.cxx"
426515

‎tests/testdata/points_relations.dbf

343 Bytes
Binary file not shown.

‎tests/testdata/points_relations.prj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

‎tests/testdata/points_relations.qpj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

‎tests/testdata/points_relations.shp

184 Bytes
Binary file not shown.

‎tests/testdata/points_relations.shx

124 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.