Skip to content

Commit e543ff4

Browse files
authoredMar 7, 2017
Merge pull request #4230 from nyalldawson/cache_table_218
Backport attribute table optimisations from 3.0
2 parents 2a6bceb + 8e875ed commit e543ff4

File tree

10 files changed

+328
-70
lines changed

10 files changed

+328
-70
lines changed
 

‎python/core/qgsvectorlayercache.sip

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,10 @@ class QgsVectorLayerCache : QObject
3333
*/
3434
int cacheSize();
3535

36-
/**
37-
* Enable or disable the caching of geometries
38-
*
39-
* @param cacheGeometry Enable or disable the caching of geometries
40-
*/
4136
void setCacheGeometry( bool cacheGeometry );
4237

38+
bool cacheGeometry() const;
4339

44-
/**
45-
* Set the subset of attributes to be cached
46-
*
47-
* @param attributes The attributes to be cached
48-
*/
4940
void setCacheSubsetOfAttributes( const QgsAttributeList& attributes );
5041

5142
/**

‎python/gui/attributetable/qgsdualview.sip

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,8 @@ class QgsDualView : QStackedWidget
3232
explicit QgsDualView( QWidget* parent /TransferThis/ = 0 );
3333
virtual ~QgsDualView();
3434

35-
/**
36-
* Has to be called to initialize the dual view.
37-
*
38-
* @param layer The layer which should be used to fetch features
39-
* @param mapCanvas The mapCanvas (used for the FilterMode
40-
* {@link QgsAttributeTableFilterModel::ShowVisible}
41-
* @param request Use a modified request to limit the shown features
42-
* @param context The context in which this view is shown
43-
*/
44-
void init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request = QgsFeatureRequest(), const QgsAttributeEditorContext& context = QgsAttributeEditorContext() );
35+
void init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request = QgsFeatureRequest(), const QgsAttributeEditorContext& context = QgsAttributeEditorContext(),
36+
bool loadFeatures = true );
4537

4638
/**
4739
* Change the current view mode.

‎src/app/qgsattributetabledialog.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
131131
mEditorContext.setVectorLayerTools( QgisApp::instance()->vectorLayerTools() );
132132

133133
QgsFeatureRequest r;
134+
bool needsGeom = false;
135+
QgsAttributeTableFilterModel::FilterMode initialMode = static_cast< QgsAttributeTableFilterModel::FilterMode>( settings.value( QString( "/qgis/attributeTableBehaviour" ), QgsAttributeTableFilterModel::ShowAll ).toInt() );
134136
if ( mLayer->geometryType() != QGis::NoGeometry &&
135-
settings.value( "/qgis/attributeTableBehaviour", QgsAttributeTableFilterModel::ShowAll ).toInt() == QgsAttributeTableFilterModel::ShowVisible )
137+
initialMode == QgsAttributeTableFilterModel::ShowVisible )
136138
{
137139
QgsMapCanvas *mc = QgisApp::instance()->mapCanvas();
138140
QgsRectangle extent( mc->mapSettings().mapToLayerCoordinates( theLayer, mc->extent() ) );
@@ -144,10 +146,18 @@ QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWid
144146
delete g;
145147

146148
mActionShowAllFilter->setText( tr( "Show All Features In Initial Canvas Extent" ) );
149+
needsGeom = true;
147150
}
151+
else if ( initialMode == QgsAttributeTableFilterModel::ShowSelected )
152+
{
153+
if ( theLayer->selectedFeatureCount() > 0 )
154+
r.setFilterFids( theLayer->selectedFeaturesIds() );
155+
}
156+
if ( !needsGeom )
157+
r.setFlags( QgsFeatureRequest::NoGeometry );
148158

149159
// Initialize dual view
150-
mMainView->init( mLayer, QgisApp::instance()->mapCanvas(), r, mEditorContext );
160+
mMainView->init( mLayer, QgisApp::instance()->mapCanvas(), r, mEditorContext, false );
151161

152162
QgsAttributeTableConfig config = mLayer->attributeTableConfig();
153163
mMainView->setAttributeTableConfig( config );
@@ -322,7 +332,7 @@ void QgsAttributeTableDialog::updateTitle()
322332
QWidget *w = mDock ? qobject_cast<QWidget*>( mDock ) : qobject_cast<QWidget*>( this );
323333
w->setWindowTitle( tr( " %1 :: Features total: %2, filtered: %3, selected: %4%5" )
324334
.arg( mLayer->name() )
325-
.arg( mMainView->featureCount() )
335+
.arg( qMax( static_cast< long >( mMainView->featureCount() ), mLayer->featureCount() ) ) // layer count may be estimated, so use larger of the two
326336
.arg( mMainView->filteredFeatureCount() )
327337
.arg( mLayer->selectedFeatureCount() )
328338
.arg( mRubberBand ? tr( ", spatially limited" ) : "" )

‎src/core/qgsvectorlayercache.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ int QgsVectorLayerCache::cacheSize()
5858

5959
void QgsVectorLayerCache::setCacheGeometry( bool cacheGeometry )
6060
{
61-
mCacheGeometry = cacheGeometry && mLayer->hasGeometryType();
61+
bool shouldCacheGeometry = cacheGeometry && mLayer->hasGeometryType();
62+
bool mustInvalidate = shouldCacheGeometry && !mCacheGeometry; // going from no geometry -> geometry, so have to clear existing cache entries
63+
mCacheGeometry = shouldCacheGeometry;
6264
if ( cacheGeometry )
6365
{
6466
connect( mLayer, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), SLOT( geometryChanged( QgsFeatureId, QgsGeometry& ) ) );
@@ -67,6 +69,10 @@ void QgsVectorLayerCache::setCacheGeometry( bool cacheGeometry )
6769
{
6870
disconnect( mLayer, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SLOT( geometryChanged( QgsFeatureId, QgsGeometry& ) ) );
6971
}
72+
if ( mustInvalidate )
73+
{
74+
invalidate();
75+
}
7076
}
7177

7278
void QgsVectorLayerCache::setCacheSubsetOfAttributes( const QgsAttributeList& attributes )
@@ -231,7 +237,7 @@ void QgsVectorLayerCache::attributeAdded( int field )
231237
{
232238
Q_UNUSED( field )
233239
mCachedAttributes.append( field );
234-
mCache.clear();
240+
invalidate();
235241
}
236242

237243
void QgsVectorLayerCache::attributeDeleted( int field )
@@ -267,6 +273,7 @@ void QgsVectorLayerCache::layerDeleted()
267273
void QgsVectorLayerCache::invalidate()
268274
{
269275
mCache.clear();
276+
mFullCache = false;
270277
emit invalidated();
271278
}
272279

‎src/core/qgsvectorlayercache.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,16 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
103103
* Enable or disable the caching of geometries
104104
*
105105
* @param cacheGeometry Enable or disable the caching of geometries
106+
* @see cacheGeometry()
106107
*/
107108
void setCacheGeometry( bool cacheGeometry );
108109

110+
/**
111+
* Returns true if the cache will fetch and cache feature geometries.
112+
* @note added in QGIS 3.0
113+
* @see setCacheGeometry()
114+
*/
115+
bool cacheGeometry() const { return mCacheGeometry; }
109116

110117
/**
111118
* Set the subset of attributes to be cached
@@ -131,6 +138,8 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
131138
* be used for slow data sources, be aware, that the call to this method might take a long time.
132139
*
133140
* @param fullCache True: enable full caching, False: disable full caching
141+
* @note when a cache is invalidated() (e.g. by adding an attribute to a layer) this setting
142+
* is reset. A full cache rebuild must be performed by calling setFullCache( true ) again.
134143
* @see hasFullCache()
135144
*/
136145
void setFullCache( bool fullCache );
@@ -273,7 +282,9 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
273282
void featureAdded( QgsFeatureId fid );
274283

275284
/**
276-
* The cache has been invalidated and cleared.
285+
* The cache has been invalidated and cleared. Note that when a cache is invalidated
286+
* the fullCache() setting will be cleared, and a full cache rebuild via setFullCache( true )
287+
* will need to be performed.
277288
*/
278289
void invalidated();
279290

‎src/gui/attributetable/qgsdualview.cpp

Lines changed: 145 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ QgsDualView::QgsDualView( QWidget* parent )
4848
, mProgressDlg( nullptr )
4949
, mFeatureSelectionManager( nullptr )
5050
, mAttributeEditorScrollArea( nullptr )
51+
, mMapCanvas( nullptr )
5152
{
5253
setupUi( this );
5354

@@ -67,27 +68,31 @@ QgsDualView::QgsDualView( QWidget* parent )
6768
connect( mFeatureList, SIGNAL( displayExpressionChanged( QString ) ), this, SLOT( previewExpressionChanged( QString ) ) );
6869
}
6970

70-
void QgsDualView::init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context )
71+
void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context, bool loadFeatures )
7172
{
73+
mMapCanvas = mapCanvas;
74+
7275
if ( !layer )
7376
return;
7477

78+
mLayer = layer;
79+
7580
mEditorContext = context;
7681

7782
connect( mTableView, SIGNAL( willShowContextMenu( QMenu*, QModelIndex ) ), this, SLOT( viewWillShowContextMenu( QMenu*, QModelIndex ) ) );
7883
mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
7984
connect( mTableView->horizontalHeader(), SIGNAL( customContextMenuRequested( QPoint ) ), this, SLOT( showViewHeaderMenu( QPoint ) ) );
8085
connect( mTableView, SIGNAL( columnResized( int, int ) ), this, SLOT( tableColumnResized( int, int ) ) );
8186

82-
initLayerCache( layer, !request.filterRect().isNull() );
83-
initModels( mapCanvas, request );
87+
initLayerCache( !( request.flags() & QgsFeatureRequest::NoGeometry ) || !request.filterRect().isNull() );
88+
initModels( mapCanvas, request, loadFeatures );
8489

85-
mConditionalFormatWidget->setLayer( layer );
90+
mConditionalFormatWidget->setLayer( mLayer );
8691

8792
mTableView->setModel( mFilterModel );
8893
mFeatureList->setModel( mFeatureListModel );
8994
delete mAttributeForm;
90-
mAttributeForm = new QgsAttributeForm( layer, QgsFeature(), mEditorContext );
95+
mAttributeForm = new QgsAttributeForm( mLayer, QgsFeature(), mEditorContext );
9196
if ( !context.parentContext() )
9297
{
9398
mAttributeEditorScrollArea = new QScrollArea();
@@ -119,27 +124,27 @@ void QgsDualView::init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const Qg
119124
void QgsDualView::columnBoxInit()
120125
{
121126
// load fields
122-
QList<QgsField> fields = mLayerCache->layer()->fields().toList();
127+
QList<QgsField> fields = mLayer->fields().toList();
123128

124129
QString defaultField;
125130

126131
// default expression: saved value
127-
QString displayExpression = mLayerCache->layer()->displayExpression();
132+
QString displayExpression = mLayer->displayExpression();
128133

129134
// if no display expression is saved: use display field instead
130135
if ( displayExpression.isEmpty() )
131136
{
132-
if ( !mLayerCache->layer()->displayField().isEmpty() )
137+
if ( !mLayer->displayField().isEmpty() )
133138
{
134-
defaultField = mLayerCache->layer()->displayField();
139+
defaultField = mLayer->displayField();
135140
displayExpression = QString( "COALESCE(\"%1\", '<NULL>')" ).arg( defaultField );
136141
}
137142
}
138143

139144
// if neither display expression nor display field is saved...
140145
if ( displayExpression.isEmpty() )
141146
{
142-
QgsAttributeList pkAttrs = mLayerCache->layer()->pkAttributeList();
147+
QgsAttributeList pkAttrs = mLayer->pkAttributeList();
143148

144149
if ( !pkAttrs.isEmpty() )
145150
{
@@ -182,13 +187,13 @@ void QgsDualView::columnBoxInit()
182187

183188
Q_FOREACH ( const QgsField& field, fields )
184189
{
185-
int fieldIndex = mLayerCache->layer()->fieldNameIndex( field.name() );
190+
int fieldIndex = mLayer->fieldNameIndex( field.name() );
186191
if ( fieldIndex == -1 )
187192
continue;
188193

189-
if ( mLayerCache->layer()->editFormConfig()->widgetType( fieldIndex ) != "Hidden" )
194+
if ( mLayer->editFormConfig()->widgetType( fieldIndex ) != "Hidden" )
190195
{
191-
QIcon icon = mLayerCache->layer()->fields().iconForField( fieldIndex );
196+
QIcon icon = mLayer->fields().iconForField( fieldIndex );
192197
QString text = field.name();
193198

194199
// Generate action for the preview popup button of the feature list
@@ -233,6 +238,71 @@ QgsDualView::ViewMode QgsDualView::view() const
233238

234239
void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filterMode )
235240
{
241+
// cleanup any existing connections
242+
switch ( mFilterModel->filterMode() )
243+
{
244+
case QgsAttributeTableFilterModel::ShowVisible:
245+
disconnect( mMapCanvas, SIGNAL( extentsChanged() ), this, SLOT( extentChanged() ) );
246+
break;
247+
248+
case QgsAttributeTableFilterModel::ShowAll:
249+
case QgsAttributeTableFilterModel::ShowEdited:
250+
case QgsAttributeTableFilterModel::ShowFilteredList:
251+
break;
252+
253+
case QgsAttributeTableFilterModel::ShowSelected:
254+
disconnect( masterModel()->layer(), SIGNAL( selectionChanged() ), this,
255+
SLOT( updateSelectedFeatures() ) );
256+
break;
257+
}
258+
259+
QgsFeatureRequest r = mMasterModel->request();
260+
bool needsGeometry = filterMode == QgsAttributeTableFilterModel::ShowVisible;
261+
262+
bool requiresTableReload = ( r.filterType() != QgsFeatureRequest::FilterNone || !r.filterRect().isNull() ) // previous request was subset
263+
|| ( needsGeometry && r.flags() & QgsFeatureRequest::NoGeometry ) // no geometry for last request
264+
|| ( mMasterModel->rowCount() == 0 ); // no features
265+
266+
if ( !needsGeometry )
267+
r.setFlags( r.flags() | QgsFeatureRequest::NoGeometry );
268+
else
269+
r.setFlags( r.flags() & ~( QgsFeatureRequest::NoGeometry ) );
270+
r.setFilterFids( QgsFeatureIds() );
271+
r.setFilterRect( QgsRectangle() );
272+
r.disableFilter();
273+
274+
// setup new connections and filter request parameters
275+
switch ( filterMode )
276+
{
277+
case QgsAttributeTableFilterModel::ShowVisible:
278+
connect( mMapCanvas, SIGNAL( extentsChanged() ), this, SLOT( extentChanged() ) );
279+
if ( mMapCanvas )
280+
{
281+
QgsRectangle rect = mMapCanvas->mapSettings().mapToLayerCoordinates( mLayer, mMapCanvas->extent() );
282+
r.setFilterRect( rect );
283+
}
284+
break;
285+
286+
case QgsAttributeTableFilterModel::ShowAll:
287+
case QgsAttributeTableFilterModel::ShowEdited:
288+
case QgsAttributeTableFilterModel::ShowFilteredList:
289+
break;
290+
291+
case QgsAttributeTableFilterModel::ShowSelected:
292+
connect( masterModel()->layer(), SIGNAL( selectionChanged() ), this, SLOT( updateSelectedFeatures() ) );
293+
if ( masterModel()->layer()->selectedFeatureCount() > 0 )
294+
r.setFilterFids( masterModel()->layer()->selectedFeaturesIds() );
295+
break;
296+
}
297+
298+
if ( requiresTableReload )
299+
{
300+
mMasterModel->setRequest( r );
301+
whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
302+
mMasterModel->loadLayer();
303+
}
304+
305+
//update filter model
236306
mFilterModel->setFilterMode( filterMode );
237307
emit filterChanged();
238308
}
@@ -242,23 +312,21 @@ void QgsDualView::setSelectedOnTop( bool selectedOnTop )
242312
mFilterModel->setSelectedOnTop( selectedOnTop );
243313
}
244314

245-
void QgsDualView::initLayerCache( QgsVectorLayer* layer, bool cacheGeometry )
315+
void QgsDualView::initLayerCache( bool cacheGeometry )
246316
{
247317
// Initialize the cache
248318
QSettings settings;
249319
int cacheSize = settings.value( "/qgis/attributeTableRowCache", "10000" ).toInt();
250-
mLayerCache = new QgsVectorLayerCache( layer, cacheSize, this );
320+
mLayerCache = new QgsVectorLayerCache( mLayer, cacheSize, this );
251321
mLayerCache->setCacheGeometry( cacheGeometry );
252-
if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayerCache->layer()->dataProvider()->capabilities() ) )
322+
if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayer->dataProvider()->capabilities() ) )
253323
{
254-
connect( mLayerCache, SIGNAL( progress( int, bool & ) ), this, SLOT( progress( int, bool & ) ) );
255-
connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ) );
256-
257-
mLayerCache->setFullCache( true );
324+
connect( mLayerCache, SIGNAL( invalidated() ), this, SLOT( rebuildFullLayerCache() ) );
325+
rebuildFullLayerCache();
258326
}
259327
}
260328

261-
void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request )
329+
void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, bool loadFeatures )
262330
{
263331
delete mFeatureListModel;
264332
delete mFilterModel;
@@ -274,7 +342,8 @@ void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest&
274342

275343
connect( mConditionalFormatWidget, SIGNAL( rulesUpdated( QString ) ), mMasterModel, SLOT( fieldConditionalStyleChanged( QString ) ) );
276344

277-
mMasterModel->loadLayer();
345+
if ( loadFeatures )
346+
mMasterModel->loadLayer();
278347

279348
mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
280349

@@ -285,13 +354,13 @@ void QgsDualView::initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest&
285354

286355
void QgsDualView::on_mFeatureList_aboutToChangeEditSelection( bool& ok )
287356
{
288-
if ( mLayerCache->layer()->isEditable() && !mAttributeForm->save() )
357+
if ( mLayer->isEditable() && !mAttributeForm->save() )
289358
ok = false;
290359
}
291360

292361
void QgsDualView::on_mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
293362
{
294-
if ( !mLayerCache->layer()->isEditable() || mAttributeForm->save() )
363+
if ( !mLayer->isEditable() || mAttributeForm->save() )
295364
{
296365
mAttributeForm->setFeature( feat );
297366
setCurrentEditSelection( QgsFeatureIds() << feat.id() );
@@ -346,9 +415,9 @@ void QgsDualView::previewExpressionBuilder()
346415
QgsExpressionContext context;
347416
context << QgsExpressionContextUtils::globalScope()
348417
<< QgsExpressionContextUtils::projectScope()
349-
<< QgsExpressionContextUtils::layerScope( mLayerCache->layer() );
418+
<< QgsExpressionContextUtils::layerScope( mLayer );
350419

351-
QgsExpressionBuilderDialog dlg( mLayerCache->layer(), mFeatureList->displayExpression(), this, "generic", context );
420+
QgsExpressionBuilderDialog dlg( mLayer, mFeatureList->displayExpression(), this, "generic", context );
352421
dlg.setWindowTitle( tr( "Expression based preview" ) );
353422
dlg.setExpressionText( mFeatureList->displayExpression() );
354423

@@ -433,15 +502,15 @@ void QgsDualView::viewWillShowContextMenu( QMenu* menu, const QModelIndex& atInd
433502
}
434503

435504
//add user-defined actions to context menu
436-
if ( mLayerCache->layer()->actions()->size() != 0 )
505+
if ( mLayer->actions()->size() != 0 )
437506
{
438507

439508
QAction *a = menu->addAction( tr( "Run layer action" ) );
440509
a->setEnabled( false );
441510

442-
for ( int i = 0; i < mLayerCache->layer()->actions()->size(); i++ )
511+
for ( int i = 0; i < mLayer->actions()->size(); i++ )
443512
{
444-
const QgsAction &action = mLayerCache->layer()->actions()->at( i );
513+
const QgsAction &action = mLayer->actions()->at( i );
445514

446515
if ( !action.runable() )
447516
continue;
@@ -452,7 +521,7 @@ void QgsDualView::viewWillShowContextMenu( QMenu* menu, const QModelIndex& atInd
452521
}
453522

454523
//add actions from QgsMapLayerActionRegistry to context menu
455-
QList<QgsMapLayerAction *> registeredActions = QgsMapLayerActionRegistry::instance()->mapLayerActions( mLayerCache->layer() );
524+
QList<QgsMapLayerAction *> registeredActions = QgsMapLayerActionRegistry::instance()->mapLayerActions( mLayer );
456525
if ( !registeredActions.isEmpty() )
457526
{
458527
//add a separator between user defined and standard actions
@@ -504,12 +573,12 @@ void QgsDualView::showViewHeaderMenu( QPoint point )
504573

505574
void QgsDualView::organizeColumns()
506575
{
507-
if ( !mLayerCache->layer() )
576+
if ( !mLayer )
508577
{
509578
return;
510579
}
511580

512-
QgsOrganizeTableColumnsDialog dialog( mLayerCache->layer(), this );
581+
QgsOrganizeTableColumnsDialog dialog( mLayer, this );
513582
if ( dialog.exec() == QDialog::Accepted )
514583
{
515584
QgsAttributeTableConfig config = dialog.config();
@@ -573,8 +642,7 @@ void QgsDualView::autosizeColumn()
573642

574643
void QgsDualView::modifySort()
575644
{
576-
QgsVectorLayer* layer = mLayerCache->layer();
577-
if ( !layer )
645+
if ( !mLayer )
578646
return;
579647

580648
QgsAttributeTableConfig config = mConfig;
@@ -598,12 +666,12 @@ void QgsDualView::modifySort()
598666
QgsExpressionContext context;
599667
context << QgsExpressionContextUtils::globalScope()
600668
<< QgsExpressionContextUtils::projectScope()
601-
<< QgsExpressionContextUtils::layerScope( layer );
669+
<< QgsExpressionContextUtils::layerScope( mLayer );
602670
expressionBuilder->setExpressionContext( context );
603-
expressionBuilder->setLayer( layer );
671+
expressionBuilder->setLayer( mLayer );
604672
expressionBuilder->loadFieldNames();
605673
expressionBuilder->loadRecent( "generic" );
606-
expressionBuilder->setExpressionText( sortExpression().isEmpty() ? layer->displayExpression() : sortExpression() );
674+
expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
607675

608676
sortingGroupBox->layout()->addWidget( expressionBuilder );
609677

@@ -644,18 +712,26 @@ void QgsDualView::zoomToCurrentFeature()
644712
QgsMapCanvas* canvas = mFilterModel->mapCanvas();
645713
if ( canvas )
646714
{
647-
canvas->zoomToFeatureIds( mLayerCache->layer(), ids );
715+
canvas->zoomToFeatureIds( mLayer, ids );
648716
}
649717
}
650718

719+
void QgsDualView::rebuildFullLayerCache()
720+
{
721+
connect( mLayerCache, SIGNAL( progress( int, bool& ) ), this, SLOT( progress( int, bool& ) ), Qt::UniqueConnection );
722+
connect( mLayerCache, SIGNAL( finished() ), this, SLOT( finished() ), Qt::UniqueConnection );
723+
724+
mLayerCache->setFullCache( true );
725+
}
726+
651727
void QgsDualView::previewExpressionChanged( const QString& expression )
652728
{
653-
mLayerCache->layer()->setDisplayExpression( expression );
729+
mLayer->setDisplayExpression( expression );
654730
}
655731

656732
void QgsDualView::onSortColumnChanged()
657733
{
658-
QgsAttributeTableConfig cfg = mLayerCache->layer()->attributeTableConfig();
734+
QgsAttributeTableConfig cfg = mLayer->attributeTableConfig();
659735
cfg.setSortExpression( mFilterModel->sortExpression() );
660736
cfg.setSortOrder( mFilterModel->sortOrder() );
661737
setAttributeTableConfig( cfg );
@@ -671,6 +747,34 @@ void QgsDualView::sortByPreviewExpression()
671747
setSortExpression( mFeatureList->displayExpression(), sortOrder );
672748
}
673749

750+
void QgsDualView::updateSelectedFeatures()
751+
{
752+
QgsFeatureRequest r = mMasterModel->request();
753+
if ( r.filterType() == QgsFeatureRequest::FilterNone && r.filterRect().isNull() )
754+
return; // already requested all features
755+
756+
if ( masterModel()->layer()->selectedFeatureCount() > 0 )
757+
r.setFilterFids( masterModel()->layer()->selectedFeaturesIds() );
758+
else
759+
r.disableFilter();
760+
mMasterModel->setRequest( r );
761+
mMasterModel->loadLayer();
762+
emit filterChanged();
763+
}
764+
765+
void QgsDualView::extentChanged()
766+
{
767+
QgsFeatureRequest r = mMasterModel->request();
768+
if ( mMapCanvas && ( r.filterType() != QgsFeatureRequest::FilterNone || !r.filterRect().isNull() ) )
769+
{
770+
QgsRectangle rect = mMapCanvas->mapSettings().mapToLayerCoordinates( mLayer, mMapCanvas->extent() );
771+
r.setFilterRect( rect );
772+
mMasterModel->setRequest( r );
773+
mMasterModel->loadLayer();
774+
}
775+
emit filterChanged();
776+
}
777+
674778
void QgsDualView::featureFormAttributeChanged()
675779
{
676780
mFeatureList->setCurrentFeatureEdited( true );
@@ -699,7 +803,7 @@ void QgsDualView::setFeatureSelectionManager( QgsIFeatureSelectionManager* featu
699803

700804
void QgsDualView::setAttributeTableConfig( const QgsAttributeTableConfig& config )
701805
{
702-
mLayerCache->layer()->setAttributeTableConfig( config );
806+
mLayer->setAttributeTableConfig( config );
703807
mFilterModel->setAttributeTableConfig( config );
704808
mTableView->setAttributeTableConfig( config );
705809
mConfig = config;

‎src/gui/attributetable/qgsdualview.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,11 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
8080
* {@link QgsAttributeTableFilterModel::ShowVisible}
8181
* @param request Use a modified request to limit the shown features
8282
* @param context The context in which this view is shown
83+
* @param loadFeatures whether to initially load all features into the view. If set to
84+
* false, limited features can later be loaded using setFilterMode()
8385
*/
84-
void init( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request = QgsFeatureRequest(), const QgsAttributeEditorContext& context = QgsAttributeEditorContext() );
86+
void init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request = QgsFeatureRequest(), const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
87+
bool loadFeatures = true );
8588

8689
/**
8790
* Change the current view mode.
@@ -294,6 +297,10 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
294297

295298
void sortByPreviewExpression();
296299

300+
void updateSelectedFeatures();
301+
302+
void extentChanged();
303+
297304
/**
298305
* Will be called whenever the currently shown feature form changes.
299306
* Will forward this signal to the feature list to visually represent
@@ -318,9 +325,11 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
318325
/** Zooms to the active feature*/
319326
void zoomToCurrentFeature();
320327

328+
void rebuildFullLayerCache();
329+
321330
private:
322-
void initLayerCache( QgsVectorLayer *layer, bool cacheGeometry );
323-
void initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request );
331+
void initLayerCache( bool cacheGeometry );
332+
void initModels( QgsMapCanvas* mapCanvas, const QgsFeatureRequest& request, bool loadFeatures );
324333

325334
QgsAttributeEditorContext mEditorContext;
326335
QgsAttributeTableModel* mMasterModel;
@@ -331,12 +340,14 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
331340
QMenu* mPreviewColumnsMenu;
332341
QMenu* mHorizontalHeaderMenu;
333342
QgsVectorLayerCache* mLayerCache;
343+
QgsVectorLayer *mLayer;
334344
QProgressDialog* mProgressDlg;
335345
QgsIFeatureSelectionManager* mFeatureSelectionManager;
336346
QgsDistanceArea mDistanceArea;
337347
QString mDisplayExpression;
338348
QgsAttributeTableConfig mConfig;
339349
QScrollArea* mAttributeEditorScrollArea;
350+
QgsMapCanvas *mMapCanvas;
340351

341352
friend class TestQgsDualView;
342353
};

‎tests/src/app/testqgsattributetable.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class TestQgsAttributeTable : public QObject
4040
void cleanup() {} // will be called after every testfunction.
4141
void testFieldCalculation();
4242
void testFieldCalculationArea();
43+
void testNoGeom();
4344

4445
private:
4546
QgisApp * mQgisApp;
@@ -59,6 +60,13 @@ void TestQgsAttributeTable::initTestCase()
5960
QgsApplication::init();
6061
QgsApplication::initQgis();
6162
mQgisApp = new QgisApp();
63+
64+
// setup the test QSettings environment
65+
QCoreApplication::setOrganizationName( QString( "QGIS" ) );
66+
QCoreApplication::setOrganizationDomain( QString( "qgis.org" ) );
67+
QCoreApplication::setApplicationName( QString( "QGIS-TEST" ) );
68+
69+
QSettings().setValue( QString( "/qgis/attributeTableBehavior" ), QgsAttributeTableFilterModel::ShowAll );
6270
}
6371

6472
//runs after all tests
@@ -168,5 +176,36 @@ void TestQgsAttributeTable::testFieldCalculationArea()
168176
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
169177
}
170178

179+
void TestQgsAttributeTable::testNoGeom()
180+
{
181+
//test that by default the attribute table DOESN'T fetch geometries (because performance)
182+
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( QString( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QString( "vl" ), QString( "memory" ) ) );
183+
QVERIFY( tempLayer->isValid() );
184+
185+
QSettings().setValue( QString( "/qgis/attributeTableBehaviour" ), QgsAttributeTableFilterModel::ShowAll );
186+
QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );
187+
188+
QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
189+
QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );
190+
191+
// but if we are requesting only visible features, then geometry must be fetched...
192+
193+
QSettings().setValue( QString( "/qgis/attributeTableBehaviour" ), QgsAttributeTableFilterModel::ShowVisible );
194+
dlg.reset( new QgsAttributeTableDialog( tempLayer.data() ) );
195+
QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
196+
QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );
197+
198+
// try changing existing dialog to no geometry mode
199+
dlg->filterShowAll();
200+
QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
201+
QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );
202+
203+
// and back to a geometry mode
204+
dlg->filterVisible();
205+
QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
206+
QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );
207+
208+
}
209+
171210
QTEST_MAIN( TestQgsAttributeTable )
172211
#include "testqgsattributetable.moc"

‎tests/src/core/testqgsvectorlayercache.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class TestVectorLayerCache : public QObject
5454
void testFullCache();
5555
void testFullCacheThroughRequest();
5656
void testCanUseCacheForRequest();
57+
void testCacheGeom();
5758

5859
void onCommittedFeaturesAdded( const QString&, const QgsFeatureList& );
5960

@@ -240,6 +241,15 @@ void TestVectorLayerCache::testFullCache()
240241
{
241242
QVERIFY( cache.isFidCached( f.id() ) );
242243
}
244+
245+
// add a feature to the layer
246+
mPointsLayer->startEditing();
247+
QgsFeature f2( mPointsLayer->fields() );
248+
QVERIFY( mPointsLayer->addFeature( f2 ) );
249+
QVERIFY( cache.hasFullCache() );
250+
QVERIFY( cache.isFidCached( f2.id() ) );
251+
252+
mPointsLayer->rollBack();
243253
}
244254

245255
void TestVectorLayerCache::testFullCacheThroughRequest()
@@ -330,6 +340,58 @@ void TestVectorLayerCache::testCanUseCacheForRequest()
330340
QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) );
331341
}
332342

343+
void TestVectorLayerCache::testCacheGeom()
344+
{
345+
QgsVectorLayerCache cache( mPointsLayer, 2 );
346+
// cache geometry
347+
cache.setCacheGeometry( true );
348+
349+
//first get some feature ids from layer
350+
QgsFeature f;
351+
QgsFeatureIterator it = mPointsLayer->getFeatures();
352+
it.nextFeature( f );
353+
QgsFeatureId id1 = f.id();
354+
it.nextFeature( f );
355+
QgsFeatureId id2 = f.id();
356+
357+
QgsFeatureRequest req;
358+
req.setFlags( QgsFeatureRequest::NoGeometry ); // should be ignored by cache
359+
req.setFilterFids( QgsFeatureIds() << id1 << id2 );
360+
361+
it = cache.getFeatures( req );
362+
while ( it.nextFeature( f ) )
363+
{
364+
QVERIFY( f.constGeometry() );
365+
}
366+
367+
// disabled geometry caching
368+
cache.setCacheGeometry( false );
369+
// we should still have cached features... no need to lose these!
370+
QCOMPARE( cache.cachedFeatureIds(), QgsFeatureIds() << id1 << id2 );
371+
it = cache.getFeatures( req );
372+
while ( it.nextFeature( f ) )
373+
{
374+
QVERIFY( f.constGeometry() );
375+
}
376+
377+
// now upgrade cache from no geometry -> geometry, should be cleared since we
378+
// cannot be confident that features existing in the cache have geometry
379+
cache.setCacheGeometry( true );
380+
QVERIFY( cache.cachedFeatureIds().isEmpty() );
381+
it = cache.getFeatures( req );
382+
while ( it.nextFeature( f ) )
383+
{
384+
QVERIFY( f.constGeometry() );
385+
}
386+
387+
// another test...
388+
cache.setCacheGeometry( false );
389+
cache.setFullCache( true );
390+
QVERIFY( cache.hasFullCache() );
391+
cache.setCacheGeometry( true );
392+
QVERIFY( !cache.hasFullCache() );
393+
}
394+
333395
void TestVectorLayerCache::onCommittedFeaturesAdded( const QString& layerId, const QgsFeatureList& features )
334396
{
335397
Q_UNUSED( layerId )

‎tests/src/gui/testqgsdualview.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class TestQgsDualView : public QObject
5353
void testSort();
5454

5555
void testAttributeFormSharedValueScanning();
56+
void testNoGeom();
5657

5758
private:
5859
QgsMapCanvas* mCanvas;
@@ -266,6 +267,36 @@ void TestQgsDualView::testAttributeFormSharedValueScanning()
266267
QVERIFY( mixedValueFields.isEmpty() );
267268
}
268269

270+
void TestQgsDualView::testNoGeom()
271+
{
272+
//test that both the master model and cache for the dual view either both request geom or both don't request geom
273+
QScopedPointer< QgsDualView > dv( new QgsDualView() );
274+
275+
// request with geometry
276+
QgsFeatureRequest req;
277+
dv->init( mPointsLayer, mCanvas, req );
278+
// check that both master model AND cache are using geometry
279+
QgsAttributeTableModel* model = dv->masterModel();
280+
QVERIFY( model->layerCache()->cacheGeometry() );
281+
QVERIFY( !( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
282+
283+
// request with NO geometry, but using filter rect (which should override and request geom)
284+
req = QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) );
285+
dv.reset( new QgsDualView() );
286+
dv->init( mPointsLayer, mCanvas, req );
287+
model = dv->masterModel();
288+
QVERIFY( model->layerCache()->cacheGeometry() );
289+
QVERIFY( !( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
290+
291+
// request with NO geometry
292+
req = QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry );
293+
dv.reset( new QgsDualView() );
294+
dv->init( mPointsLayer, mCanvas, req );
295+
model = dv->masterModel();
296+
QVERIFY( !model->layerCache()->cacheGeometry() );
297+
QVERIFY(( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
298+
}
299+
269300
QTEST_MAIN( TestQgsDualView )
270301
#include "testqgsdualview.moc"
271302

0 commit comments

Comments
 (0)
Please sign in to comment.