Skip to content

Commit 8eca38c

Browse files
committedMay 27, 2015
Attribute table performance when deleting features
This fixes performance issues with the attribute table visible when deleting a large number of features. The attribute table tries to behave smart in the following way: * It tries to remove only the deleted rows as long as they are in one or a few single blocks * If there are more than 100 rows to delete and it starts to delete blocks of a size smaller than 10 it assumes that the selection to delete is widely distributed and that a reload of the whole model is less expensive than a differential update. Fix #10167
1 parent bb9d413 commit 8eca38c

File tree

7 files changed

+155
-47
lines changed

7 files changed

+155
-47
lines changed
 

‎python/core/qgsvectorlayer.sip

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,16 @@ class QgsVectorLayer : QgsMapLayer
12921292
void attributeDeleted( int idx );
12931293
void featureAdded( QgsFeatureId fid );
12941294
void featureDeleted( QgsFeatureId fid );
1295+
/**
1296+
* Emitted when features have been deleted.
1297+
*
1298+
* If features are deleted within an edit command, this will only be emitted once at the end
1299+
* to allow connected slots to minimize the overhead.
1300+
* If features are delted outside of an edit command, this signal will be emitted once per feature.
1301+
*
1302+
* @param fids The feature ids that have been deleted.
1303+
*/
1304+
void featuresDeleted( QgsFeatureIds fids );
12951305
/**
12961306
* Is emitted, whenever the fields available from this layer have been changed.
12971307
* This can be due to manually adding attributes or due to a join.

‎python/gui/attributetable/qgsattributetablemodel.sip

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,10 @@ class QgsAttributeTableModel : QAbstractTableModel
198198
*/
199199
virtual void attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
200200
/**
201-
* Launched when a feature has been deleted
202-
* @param fid feature id
201+
* Launched when eatures have been deleted
202+
* @param fids feature ids
203203
*/
204-
virtual void featureDeleted( QgsFeatureId fid );
204+
virtual void featuresDeleted( QgsFeatureIds fid );
205205
/**
206206
* Launched when a feature has been added
207207
* @param fid feature id

‎src/app/qgsattributetabledialog.cpp

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
139139
connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) );
140140
connect( mLayer, SIGNAL( layerDeleted() ), this, SLOT( close() ) );
141141
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateTitle() ) );
142+
connect( mLayer, SIGNAL( featureAdded( QgsFeatureId ) ), this, SLOT( updateTitle() ) );
143+
connect( mLayer, SIGNAL( featuresDeleted( QgsFeatureIds ) ), this, SLOT( updateTitle() ) );
142144
connect( mLayer, SIGNAL( attributeAdded( int ) ), this, SLOT( columnBoxInit() ) );
143145
connect( mLayer, SIGNAL( attributeDeleted( int ) ), this, SLOT( columnBoxInit() ) );
144146

@@ -405,12 +407,12 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, QStrin
405407
mLayer->endEditCommand();
406408
}
407409

408-
void QgsAttributeTableDialog::replaceSearchWidget(QWidget* oldw, QWidget* neww)
410+
void QgsAttributeTableDialog::replaceSearchWidget( QWidget* oldw, QWidget* neww )
409411
{
410-
mFilterLayout->removeWidget(oldw);
411-
oldw->setVisible(false);
412-
mFilterLayout->addWidget(neww,0,0,0);
413-
neww->setVisible(true);
412+
mFilterLayout->removeWidget( oldw );
413+
oldw->setVisible( false );
414+
mFilterLayout->addWidget( neww, 0, 0, 0 );
415+
neww->setVisible( true );
414416
}
415417

416418
void QgsAttributeTableDialog::filterColumnChanged( QObject* filterAction )
@@ -427,14 +429,14 @@ void QgsAttributeTableDialog::filterColumnChanged( QObject* filterAction )
427429
QString fieldName = mFilterButton->defaultAction()->text();
428430
// get the search widget
429431
int fldIdx = mLayer->fieldNameIndex( fieldName );
430-
if ( fldIdx < 0 )
431-
return;
432+
if ( fldIdx < 0 )
433+
return;
432434
const QString widgetType = mLayer->editorWidgetV2( fldIdx );
433435
const QgsEditorWidgetConfig widgetConfig = mLayer->editorWidgetV2Config( fldIdx );
434-
mCurrentSearchWidgetWrapper= QgsEditorWidgetRegistry::instance()->
435-
createSearchWidget(widgetType, mLayer, fldIdx, widgetConfig, mFilterContainer);
436+
mCurrentSearchWidgetWrapper = QgsEditorWidgetRegistry::instance()->
437+
createSearchWidget( widgetType, mLayer, fldIdx, widgetConfig, mFilterContainer );
436438

437-
replaceSearchWidget(mFilterQuery, mCurrentSearchWidgetWrapper->widget());
439+
replaceSearchWidget( mFilterQuery, mCurrentSearchWidgetWrapper->widget() );
438440

439441
mApplyFilterButton->setVisible( true );
440442
}
@@ -723,7 +725,7 @@ void QgsAttributeTableDialog::filterQueryChanged( const QString& query )
723725
QString nullValue = settings.value( "qgis/nullValue", "NULL" ).toString();
724726
QString value = mCurrentSearchWidgetWrapper->value().toString();
725727

726-
if ( value == nullValue )
728+
if ( value == nullValue )
727729
{
728730
str = QString( "%1 IS NULL" ).arg( QgsExpression::quotedColumnRef( fieldName ) );
729731
}
@@ -745,9 +747,9 @@ void QgsAttributeTableDialog::filterQueryChanged( const QString& query )
745747

746748
void QgsAttributeTableDialog::filterQueryAccepted()
747749
{
748-
if ( (mFilterQuery->isVisible() && mFilterQuery->text().isEmpty()) ||
749-
(mCurrentSearchWidgetWrapper!=0 && mCurrentSearchWidgetWrapper->widget()->isVisible()
750-
&& mCurrentSearchWidgetWrapper->value().toString().isEmpty() ))
750+
if (( mFilterQuery->isVisible() && mFilterQuery->text().isEmpty() ) ||
751+
( mCurrentSearchWidgetWrapper != 0 && mCurrentSearchWidgetWrapper->widget()->isVisible()
752+
&& mCurrentSearchWidgetWrapper->value().toString().isEmpty() ) )
751753
{
752754
filterShowAll();
753755
return;
@@ -763,9 +765,9 @@ void QgsAttributeTableDialog::setFilterExpression( QString filterString )
763765
mCbxCaseSensitive->setVisible( false );
764766

765767
mFilterQuery->setVisible( true );
766-
if ( mCurrentSearchWidgetWrapper != 0 )
768+
if ( mCurrentSearchWidgetWrapper != 0 )
767769
{
768-
replaceSearchWidget(mCurrentSearchWidgetWrapper->widget(),mFilterQuery);
770+
replaceSearchWidget( mCurrentSearchWidgetWrapper->widget(), mFilterQuery );
769771
}
770772
mApplyFilterButton->setVisible( true );
771773
mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowFilteredList );

‎src/core/qgsvectorlayer.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ QgsVectorLayer::QgsVectorLayer( QString vectorLayerPath,
145145
, mValidExtent( false )
146146
, mLazyExtent( true )
147147
, mSymbolFeatureCounted( false )
148+
, mEditCommandActive( false )
148149
{
149150
mActions = new QgsAttributeAction( this );
150151

@@ -1210,7 +1211,7 @@ bool QgsVectorLayer::startEditing()
12101211
connect( mEditBuffer, SIGNAL( layerModified() ), this, SIGNAL( layerModified() ) ); // TODO[MD]: necessary?
12111212
//connect( mEditBuffer, SIGNAL( layerModified() ), this, SLOT( triggerRepaint() ) ); // TODO[MD]: works well?
12121213
connect( mEditBuffer, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( featureAdded( QgsFeatureId ) ) );
1213-
connect( mEditBuffer, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SIGNAL( featureDeleted( QgsFeatureId ) ) );
1214+
connect( mEditBuffer, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( onFeatureDeleted( QgsFeatureId ) ) );
12141215
connect( mEditBuffer, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ) );
12151216
connect( mEditBuffer, SIGNAL( attributeValueChanged( QgsFeatureId, int, QVariant ) ), this, SIGNAL( attributeValueChanged( QgsFeatureId, int, QVariant ) ) );
12161217
connect( mEditBuffer, SIGNAL( attributeAdded( int ) ), this, SIGNAL( attributeAdded( int ) ) );
@@ -2783,6 +2784,7 @@ void QgsVectorLayer::beginEditCommand( QString text )
27832784
if ( !mDataProvider->transaction() )
27842785
{
27852786
undoStack()->beginMacro( text );
2787+
mEditCommandActive = true;
27862788
emit editCommandStarted( text );
27872789
}
27882790
}
@@ -2796,6 +2798,12 @@ void QgsVectorLayer::endEditCommand()
27962798
if ( !mDataProvider->transaction() )
27972799
{
27982800
undoStack()->endMacro();
2801+
mEditCommandActive = false;
2802+
if ( mDeletedFids.count() )
2803+
{
2804+
emit featuresDeleted( mDeletedFids );
2805+
mDeletedFids.clear();
2806+
}
27992807
emit editCommandEnded();
28002808
}
28012809
}
@@ -2810,6 +2818,8 @@ void QgsVectorLayer::destroyEditCommand()
28102818
{
28112819
undoStack()->endMacro();
28122820
undoStack()->undo();
2821+
mEditCommandActive = false;
2822+
mDeletedFids.clear();
28132823
emit editCommandDestroyed();
28142824
}
28152825
}
@@ -3762,6 +3772,16 @@ void QgsVectorLayer::onJoinedFieldsChanged()
37623772
updateFields();
37633773
}
37643774

3775+
void QgsVectorLayer::onFeatureDeleted( const QgsFeatureId& fid )
3776+
{
3777+
if ( mEditCommandActive )
3778+
mDeletedFids << fid;
3779+
else
3780+
emit featuresDeleted( QgsFeatureIds() << fid );
3781+
3782+
emit featureDeleted( fid );
3783+
}
3784+
37653785
QgsVectorLayer::ValueRelationData QgsVectorLayer::valueRelation( int idx )
37663786
{
37673787
if ( editorWidgetV2( idx ) == "ValueRelation" )

‎src/core/qgsvectorlayer.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,8 +1656,31 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
16561656
* @see updatedFields()
16571657
*/
16581658
void attributeDeleted( int idx );
1659+
/**
1660+
* Emitted when a new feature has been added to the layer
1661+
*
1662+
* @param fid The id of the new feature
1663+
*/
16591664
void featureAdded( QgsFeatureId fid );
1665+
/**
1666+
* Emitted when a feature has been deleted.
1667+
*
1668+
* If you do expensive operations in a slot connected to this, you should prever to use
1669+
* {@link featuresDeleted(QgsFeatureIds)}.
1670+
*
1671+
* @param fid The id of the feature which has been deleted
1672+
*/
16601673
void featureDeleted( QgsFeatureId fid );
1674+
/**
1675+
* Emitted when features have been deleted.
1676+
*
1677+
* If features are deleted within an edit command, this will only be emitted once at the end
1678+
* to allow connected slots to minimize the overhead.
1679+
* If features are delted outside of an edit command, this signal will be emitted once per feature.
1680+
*
1681+
* @param fids The feature ids that have been deleted.
1682+
*/
1683+
void featuresDeleted( QgsFeatureIds fids );
16611684
/**
16621685
* Is emitted, whenever the fields available from this layer have been changed.
16631686
* This can be due to manually adding attributes or due to a join.
@@ -1734,6 +1757,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
17341757
private slots:
17351758
void onRelationsLoaded();
17361759
void onJoinedFieldsChanged();
1760+
void onFeatureDeleted( const QgsFeatureId& fid );
17371761

17381762
protected:
17391763
/** Set the extent */
@@ -1896,6 +1920,11 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
18961920
// Feature counts for each renderer symbol
18971921
QMap<QgsSymbolV2*, long> mSymbolFeatureCountMap;
18981922

1923+
//! True while an undo command is active
1924+
bool mEditCommandActive;
1925+
1926+
QgsFeatureIds mDeletedFids;
1927+
18991928
friend class QgsVectorLayerFeatureSource;
19001929
};
19011930

‎src/gui/attributetable/qgsattributetablemodel.cpp

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ QgsAttributeTableModel::QgsAttributeTableModel( QgsVectorLayerCache *layerCache,
5353
loadAttributes();
5454

5555
connect( mLayerCache, SIGNAL( attributeValueChanged( QgsFeatureId, int, const QVariant& ) ), this, SLOT( attributeValueChanged( QgsFeatureId, int, const QVariant& ) ) );
56-
connect( layer(), SIGNAL( featureDeleted( QgsFeatureId ) ), this, SLOT( featureDeleted( QgsFeatureId ) ) );
56+
connect( layer(), SIGNAL( featuresDeleted( QgsFeatureIds ) ), this, SLOT( featuresDeleted( QgsFeatureIds ) ) );
5757
connect( layer(), SIGNAL( attributeDeleted( int ) ), this, SLOT( attributeDeleted( int ) ) );
5858
connect( layer(), SIGNAL( updatedFields() ), this, SLOT( updatedFields() ) );
5959
connect( layer(), SIGNAL( editCommandEnded() ), this, SLOT( editCommandEnded() ) );
@@ -73,29 +73,74 @@ bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const
7373
return mLayerCache->featureAtId( fid, mFeat );
7474
}
7575

76-
void QgsAttributeTableModel::featureDeleted( QgsFeatureId fid )
76+
void QgsAttributeTableModel::featuresDeleted( QgsFeatureIds fids )
7777
{
78-
QgsDebugMsgLevel( QString( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 );
79-
mFieldCache.remove( fid );
78+
QList<int> rows;
79+
80+
Q_FOREACH ( const QgsFeatureId& fid, fids )
81+
{
82+
QgsDebugMsgLevel( QString( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 );
8083

81-
int row = idToRow( fid );
84+
int row = idToRow( fid );
85+
if ( row != -1 )
86+
rows << row;
87+
}
8288

83-
if ( row != -1 )
89+
qSort( rows );
90+
91+
int lastRow = -1;
92+
int beginRow = -1;
93+
int currentRowCount = 0;
94+
int removedRows = 0;
95+
bool reset = false;
96+
97+
Q_FOREACH ( int row, rows )
8498
{
85-
beginRemoveRows( QModelIndex(), row, row );
86-
removeRow( row );
87-
endRemoveRows();
99+
#if 0
100+
qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows;
101+
#endif
102+
if ( lastRow == -1 )
103+
{
104+
beginRow = row;
105+
}
106+
107+
if ( row != lastRow + 1 && lastRow != -1 )
108+
{
109+
if ( rows.count() > 100 && currentRowCount < 10 )
110+
{
111+
reset = true;
112+
break;
113+
}
114+
removeRows( beginRow - removedRows, currentRowCount );
115+
116+
beginRow = row;
117+
removedRows += currentRowCount;
118+
currentRowCount = 0;
119+
}
120+
121+
currentRowCount++;
122+
123+
lastRow = row;
88124
}
125+
126+
if ( !reset )
127+
removeRows( beginRow - removedRows, currentRowCount );
128+
else
129+
resetModel();
89130
}
90131

91132
bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent )
92133
{
93-
Q_UNUSED( parent );
94-
QgsDebugMsgLevel( QString( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
134+
beginRemoveRows( parent, row, row + count );
135+
#ifdef QGISDEBUG
136+
if ( 3 > QgsLogger::debugLevel() )
137+
QgsDebugMsgLevel( QString( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
138+
#endif
95139

96140
// clean old references
97141
for ( int i = row; i < row + count; i++ )
98142
{
143+
mFieldCache.remove( mRowIdMap[i] );
99144
mIdRowMap.remove( mRowIdMap[ i ] );
100145
mRowIdMap.remove( i );
101146
}
@@ -111,20 +156,25 @@ bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &
111156
}
112157

113158
#ifdef QGISDEBUG
114-
QgsDebugMsgLevel( QString( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
115-
QgsDebugMsgLevel( "id->row", 4 );
116-
for ( QHash<QgsFeatureId, int>::iterator it = mIdRowMap.begin(); it != mIdRowMap.end(); ++it )
117-
QgsDebugMsgLevel( QString( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
159+
if ( 4 > QgsLogger::debugLevel() )
160+
{
161+
QgsDebugMsgLevel( QString( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
162+
QgsDebugMsgLevel( "id->row", 4 );
163+
for ( QHash<QgsFeatureId, int>::iterator it = mIdRowMap.begin(); it != mIdRowMap.end(); ++it )
164+
QgsDebugMsgLevel( QString( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
118165

119-
QHash<QgsFeatureId, int>::iterator idit;
166+
QHash<QgsFeatureId, int>::iterator idit;
120167

121-
QgsDebugMsgLevel( "row->id", 4 );
122-
for ( QHash<int, QgsFeatureId>::iterator it = mRowIdMap.begin(); it != mRowIdMap.end(); ++it )
123-
QgsDebugMsgLevel( QString( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
168+
QgsDebugMsgLevel( "row->id", 4 );
169+
for ( QHash<int, QgsFeatureId>::iterator it = mRowIdMap.begin(); it != mRowIdMap.end(); ++it )
170+
QgsDebugMsgLevel( QString( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
171+
}
124172
#endif
125173

126174
Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
127175

176+
endRemoveRows();
177+
128178
return true;
129179
}
130180

@@ -179,9 +229,7 @@ void QgsAttributeTableModel::layerDeleted()
179229
{
180230
QgsDebugMsg( "entered." );
181231

182-
beginRemoveRows( QModelIndex(), 0, rowCount() - 1 );
183232
removeRows( 0, rowCount() );
184-
endRemoveRows();
185233

186234
mAttributeWidgetCaches.clear();
187235
mAttributes.clear();
@@ -223,7 +271,7 @@ void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, c
223271
if ( mIdRowMap.contains( fid ) )
224272
{
225273
// Feature changed such, that it is no longer shown
226-
featureDeleted( fid );
274+
featuresDeleted( QgsFeatureIds() << fid );
227275
}
228276
// else: we don't care
229277
}
@@ -291,9 +339,7 @@ void QgsAttributeTableModel::loadLayer()
291339

292340
if ( rowCount() != 0 )
293341
{
294-
beginRemoveRows( QModelIndex(), 0, rowCount() - 1 );
295342
removeRows( 0, rowCount() );
296-
endRemoveRows();
297343
}
298344

299345
QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
@@ -575,6 +621,7 @@ void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelInde
575621
void QgsAttributeTableModel::resetModel()
576622
{
577623
beginResetModel();
624+
loadLayer();
578625
endResetModel();
579626
}
580627

‎src/gui/attributetable/qgsattributetablemodel.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,10 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
261261
*/
262262
virtual void attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
263263
/**
264-
* Launched when a feature has been deleted
265-
* @param fid feature id
264+
* Launched when eatures have been deleted
265+
* @param fids feature ids
266266
*/
267-
virtual void featureDeleted( QgsFeatureId fid );
267+
virtual void featuresDeleted( QgsFeatureIds fid );
268268
/**
269269
* Launched when a feature has been added
270270
* @param fid feature id

0 commit comments

Comments
 (0)
Please sign in to comment.