diff --git a/python/core/composer/qgscomposerattributetablev2.sip b/python/core/composer/qgscomposerattributetablev2.sip
index 1d38ac5e955..0aa9c61980c 100644
--- a/python/core/composer/qgscomposerattributetablev2.sip
+++ b/python/core/composer/qgscomposerattributetablev2.sip
@@ -146,7 +146,21 @@ class QgsComposerAttributeTableV2 : QgsComposerTableV2
* @see setMaximumNumberOfFeatures
*/
int maximumNumberOfFeatures() const;
+
+ /**Sets attribute table to only show unique rows.
+ * @param uniqueOnly set to true to show only unique rows. Duplicate rows
+ * will be stripped from the table.
+ * @see uniqueRowsOnly
+ */
+ void setUniqueRowsOnly( const bool uniqueOnly );
+ /**Returns true if the table is set to show only unique rows.
+ * @returns true if table only shows unique rows and is stripping out
+ * duplicate rows.
+ * @see setUniqueRowsOnly
+ */
+ bool uniqueRowsOnly() const;
+
/**Sets attribute table to only show features which are visible in a composer map item. Changing
* this setting forces the table to refetch features from its vector layer, and may result in
* the table changing size to accommodate the new displayed feature attributes.
diff --git a/python/core/composer/qgscomposertablev2.sip b/python/core/composer/qgscomposertablev2.sip
index 81a69af4890..8a820525fe8 100644
--- a/python/core/composer/qgscomposertablev2.sip
+++ b/python/core/composer/qgscomposertablev2.sip
@@ -358,4 +358,11 @@ class QgsComposerTableV2: QgsComposerMultiFrame
*/
void recalculateTableSize();
+ /**Checks whether a table contents contains a given row
+ * @param contents table contents to check
+ * @param row row to check for
+ * @returns true if contents contains rows
+ */
+ bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
+
};
diff --git a/src/app/composer/qgsattributeselectiondialog.cpp b/src/app/composer/qgsattributeselectiondialog.cpp
index 5ca11aa6e99..33f2bccb001 100644
--- a/src/app/composer/qgsattributeselectiondialog.cpp
+++ b/src/app/composer/qgsattributeselectiondialog.cpp
@@ -383,15 +383,21 @@ void QgsAttributeSelectionDialog::on_mRemoveColumnPushButton_clicked()
{
//remove selected row from model
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModel->removeRow( selectedRow );
+ if ( viewSelection.length() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModel->removeRow( selectedRow );
+ }
}
if ( mComposerTableV1 )
{
//remove selected row from model
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModelV1->removeRow( selectedRow );
+ if ( viewSelection.length() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModelV1->removeRow( selectedRow );
+ }
}
}
@@ -416,15 +422,21 @@ void QgsAttributeSelectionDialog::on_mColumnUpPushButton_clicked()
{
//move selected row up
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModel->moveRow( selectedRow, QgsComposerAttributeTableColumnModelV2::ShiftUp );
+ if ( viewSelection.size() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModel->moveRow( selectedRow, QgsComposerAttributeTableColumnModelV2::ShiftUp );
+ }
}
else if ( mComposerTableV1 )
{
//move selected row up
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModelV1->moveRow( selectedRow, QgsComposerAttributeTableColumnModel::ShiftUp );
+ if ( viewSelection.size() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModelV1->moveRow( selectedRow, QgsComposerAttributeTableColumnModel::ShiftUp );
+ }
}
}
@@ -434,15 +446,21 @@ void QgsAttributeSelectionDialog::on_mColumnDownPushButton_clicked()
{
//move selected row down
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModel->moveRow( selectedRow, QgsComposerAttributeTableColumnModelV2::ShiftDown );
+ if ( viewSelection.size() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModel->moveRow( selectedRow, QgsComposerAttributeTableColumnModelV2::ShiftDown );
+ }
}
else if ( mComposerTableV1 )
{
//move selected row down
QItemSelection viewSelection( mColumnsTableView->selectionModel()->selection() );
- int selectedRow = viewSelection.indexes().at( 0 ).row();
- mColumnModelV1->moveRow( selectedRow, QgsComposerAttributeTableColumnModel::ShiftDown );
+ if ( viewSelection.size() > 0 )
+ {
+ int selectedRow = viewSelection.indexes().at( 0 ).row();
+ mColumnModelV1->moveRow( selectedRow, QgsComposerAttributeTableColumnModel::ShiftDown );
+ }
}
}
@@ -498,8 +516,11 @@ void QgsAttributeSelectionDialog::on_mAddSortColumnPushButton_clicked()
void QgsAttributeSelectionDialog::on_mRemoveSortColumnPushButton_clicked()
{
//remove selected rows from sort order widget
-
QItemSelection sortSelection( mSortColumnTableView->selectionModel()->selection() );
+ if ( sortSelection.length() < 1 )
+ {
+ return;
+ }
QModelIndex selectedIndex = sortSelection.indexes().at( 0 );
int rowToRemove = selectedIndex.row();
@@ -536,6 +557,10 @@ void QgsAttributeSelectionDialog::on_mSortColumnUpPushButton_clicked()
{
//find selected row
QItemSelection sortSelection( mSortColumnTableView->selectionModel()->selection() );
+ if ( sortSelection.length() < 1 )
+ {
+ return;
+ }
QModelIndex selectedIndex = sortSelection.indexes().at( 0 );
if ( mComposerTable )
@@ -562,10 +587,13 @@ void QgsAttributeSelectionDialog::on_mSortColumnUpPushButton_clicked()
void QgsAttributeSelectionDialog::on_mSortColumnDownPushButton_clicked()
{
-
-
//find selected row
QItemSelection sortSelection( mSortColumnTableView->selectionModel()->selection() );
+ if ( sortSelection.length() < 1 )
+ {
+ return;
+ }
+
QModelIndex selectedIndex = sortSelection.indexes().at( 0 );
if ( mComposerTable )
diff --git a/src/app/composer/qgscomposerattributetablewidget.cpp b/src/app/composer/qgscomposerattributetablewidget.cpp
index 42c5af95e62..226b518ccd8 100644
--- a/src/app/composer/qgscomposerattributetablewidget.cpp
+++ b/src/app/composer/qgscomposerattributetablewidget.cpp
@@ -491,6 +491,7 @@ void QgsComposerAttributeTableWidget::updateGuiElements()
mComposerMapLabel->setEnabled( false );
}
+ mUniqueOnlyCheckBox->setChecked( mComposerTable->uniqueRowsOnly() );
mIntersectAtlasCheckBox->setChecked( mComposerTable->filterToAtlasFeature() );
mFeatureFilterEdit->setText( mComposerTable->featureFilter() );
mFeatureFilterCheckBox->setCheckState( mComposerTable->filterFeatures() ? Qt::Checked : Qt::Unchecked );
@@ -595,6 +596,7 @@ void QgsComposerAttributeTableWidget::blockAllSignals( bool b )
mGridStrokeWidthSpinBox->blockSignals( b );
mShowGridGroupCheckBox->blockSignals( b );
mShowOnlyVisibleFeaturesCheckBox->blockSignals( b );
+ mUniqueOnlyCheckBox->blockSignals( b );
mIntersectAtlasCheckBox->blockSignals( b );
mFeatureFilterEdit->blockSignals( b );
mFeatureFilterCheckBox->blockSignals( b );
@@ -640,6 +642,26 @@ void QgsComposerAttributeTableWidget::on_mShowOnlyVisibleFeaturesCheckBox_stateC
mComposerMapLabel->setEnabled( state == Qt::Checked );
}
+void QgsComposerAttributeTableWidget::on_mUniqueOnlyCheckBox_stateChanged( int state )
+{
+ if ( !mComposerTable )
+ {
+ return;
+ }
+
+ QgsComposition* composition = mComposerTable->composition();
+ if ( composition )
+ {
+ composition->beginMultiFrameCommand( mComposerTable, tr( "Table remove duplicates changed" ) );
+ }
+ mComposerTable->setUniqueRowsOnly( state == Qt::Checked );
+ mComposerTable->update();
+ if ( composition )
+ {
+ composition->endMultiFrameCommand();
+ }
+}
+
void QgsComposerAttributeTableWidget::on_mIntersectAtlasCheckBox_stateChanged( int state )
{
if ( !mComposerTable )
diff --git a/src/app/composer/qgscomposerattributetablewidget.h b/src/app/composer/qgscomposerattributetablewidget.h
index 197d1f53184..4e5db698cc8 100644
--- a/src/app/composer/qgscomposerattributetablewidget.h
+++ b/src/app/composer/qgscomposerattributetablewidget.h
@@ -73,6 +73,7 @@ class QgsComposerAttributeTableWidget: public QgsComposerItemBaseWidget, private
void on_mEmptyModeComboBox_currentIndexChanged( int index );
void on_mEmptyMessageLineEdit_editingFinished();
void on_mIntersectAtlasCheckBox_stateChanged( int state );
+ void on_mUniqueOnlyCheckBox_stateChanged( int state );
/**Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)*/
void setMaximumNumberOfFeatures( int n );
diff --git a/src/core/composer/qgscomposerattributetablev2.cpp b/src/core/composer/qgscomposerattributetablev2.cpp
index 7aa52e2270f..617c72cfaa0 100644
--- a/src/core/composer/qgscomposerattributetablev2.cpp
+++ b/src/core/composer/qgscomposerattributetablev2.cpp
@@ -104,6 +104,7 @@ QgsComposerAttributeTableV2::QgsComposerAttributeTableV2( QgsComposition* compos
, mCurrentAtlasLayer( 0 )
, mComposerMap( 0 )
, mMaximumNumberOfFeatures( 5 )
+ , mShowUniqueRowsOnly( false )
, mShowOnlyVisibleFeatures( false )
, mFilterToAtlasIntersection( false )
, mFilterFeatures( false )
@@ -293,6 +294,18 @@ void QgsComposerAttributeTableV2::setMaximumNumberOfFeatures( const int features
emit changed();
}
+void QgsComposerAttributeTableV2::setUniqueRowsOnly( const bool uniqueOnly )
+{
+ if ( uniqueOnly == mShowUniqueRowsOnly )
+ {
+ return;
+ }
+
+ mShowUniqueRowsOnly = uniqueOnly;
+ refreshAttributes();
+ emit changed();
+}
+
void QgsComposerAttributeTableV2::setDisplayOnlyVisibleFeatures( const bool visibleOnly )
{
if ( visibleOnly == mShowOnlyVisibleFeatures )
@@ -555,8 +568,12 @@ bool QgsComposerAttributeTableV2::getTableContents( QgsComposerTableContents &co
currentRow << value;
}
}
- contents << currentRow;
- ++counter;
+
+ if ( !mShowUniqueRowsOnly || !contentsContainsRow( contents, currentRow ) )
+ {
+ contents << currentRow;
+ ++counter;
+ }
}
//sort the list, starting with the last attribute
@@ -644,6 +661,7 @@ bool QgsComposerAttributeTableV2::writeXML( QDomElement& elem, QDomDocument & do
QDomElement composerTableElem = doc.createElement( "ComposerAttributeTableV2" );
composerTableElem.setAttribute( "source", QString::number(( int )mSource ) );
composerTableElem.setAttribute( "relationId", mRelationId );
+ composerTableElem.setAttribute( "showUniqueRowsOnly", mShowUniqueRowsOnly );
composerTableElem.setAttribute( "showOnlyVisibleFeatures", mShowOnlyVisibleFeatures );
composerTableElem.setAttribute( "filterToAtlasIntersection", mFilterToAtlasIntersection );
composerTableElem.setAttribute( "maxFeatures", mMaximumNumberOfFeatures );
@@ -698,6 +716,7 @@ bool QgsComposerAttributeTableV2::readXML( const QDomElement& itemElem, const QD
mCurrentAtlasLayer = mComposition->atlasComposition().coverageLayer();
}
+ mShowUniqueRowsOnly = itemElem.attribute( "showUniqueRowsOnly", "0" ).toInt();
mShowOnlyVisibleFeatures = itemElem.attribute( "showOnlyVisibleFeatures", "1" ).toInt();
mFilterToAtlasIntersection = itemElem.attribute( "filterToAtlasIntersection", "0" ).toInt();
mFilterFeatures = itemElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
diff --git a/src/core/composer/qgscomposerattributetablev2.h b/src/core/composer/qgscomposerattributetablev2.h
index 92cd4fc6fb0..be5299638a3 100644
--- a/src/core/composer/qgscomposerattributetablev2.h
+++ b/src/core/composer/qgscomposerattributetablev2.h
@@ -170,6 +170,20 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
*/
int maximumNumberOfFeatures() const { return mMaximumNumberOfFeatures; }
+ /**Sets attribute table to only show unique rows.
+ * @param uniqueOnly set to true to show only unique rows. Duplicate rows
+ * will be stripped from the table.
+ * @see uniqueRowsOnly
+ */
+ void setUniqueRowsOnly( const bool uniqueOnly );
+
+ /**Returns true if the table is set to show only unique rows.
+ * @returns true if table only shows unique rows and is stripping out
+ * duplicate rows.
+ * @see setUniqueRowsOnly
+ */
+ bool uniqueRowsOnly() const { return mShowUniqueRowsOnly; }
+
/**Sets attribute table to only show features which are visible in a composer map item. Changing
* this setting forces the table to refetch features from its vector layer, and may result in
* the table changing size to accommodate the new displayed feature attributes.
@@ -278,6 +292,9 @@ class CORE_EXPORT QgsComposerAttributeTableV2: public QgsComposerTableV2
/**Maximum number of features that is displayed*/
int mMaximumNumberOfFeatures;
+ /**True if only unique rows should be shown*/
+ bool mShowUniqueRowsOnly;
+
/**Shows only the features that are visible in the associated composer map (true by default)*/
bool mShowOnlyVisibleFeatures;
diff --git a/src/core/composer/qgscomposertablev2.cpp b/src/core/composer/qgscomposertablev2.cpp
index 9780f4cf395..33bbb3dc5c2 100644
--- a/src/core/composer/qgscomposertablev2.cpp
+++ b/src/core/composer/qgscomposertablev2.cpp
@@ -801,3 +801,15 @@ void QgsComposerTableV2::recalculateTableSize()
//fixed and minimum frame sizes
recalculateFrameRects();
}
+
+bool QgsComposerTableV2::contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const
+{
+ if ( contents.indexOf( row ) >= 0 )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/src/core/composer/qgscomposertablev2.h b/src/core/composer/qgscomposertablev2.h
index 3f6929d6133..9729b4cdddc 100644
--- a/src/core/composer/qgscomposertablev2.h
+++ b/src/core/composer/qgscomposertablev2.h
@@ -428,6 +428,15 @@ class CORE_EXPORT QgsComposerTableV2: public QgsComposerMultiFrame
/**Recalculates and updates the size of the table and all table frames.
*/
void recalculateTableSize();
+
+ /**Checks whether a table contents contains a given row
+ * @param contents table contents to check
+ * @param row row to check for
+ * @returns true if contents contains rows
+ */
+ bool contentsContainsRow( const QgsComposerTableContents &contents, const QgsComposerTableRow &row ) const;
+
+ friend class TestQgsComposerTableV2;
};
#endif // QGSCOMPOSERTABLEV2_H
diff --git a/src/ui/qgscomposerattributetablewidgetbase.ui b/src/ui/qgscomposerattributetablewidgetbase.ui
index 6f4b160c022..3202d539236 100755
--- a/src/ui/qgscomposerattributetablewidgetbase.ui
+++ b/src/ui/qgscomposerattributetablewidgetbase.ui
@@ -47,7 +47,7 @@
0
0
392
- 1048
+ 1076
@@ -158,14 +158,14 @@
- -
+
-
Show only features visible within a map
- -
+
-
Composer map
@@ -178,17 +178,17 @@
- -
+
-
- -
+
-
Filter with
- -
+
-
-
@@ -206,13 +206,20 @@
- -
+
-
Show only features intersecting atlas feature
+ -
+
+
+ Remove duplicate rows from table
+
+
+
mShowOnlyVisibleFeaturesCheckBox
mComposerMapLabel
@@ -221,6 +228,7 @@
mMaximumRowsSpinBox
mMaxNumFeaturesLabel
mIntersectAtlasCheckBox
+ mUniqueOnlyCheckBox
-
diff --git a/tests/src/core/testqgscomposertablev2.cpp b/tests/src/core/testqgscomposertablev2.cpp
index fbc6feeea37..6ab2cd7792f 100644
--- a/tests/src/core/testqgscomposertablev2.cpp
+++ b/tests/src/core/testqgscomposertablev2.cpp
@@ -55,6 +55,8 @@ class TestQgsComposerTableV2: public QObject
void attributeTableRepeat();
void attributeTableAtlasSource(); //test attribute table in atlas feature mode
void attributeTableRelationSource(); //test attribute table in relation mode
+ void contentsContainsRow(); //test the contentsContainsRow function
+ void removeDuplicates(); //test removing duplicate rows
private:
QgsComposition* mComposition;
@@ -535,6 +537,74 @@ void TestQgsComposerTableV2::attributeTableRelationSource()
delete table;
}
+void TestQgsComposerTableV2::contentsContainsRow()
+{
+ QgsComposerTableContents testContents;
+ QgsComposerTableRow row1;
+ row1 << QVariant( QString( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QString( "string 2" ) );
+ QgsComposerTableRow row2;
+ row2 << QVariant( QString( "string 2" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QString( "string 2" ) );
+ //same as row1
+ QgsComposerTableRow row3;
+ row3 << QVariant( QString( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QString( "string 2" ) );
+ QgsComposerTableRow row4;
+ row4 << QVariant( QString( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.7 ) << QVariant( QString( "string 2" ) );
+
+ testContents << row1;
+ testContents << row2;
+
+ QVERIFY( mComposerAttributeTable->contentsContainsRow( testContents, row1 ) );
+ QVERIFY( mComposerAttributeTable->contentsContainsRow( testContents, row2 ) );
+ QVERIFY( mComposerAttributeTable->contentsContainsRow( testContents, row3 ) );
+ QVERIFY( !mComposerAttributeTable->contentsContainsRow( testContents, row4 ) );
+}
+
+void TestQgsComposerTableV2::removeDuplicates()
+{
+ QgsVectorLayer* dupesLayer = new QgsVectorLayer( "Point?field=col1:integer&field=col2:integer&field=col3:integer", "dupes", "memory" );
+ QVERIFY( dupesLayer->isValid() );
+ QgsFeature f1( dupesLayer->dataProvider()->fields(), 1 );
+ f1.setAttribute( "col1", 1 );
+ f1.setAttribute( "col2", 1 );
+ f1.setAttribute( "col3", 1 );
+ QgsFeature f2( dupesLayer->dataProvider()->fields(), 2 );
+ f2.setAttribute( "col1", 1 );
+ f2.setAttribute( "col2", 2 );
+ f2.setAttribute( "col3", 2 );
+ QgsFeature f3( dupesLayer->dataProvider()->fields(), 3 );
+ f3.setAttribute( "col1", 1 );
+ f3.setAttribute( "col2", 2 );
+ f3.setAttribute( "col3", 3 );
+ QgsFeature f4( dupesLayer->dataProvider()->fields(), 4 );
+ f4.setAttribute( "col1", 1 );
+ f4.setAttribute( "col2", 1 );
+ f4.setAttribute( "col3", 1 );
+ dupesLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 );
+
+ QgsComposerAttributeTableV2* table = new QgsComposerAttributeTableV2( mComposition, false );
+ table->setSource( QgsComposerAttributeTableV2::LayerAttributes );
+ table->setVectorLayer( dupesLayer );
+ table->setMaximumNumberOfFeatures( 50 );
+ QCOMPARE( table->contents()->length(), 4 );
+
+ table->setUniqueRowsOnly( true );
+ QCOMPARE( table->contents()->length(), 3 );
+
+ //check if removing attributes in unique mode works correctly (should result in duplicate rows,
+ //which will be stripped out)
+ table->columns()->removeLast();
+ table->refreshAttributes();
+ QCOMPARE( table->contents()->length(), 2 );
+ table->columns()->removeLast();
+ table->refreshAttributes();
+ QCOMPARE( table->contents()->length(), 1 );
+ table->setUniqueRowsOnly( false );
+ QCOMPARE( table->contents()->length(), 4 );
+
+ mComposition->removeMultiFrame( table );
+ delete dupesLayer;
+}
+
QTEST_MAIN( TestQgsComposerTableV2 )
#include "moc_testqgscomposertablev2.cxx"