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"