QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsdualview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdualview.cpp
3 --------------------------------------
4 Date : 10.2.2013
5 Copyright : (C) 2013 Matthias Kuhn
6 Email : matthias at opengis dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QClipboard>
17#include <QDialog>
18#include <QMenu>
19#include <QMessageBox>
20#include <QProgressDialog>
21#include <QGroupBox>
22#include <QInputDialog>
23#include <QTimer>
24#include <QShortcut>
25
26#include "qgsapplication.h"
27#include "qgsactionmanager.h"
29#include "qgsdualview.h"
31#include "qgsfeaturelistmodel.h"
33#include "qgsmapcanvas.h"
34#include "qgsmaplayeraction.h"
36#include "qgsvectorlayercache.h"
39#include "qgssettings.h"
40#include "qgsscrollarea.h"
41#include "qgsgui.h"
43#include "qgsshortcutsmanager.h"
45#include "qgsmapcanvasutils.h"
46#include "qgsmessagebar.h"
48#include "qgsactionmenu.h"
49
50
51QgsDualView::QgsDualView( QWidget *parent )
52 : QStackedWidget( parent )
53{
54 setupUi( this );
55 connect( mFeatureListView, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::featureListAboutToChangeEditSelection );
56 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::featureListCurrentEditSelectionChanged );
57 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionProgressChanged, this, &QgsDualView::updateEditSelectionProgress );
58 connect( mFeatureListView, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
59
60 connect( mTableView, &QgsAttributeTableView::willShowContextMenu, this, &QgsDualView::viewWillShowContextMenu );
61 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
62 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &QgsDualView::showViewHeaderMenu );
63 connect( mTableView, &QgsAttributeTableView::columnResized, this, &QgsDualView::tableColumnResized );
64
65 mConditionalFormatWidgetStack->hide();
66 mConditionalFormatWidget = new QgsFieldConditionalFormatWidget( this );
67 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
68 mConditionalFormatWidget->setDockMode( true );
69
70 const QgsSettings settings;
71 mConditionalSplitter->restoreState( settings.value( QStringLiteral( "/qgis/attributeTable/splitterState" ), QByteArray() ).toByteArray() );
72
73 mPreviewColumnsMenu = new QMenu( this );
74 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
75
76 // Set preview icon
77 mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionPreview.svg" ) ) );
78
79 // Connect layer list preview signals
80 connect( mActionExpressionPreview, &QAction::triggered, this, &QgsDualView::previewExpressionBuilder );
81 connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
82
83 // browsing toolbar
84 connect( mNextFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editNextFeature );
85 connect( mPreviousFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editPreviousFeature );
86 connect( mFirstFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editFirstFeature );
87 connect( mLastFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editLastFeature );
88
89 auto createShortcuts = [ = ]( const QString & objectName, void ( QgsFeatureListView::* slot )() )
90 {
91 QShortcut *sc = QgsGui::shortcutsManager()->shortcutByName( objectName );
92 // do not assert for sc as it would lead to crashes in testing
93 // or when using custom widgets lib if built with Debug
94 if ( sc )
95 connect( sc, &QShortcut::activated, mFeatureListView, slot );
96 };
97 createShortcuts( QStringLiteral( "mAttributeTableFirstEditedFeature" ), &QgsFeatureListView::editFirstFeature );
98 createShortcuts( QStringLiteral( "mAttributeTablePreviousEditedFeature" ), &QgsFeatureListView::editPreviousFeature );
99 createShortcuts( QStringLiteral( "mAttributeTableNextEditedFeature" ), &QgsFeatureListView::editNextFeature );
100 createShortcuts( QStringLiteral( "mAttributeTableLastEditedFeature" ), &QgsFeatureListView::editLastFeature );
101
102 QButtonGroup *buttonGroup = new QButtonGroup( this );
103 buttonGroup->setExclusive( false );
104 buttonGroup->addButton( mAutoPanButton, PanToFeature );
105 buttonGroup->addButton( mAutoZoomButton, ZoomToFeature );
106 const FeatureListBrowsingAction action = QgsSettings().enumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
107 QAbstractButton *bt = buttonGroup->button( static_cast<int>( action ) );
108 if ( bt )
109 bt->setChecked( true );
110 connect( buttonGroup, qOverload< QAbstractButton *, bool >( &QButtonGroup::buttonToggled ), this, &QgsDualView::panZoomGroupButtonToggled );
111 mFlashButton->setChecked( QgsSettings().value( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), true ).toBool() );
112 connect( mFlashButton, &QToolButton::clicked, this, &QgsDualView::flashButtonClicked );
113}
114
116{
117 QgsSettings settings;
118 settings.setValue( QStringLiteral( "/qgis/attributeTable/splitterState" ), mConditionalSplitter->saveState() );
119}
120
121void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request,
122 const QgsAttributeEditorContext &context, bool loadFeatures, bool showFirstFeature )
123{
124 if ( !layer )
125 return;
126
127 delete mAttributeForm;
128 mAttributeForm = nullptr;
129
130 mLayer = layer;
131
132 // Keep fields order in sync: force config reset
133 connect( mLayer, &QgsVectorLayer::updatedFields, this, [ = ]
134 {
135 mFilterModel->setAttributeTableConfig( attributeTableConfig(), /* force */ true );
136 } );
137
138 mEditorContext = context;
139
140 // create an empty form to find out if it needs geometry or not
141 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
142
143 const bool needsGeometry = mLayer->conditionalStyles()->rulesNeedGeometry() ||
146 || emptyForm.needsGeometry();
147
148 initLayerCache( needsGeometry );
149 initModels( mapCanvas, request, loadFeatures );
150
151 mConditionalFormatWidget->setLayer( mLayer );
152
153 mTableView->setModel( mFilterModel );
154 mFeatureListView->setModel( mFeatureListModel );
155
156 connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged );
157
158 if ( mFeatureListPreviewButton->defaultAction() )
159 mFeatureListView->setDisplayExpression( mDisplayExpression );
160 else
161 columnBoxInit();
162
163 // This slows down load of the attribute table heaps and uses loads of memory.
164 //mTableView->resizeColumnsToContents();
165
166 if ( showFirstFeature && mFeatureListModel->rowCount( ) > 0 )
167 mFeatureListView->setEditSelection( QgsFeatureIds() << mFeatureListModel->data( mFeatureListModel->index( 0, 0 ), QgsFeatureListModel::Role::FeatureRole ).value<QgsFeature>().id() );
168}
169
170void QgsDualView::initAttributeForm( const QgsFeature &feature )
171{
172 Q_ASSERT( !mAttributeForm );
173
174 mAttributeForm = new QgsAttributeForm( mLayer, feature, mEditorContext );
175 if ( !mEditorContext.parentContext() )
176 {
177 mAttributeEditorScrollArea = new QgsScrollArea();
178 mAttributeEditorScrollArea->setWidgetResizable( true );
179 mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
180 mAttributeEditorScrollArea->setWidget( mAttributeForm );
181 }
182 else
183 {
184 mAttributeEditor->layout()->addWidget( mAttributeForm );
185 }
186
187 // This is an arbitrary yet small value to fix issue GH #50181
188 // the default value is 0.
189 mAttributeForm->setMinimumWidth( 200 );
190
191 setAttributeTableConfig( mLayer->attributeTableConfig() );
192
193 connect( mAttributeForm, &QgsAttributeForm::widgetValueChanged, this, &QgsDualView::featureFormAttributeChanged );
194 connect( mAttributeForm, &QgsAttributeForm::modeChanged, this, &QgsDualView::formModeChanged );
196 connect( mAttributeForm, &QgsAttributeForm::flashFeatures, this, [ = ]( const QString & filter )
197 {
198 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
199 {
200 QgsMapCanvasUtils::flashMatchingFeatures( canvas, mLayer, filter );
201 }
202 } );
203 connect( mAttributeForm, &QgsAttributeForm::zoomToFeatures, this, [ = ]( const QString & filter )
204 {
205 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
206 {
207 QgsMapCanvasUtils::zoomToMatchingFeatures( canvas, mLayer, filter );
208 }
209 } );
210
211 connect( mMasterModel, &QgsAttributeTableModel::modelChanged, mAttributeForm, &QgsAttributeForm::refreshFeature );
212}
213
214void QgsDualView::columnBoxInit()
215{
216 // load fields
217 const QList<QgsField> fields = mLayer->fields().toList();
218
219 const QString defaultField;
220
221 mFeatureListPreviewButton->addAction( mActionExpressionPreview );
222 mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
223
224 const auto constFields = fields;
225 for ( const QgsField &field : constFields )
226 {
227 const int fieldIndex = mLayer->fields().lookupField( field.name() );
228 if ( fieldIndex == -1 )
229 continue;
230
231 const QString fieldName = field.name();
232 if ( QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldName ).type() != QLatin1String( "Hidden" ) )
233 {
234 const QIcon icon = mLayer->fields().iconForField( fieldIndex );
235 const QString text = mLayer->attributeDisplayName( fieldIndex );
236
237 // Generate action for the preview popup button of the feature list
238 QAction *previewAction = new QAction( icon, text, mFeatureListPreviewButton );
239 connect( previewAction, &QAction::triggered, this, [ = ] { previewColumnChanged( previewAction, fieldName ); } );
240 mPreviewColumnsMenu->addAction( previewAction );
241
242 if ( text == defaultField )
243 {
244 mFeatureListPreviewButton->setDefaultAction( previewAction );
245 }
246 }
247 }
248
249 QMenu *sortMenu = new QMenu( this );
250 QAction *sortMenuAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "Sort…" ), this );
251 sortMenuAction->setMenu( sortMenu );
252
253 QAction *sortByPreviewExpressionAsc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "By Preview Expression (ascending)" ), this );
254 connect( sortByPreviewExpressionAsc, &QAction::triggered, this, [ = ]()
255 {
256 mFeatureListModel->setSortByDisplayExpression( true, Qt::AscendingOrder );
257 } );
258 sortMenu->addAction( sortByPreviewExpressionAsc );
259 QAction *sortByPreviewExpressionDesc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort-reverse.svg" ) ), tr( "By Preview Expression (descending)" ), this );
260 connect( sortByPreviewExpressionDesc, &QAction::triggered, this, [ = ]()
261 {
262 mFeatureListModel->setSortByDisplayExpression( true, Qt::DescendingOrder );
263 } );
264 sortMenu->addAction( sortByPreviewExpressionDesc );
265 QAction *sortByPreviewExpressionCustom = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "mIconExpressionPreview.svg" ) ), tr( "By Custom Expression" ), this );
266 connect( sortByPreviewExpressionCustom, &QAction::triggered, this, [ = ]()
267 {
268 if ( modifySort() )
269 mFeatureListModel->setSortByDisplayExpression( false );
270 } );
271 sortMenu->addAction( sortByPreviewExpressionCustom );
272
273 mFeatureListPreviewButton->addAction( sortMenuAction );
274
275 QAction *separator = new QAction( mFeatureListPreviewButton );
276 separator->setSeparator( true );
277 mFeatureListPreviewButton->addAction( separator );
278 restoreRecentDisplayExpressions();
279
280 // If there is no single field found as preview
281 if ( !mFeatureListPreviewButton->defaultAction() )
282 {
283 mFeatureListView->setDisplayExpression( mLayer->displayExpression() );
284 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
285 const QString displayExpression = mFeatureListView->displayExpression();
286 setDisplayExpression( displayExpression.isEmpty() ? tr( "'[Please define preview text]'" ) : displayExpression );
287 }
288 else
289 {
290 mFeatureListPreviewButton->defaultAction()->trigger();
291 }
292}
293
295{
296 setCurrentIndex( view );
297}
298
300{
301 return static_cast< QgsDualView::ViewMode >( currentIndex() );
302}
303
305{
306 // cleanup any existing connections
307 switch ( mFilterModel->filterMode() )
308 {
310 disconnect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
312 break;
313
317 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
318 break;
319
322 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
323 disconnect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
324 break;
325
327 disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
328 break;
329
331 mMasterModel->setShowValidityState( false );
332 break;
333 }
334
335 QgsFeatureRequest request = mMasterModel->request();
336
337 // create an empty form to find out if it needs geometry or not
338 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
339 const bool needsGeometry = ( filterMode == QgsAttributeTableFilterModel::ShowVisible ) || emptyForm.needsGeometry();
340
341 const bool requiresTableReload = ( request.filterType() != Qgis::Qgis::FeatureRequestFilterType::NoFilter || request.spatialFilterType() != Qgis::SpatialFilterType::NoFilter ) // previous request was subset
342 || ( needsGeometry && request.flags() & Qgis::FeatureRequestFlag::NoGeometry ) // no geometry for last request
343 || ( mMasterModel->rowCount() == 0 ); // no features
344
345 if ( !needsGeometry )
347 else
348 request.setFlags( request.flags() & ~( static_cast< int >( Qgis::FeatureRequestFlag::NoGeometry ) ) );
349 request.setFilterFids( QgsFeatureIds() );
350 request.setFilterRect( QgsRectangle() );
351 request.disableFilter();
352
353 // setup new connections and filter request parameters
354 switch ( filterMode )
355 {
357 connect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
358 if ( mFilterModel->mapCanvas() )
359 {
360 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
361 request.setFilterRect( rect );
362 }
364 break;
365
367 request.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
369 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
370 connect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
371 break;
372
374 {
375 mMasterModel->setShowValidityState( true );
377 filterFeatures( QStringLiteral( "is_feature_valid() = false" ), context );
379 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
380 break;
381 }
382
385 {
386 const QString filterExpression = filterMode == QgsAttributeTableFilterModel::ShowFilteredList ? mFilterModel->filterExpression() : QString();
387 if ( !filterExpression.isEmpty() )
388 request.setFilterExpression( mFilterModel->filterExpression() );
390 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
391 break;
392 }
393
395 connect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
396 request.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
397 break;
398 }
399
400 // disable the browsing auto pan/scale if the list only shows visible items
401 switch ( filterMode )
402 {
404 setBrowsingAutoPanScaleAllowed( false );
405 break;
406
412 setBrowsingAutoPanScaleAllowed( true );
413 break;
414 }
415
416 if ( requiresTableReload )
417 {
418 //disconnect the connections of the current (old) filtermode before reload
419 mFilterModel->disconnectFilterModeConnections();
420
421 mMasterModel->setRequest( request );
422 whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
423 mMasterModel->loadLayer();
424 }
425
426 //update filter model
427 mFilterModel->setFilterMode( filterMode );
428 emit filterChanged();
429}
430
431void QgsDualView::setSelectedOnTop( bool selectedOnTop )
432{
433 mFilterModel->setSelectedOnTop( selectedOnTop );
434}
435
436void QgsDualView::initLayerCache( bool cacheGeometry )
437{
438 // Initialize the cache
439 const QgsSettings settings;
440 const int cacheSize = settings.value( QStringLiteral( "qgis/attributeTableRowCache" ), "10000" ).toInt();
441 mLayerCache = new QgsVectorLayerCache( mLayer, cacheSize, this );
442 mLayerCache->setCacheSubsetOfAttributes( requiredAttributes( mLayer ) );
443 mLayerCache->setCacheGeometry( cacheGeometry );
444 if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayer->dataProvider()->capabilities() ) )
445 {
446 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsDualView::rebuildFullLayerCache );
447 rebuildFullLayerCache();
448 }
449}
450
451void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, bool loadFeatures )
452{
453 delete mFeatureListModel;
454 delete mFilterModel;
455 delete mMasterModel;
456
457 mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
458 mMasterModel->setRequest( request );
459 mMasterModel->setEditorContext( mEditorContext );
460 mMasterModel->setExtraColumns( 1 ); // Add one extra column which we can "abuse" as an action column
461
462 connect( mMasterModel, &QgsAttributeTableModel::progress, this, &QgsDualView::progress );
463 connect( mMasterModel, &QgsAttributeTableModel::finished, this, &QgsDualView::finished );
464
466
467 if ( loadFeatures )
468 mMasterModel->loadLayer();
469
470 mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
471
472 // The following connections to invalidate() are necessary to keep the filter model in sync
473 // see regression https://github.com/qgis/QGIS/issues/23890
474 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
475 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
476
478
479 mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
480}
481
482void QgsDualView::restoreRecentDisplayExpressions()
483{
484 const QVariantList previewExpressions = mLayer->customProperty( QStringLiteral( "dualview/previewExpressions" ) ).toList();
485
486 for ( const QVariant &previewExpression : previewExpressions )
487 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
488}
489
490void QgsDualView::saveRecentDisplayExpressions() const
491{
492 if ( ! mLayer )
493 {
494 return;
495 }
496 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
497
498 // Remove existing same action
499 int index = actions.indexOf( mLastDisplayExpressionAction );
500 if ( index != -1 )
501 {
502 QVariantList previewExpressions;
503 for ( ; index < actions.length(); ++index )
504 {
505 QAction *action = actions.at( index );
506 previewExpressions << action->property( "previewExpression" );
507 }
508
509 mLayer->setCustomProperty( QStringLiteral( "dualview/previewExpressions" ), previewExpressions );
510 }
511}
512
513void QgsDualView::setDisplayExpression( const QString &expression )
514{
515 mDisplayExpression = expression;
516 insertRecentlyUsedDisplayExpression( expression );
517}
518
519void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression )
520{
521 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
522
523 // Remove existing same action
524 const int index = actions.indexOf( mLastDisplayExpressionAction );
525 if ( index != -1 )
526 {
527 for ( int i = 0; index + i < actions.length(); ++i )
528 {
529 QAction *action = actions.at( index );
530 if ( action->text() == expression || i >= 9 )
531 {
532 if ( action == mLastDisplayExpressionAction )
533 mLastDisplayExpressionAction = nullptr;
534 mFeatureListPreviewButton->removeAction( action );
535 }
536 else
537 {
538 if ( !mLastDisplayExpressionAction )
539 mLastDisplayExpressionAction = action;
540 }
541 }
542 }
543
544 QString name = expression;
545 QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionPreview.svg" ) );
546 if ( expression.startsWith( QLatin1String( "COALESCE( \"" ) ) && expression.endsWith( QLatin1String( ", '<NULL>' )" ) ) )
547 {
548 name = expression.mid( 11, expression.length() - 24 ); // Numbers calculated from the COALESCE / <NULL> parts
549
550 const int fieldIndex = mLayer->fields().indexOf( name );
551 if ( fieldIndex != -1 )
552 {
553 name = mLayer->attributeDisplayName( fieldIndex );
554 icon = mLayer->fields().iconForField( fieldIndex );
555 }
556 else
557 {
558 name = expression;
559 }
560 }
561
562 QAction *previewAction = new QAction( icon, name, mFeatureListPreviewButton );
563 previewAction->setProperty( "previewExpression", expression );
564 connect( previewAction, &QAction::triggered, this, [expression, this]( bool )
565 {
566 setDisplayExpression( expression );
567 mFeatureListPreviewButton->setText( expression );
568 }
569 );
570
571 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
572 mLastDisplayExpressionAction = previewAction;
573}
574
575void QgsDualView::updateEditSelectionProgress( int progress, int count )
576{
577 mProgressCount->setText( QStringLiteral( "%1 / %2" ).arg( progress + 1 ).arg( count ) );
578 mPreviousFeatureButton->setEnabled( progress > 0 );
579 mNextFeatureButton->setEnabled( progress + 1 < count );
580 mFirstFeatureButton->setEnabled( progress > 0 );
581 mLastFeatureButton->setEnabled( progress + 1 < count );
582 if ( mAttributeForm )
583 {
584 mAttributeForm->setVisible( count > 0 );
585 }
586}
587
588void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
589{
590 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
591 if ( canvas && view() == AttributeEditor && featureset != mLastFeatureSet )
592 {
593 if ( mBrowsingAutoPanScaleAllowed )
594 {
595 if ( mAutoPanButton->isChecked() )
596 QTimer::singleShot( 0, this, [ = ]()
597 {
598 canvas->panToFeatureIds( mLayer, featureset, false );
599 } );
600 else if ( mAutoZoomButton->isChecked() )
601 QTimer::singleShot( 0, this, [ = ]()
602 {
603 canvas->zoomToFeatureIds( mLayer, featureset );
604 } );
605 }
606 if ( mFlashButton->isChecked() )
607 QTimer::singleShot( 0, this, [ = ]()
608 {
609 canvas->flashFeatureIds( mLayer, featureset );
610 } );
611 mLastFeatureSet = featureset;
612 }
613}
614
615void QgsDualView::setBrowsingAutoPanScaleAllowed( bool allowed )
616{
617 if ( mBrowsingAutoPanScaleAllowed == allowed )
618 return;
619
620 mBrowsingAutoPanScaleAllowed = allowed;
621
622 mAutoPanButton->setEnabled( allowed );
623 mAutoZoomButton->setEnabled( allowed );
624
625 const QString disabledHint = tr( "(disabled when attribute table only shows features visible in the current map canvas extent)" );
626
627 mAutoPanButton->setToolTip( tr( "Automatically pan to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
628 mAutoZoomButton->setToolTip( tr( "Automatically zoom to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
629}
630
631void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
632{
633 if ( button == mAutoPanButton && checked )
634 {
635 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
636 mAutoZoomButton->setChecked( false );
637 }
638 else if ( button == mAutoZoomButton && checked )
639 {
640 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
641 mAutoPanButton->setChecked( false );
642 }
643 else
644 {
645 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
646 }
647
648 if ( checked && mLayer->isSpatial() )
649 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
650}
651
652void QgsDualView::flashButtonClicked( bool clicked )
653{
654 QgsSettings().setValue( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), clicked );
655 if ( !clicked )
656 return;
657
658 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
659
660 if ( canvas )
661 canvas->flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
662}
663
664void QgsDualView::filterError( const QString &errorMessage )
665{
666 if ( mEditorContext.mainMessageBar() )
667 {
668 mEditorContext.mainMessageBar()->pushWarning( tr( "An error occurred while filtering features" ), errorMessage );
669 }
670}
671
672void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
673{
674 if ( !mAttributeForm )
675 return;
676
677 if ( mLayer->isEditable() && !mAttributeForm->save() )
678 ok = false;
679}
680
681void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
682{
683 if ( !mAttributeForm )
684 {
685 initAttributeForm( feat );
686 }
687 else if ( !mLayer->isEditable() || mAttributeForm->save() )
688 {
689 mAttributeForm->setFeature( feat );
690 QgsFeatureIds featureset;
691 featureset << feat.id();
692 setCurrentEditSelection( featureset );
693
694 if ( mLayer->isSpatial() )
695 panOrZoomToFeature( featureset );
696
697 }
698 else
699 {
700 // Couldn't save feature
701 }
702}
703
705{
706 mFeatureListView->setCurrentFeatureEdited( false );
707 mFeatureListView->setEditSelection( fids );
708}
709
711{
712 return mAttributeForm ? mAttributeForm->save() : false;
713}
714
716{
717 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
718}
719
721{
722 if ( !mAttributeForm )
723 return;
724
725 if ( enabled )
726 {
727 mPreviousView = view();
729 }
730 else
731 {
732 setView( mPreviousView );
733 }
734
736}
737
739{
740 if ( !mAttributeForm )
741 return;
742
743 if ( enabled )
744 {
747 mAttributeForm->setVisible( true );
748 }
749 else
750 {
752 mAttributeForm->setVisible( mFilterModel->rowCount( ) > 0 );
753 }
754
755}
756
757void QgsDualView::previewExpressionBuilder()
758{
759 // Show expression builder
761
762 QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
763 dlg.setWindowTitle( tr( "Expression Based Preview" ) );
764 dlg.setExpressionText( mFeatureListView->displayExpression() );
765
766 if ( dlg.exec() == QDialog::Accepted )
767 {
768 mFeatureListView->setDisplayExpression( dlg.expressionText() );
769 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
770 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
771 }
772
773 setDisplayExpression( mFeatureListView->displayExpression() );
774}
775
776void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
777{
778 if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
779 {
780 QMessageBox::warning( this,
781 tr( "Column Preview" ),
782 tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
783 .arg( previewAction->text(), mFeatureListView->parserErrorString() )
784 );
785 }
786 else
787 {
788 mFeatureListPreviewButton->setText( previewAction->text() );
789 mFeatureListPreviewButton->setIcon( previewAction->icon() );
790 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
791 }
792
793 setDisplayExpression( mFeatureListView->displayExpression() );
794}
795
797{
798 return mMasterModel->rowCount();
799}
800
802{
803 return mFilterModel->rowCount();
804}
805
807{
808 const QModelIndex currentIndex = mTableView->currentIndex();
809 if ( !currentIndex.isValid() )
810 {
811 return;
812 }
813
814 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
815 QApplication::clipboard()->setText( var.toString() );
816}
817
819{
820 if ( mProgressDlg )
821 mProgressDlg->cancel();
822}
823
824void QgsDualView::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
825{
826 if ( mAttributeForm )
827 {
828 mAttributeForm->parentFormValueChanged( attribute, newValue );
829 }
830}
831
832void QgsDualView::hideEvent( QHideEvent *event )
833{
834 Q_UNUSED( event )
835 saveRecentDisplayExpressions();
836}
837
838void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &masterIndex )
839{
840 if ( !menu )
841 {
842 return;
843 }
844
845 QAction *copyContentAction = menu->addAction( tr( "Copy Cell Content" ) );
846 menu->addAction( copyContentAction );
847 connect( copyContentAction, &QAction::triggered, this, [masterIndex, this]
848 {
849 const QVariant var = mMasterModel->data( masterIndex, Qt::DisplayRole );
850 QApplication::clipboard()->setText( var.toString() );
851 } );
852
853 QgsVectorLayer *vl = mFilterModel->layer();
854 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
855 if ( canvas && vl && vl->isSpatial() )
856 {
857 QAction *zoomToFeatureAction = menu->addAction( tr( "Zoom to Feature" ) );
858 connect( zoomToFeatureAction, &QAction::triggered, this, &QgsDualView::zoomToCurrentFeature );
859
860 QAction *panToFeatureAction = menu->addAction( tr( "Pan to Feature" ) );
861 connect( panToFeatureAction, &QAction::triggered, this, &QgsDualView::panToCurrentFeature );
862
863 QAction *flashFeatureAction = menu->addAction( tr( "Flash Feature" ) );
864 connect( flashFeatureAction, &QAction::triggered, this, &QgsDualView::flashCurrentFeature );
865 }
866
867 //add user-defined actions to context menu
868 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Field" ) );
869 if ( !actions.isEmpty() )
870 {
871 QAction *a = menu->addAction( tr( "Run Layer Action" ) );
872 a->setEnabled( false );
873
874 for ( const QgsAction &action : actions )
875 {
876 if ( !action.runable() )
877 continue;
878
879 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
880 continue;
881
882 QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, action.id(), masterIndex );
883 menu->addAction( action.name(), a, &QgsAttributeTableAction::execute );
884 }
885 }
886 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
887 if ( ! rowSourceIndex.isValid() )
888 {
889 return;
890 }
891
892 //add actions from QgsMapLayerActionRegistry to context menu
894 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::Layer | Qgis::MapLayerActionTarget::SingleFeature, context );
895 if ( !registeredActions.isEmpty() )
896 {
897 //add a separator between user defined and standard actions
898 menu->addSeparator();
899
900 for ( QgsMapLayerAction *action : registeredActions )
901 {
902 QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction( action->text(), this, action, rowSourceIndex );
903 menu->addAction( action->text(), a, &QgsAttributeTableMapLayerAction::execute );
904 }
905 }
906
907 // entries for multiple features layer actions
908 // only show if the context menu is shown over a selected row
909 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
910 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
911 {
912 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::MultipleFeatures, context );
913 if ( !registeredActions.isEmpty() )
914 {
915 menu->addSeparator();
916 QAction *action = menu->addAction( tr( "Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
917 action->setEnabled( false );
918
920 for ( QgsMapLayerAction *action : registeredActions )
921 {
922 menu->addAction( action->text(), action, [ = ]()
923 {
924 Q_NOWARN_DEPRECATED_PUSH
925 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
926 Q_NOWARN_DEPRECATED_POP
927 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
928 } );
929 }
930 }
931 }
932
933 menu->addSeparator();
934 QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open Form" ), this, QUuid(), rowSourceIndex );
935 menu->addAction( tr( "Open Form…" ), a, &QgsAttributeTableAction::featureForm );
936}
937
938
939void QgsDualView::widgetWillShowContextMenu( QgsActionMenu *menu, const QModelIndex &atIndex )
940{
941 emit showContextMenuExternally( menu, mFilterModel->rowToId( atIndex ) );
942}
943
944
945void QgsDualView::showViewHeaderMenu( QPoint point )
946{
947 const int col = mTableView->columnAt( point.x() );
948
949 delete mHorizontalHeaderMenu;
950 mHorizontalHeaderMenu = new QMenu( this );
951
952 QAction *hide = new QAction( tr( "&Hide Column" ), mHorizontalHeaderMenu );
953 connect( hide, &QAction::triggered, this, &QgsDualView::hideColumn );
954 hide->setData( col );
955 mHorizontalHeaderMenu->addAction( hide );
956 QAction *setWidth = new QAction( tr( "&Set Width…" ), mHorizontalHeaderMenu );
957 connect( setWidth, &QAction::triggered, this, &QgsDualView::resizeColumn );
958 setWidth->setData( col );
959 mHorizontalHeaderMenu->addAction( setWidth );
960
961 QAction *setWidthAllColumns = new QAction( tr( "&Set All Column Widths…" ), mHorizontalHeaderMenu );
962 connect( setWidthAllColumns, &QAction::triggered, this, &QgsDualView::resizeAllColumns );
963 setWidthAllColumns->setData( col );
964 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
965
966 QAction *optimizeWidth = new QAction( tr( "&Autosize" ), mHorizontalHeaderMenu );
967 connect( optimizeWidth, &QAction::triggered, this, &QgsDualView::autosizeColumn );
968 optimizeWidth->setData( col );
969 mHorizontalHeaderMenu->addAction( optimizeWidth );
970
971 QAction *optimizeWidthAllColumns = new QAction( tr( "&Autosize All Columns" ), mHorizontalHeaderMenu );
972 connect( optimizeWidthAllColumns, &QAction::triggered, this, &QgsDualView::autosizeAllColumns );
973 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
974
975
976 mHorizontalHeaderMenu->addSeparator();
977 QAction *organize = new QAction( tr( "&Organize Columns…" ), mHorizontalHeaderMenu );
978 connect( organize, &QAction::triggered, this, &QgsDualView::organizeColumns );
979 mHorizontalHeaderMenu->addAction( organize );
980 QAction *sort = new QAction( tr( "&Sort…" ), mHorizontalHeaderMenu );
981 connect( sort, &QAction::triggered, this, [ = ]() {modifySort();} );
982 mHorizontalHeaderMenu->addAction( sort );
983
984 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
985}
986
987void QgsDualView::organizeColumns()
988{
989 if ( !mLayer )
990 {
991 return;
992 }
993
994 QgsOrganizeTableColumnsDialog dialog( mLayer, attributeTableConfig(), this );
995 if ( dialog.exec() == QDialog::Accepted )
996 {
997 const QgsAttributeTableConfig config = dialog.config();
998 setAttributeTableConfig( config );
999 }
1000}
1001
1002void QgsDualView::tableColumnResized( int column, int width )
1003{
1004 QgsAttributeTableConfig config = mConfig;
1005 const int sourceCol = config.mapVisibleColumnToIndex( column );
1006 if ( sourceCol >= 0 && config.columnWidth( sourceCol ) != width )
1007 {
1008 config.setColumnWidth( sourceCol, width );
1009 setAttributeTableConfig( config );
1010 }
1011}
1012
1013void QgsDualView::hideColumn()
1014{
1015 QAction *action = qobject_cast<QAction *>( sender() );
1016 const int col = action->data().toInt();
1017 QgsAttributeTableConfig config = mConfig;
1018 const int sourceCol = mConfig.mapVisibleColumnToIndex( col );
1019 if ( sourceCol >= 0 )
1020 {
1021 config.setColumnHidden( sourceCol, true );
1022 setAttributeTableConfig( config );
1023 }
1024}
1025
1026void QgsDualView::resizeColumn()
1027{
1028 QAction *action = qobject_cast<QAction *>( sender() );
1029 const int col = action->data().toInt();
1030 if ( col < 0 )
1031 return;
1032
1033 QgsAttributeTableConfig config = mConfig;
1034 const int sourceCol = config.mapVisibleColumnToIndex( col );
1035 if ( sourceCol >= 0 )
1036 {
1037 bool ok = false;
1038 const int width = QInputDialog::getInt( this, tr( "Set column width" ), tr( "Enter column width" ),
1039 mTableView->columnWidth( col ),
1040 0, 1000, 10, &ok );
1041 if ( ok )
1042 {
1043 config.setColumnWidth( sourceCol, width );
1044 setAttributeTableConfig( config );
1045 }
1046 }
1047}
1048
1049void QgsDualView::resizeAllColumns()
1050{
1051 QAction *action = qobject_cast<QAction *>( sender() );
1052 const int col = action->data().toInt();
1053 if ( col < 0 )
1054 return;
1055
1056 QgsAttributeTableConfig config = mConfig;
1057
1058 bool ok = false;
1059 const int width = QInputDialog::getInt( this, tr( "Set Column Width" ), tr( "Enter column width" ),
1060 mTableView->columnWidth( col ),
1061 1, 1000, 10, &ok );
1062 if ( ok )
1063 {
1064 const int colCount = mTableView->model()->columnCount();
1065 if ( colCount > 0 )
1066 {
1067 for ( int i = 0; i < colCount; i++ )
1068 {
1069 config.setColumnWidth( i, width );
1070 }
1071 setAttributeTableConfig( config );
1072 }
1073 }
1074}
1075
1076void QgsDualView::autosizeColumn()
1077{
1078 QAction *action = qobject_cast<QAction *>( sender() );
1079 const int col = action->data().toInt();
1080 mTableView->resizeColumnToContents( col );
1081}
1082
1083void QgsDualView::autosizeAllColumns()
1084{
1085 mTableView->resizeColumnsToContents();
1086}
1087
1088bool QgsDualView::modifySort()
1089{
1090 if ( !mLayer )
1091 return false;
1092
1093 QgsAttributeTableConfig config = mConfig;
1094
1095 QDialog orderByDlg;
1096 orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) );
1097 QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1098 QGridLayout *layout = new QGridLayout();
1099 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1100 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1101 orderByDlg.setLayout( layout );
1102
1103 QGroupBox *sortingGroupBox = new QGroupBox();
1104 sortingGroupBox->setTitle( tr( "Defined sort order in attribute table" ) );
1105 sortingGroupBox->setCheckable( true );
1106 sortingGroupBox->setChecked( !sortExpression().isEmpty() );
1107 layout->addWidget( sortingGroupBox );
1108 sortingGroupBox->setLayout( new QGridLayout() );
1109
1110 QgsExpressionBuilderWidget *expressionBuilder = new QgsExpressionBuilderWidget();
1112
1113 expressionBuilder->initWithLayer( mLayer, context, QStringLiteral( "generic" ) );
1114 expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
1115
1116 sortingGroupBox->layout()->addWidget( expressionBuilder );
1117
1118 QCheckBox *cbxSortAscending = new QCheckBox( tr( "Sort ascending" ) );
1119 cbxSortAscending->setChecked( config.sortOrder() == Qt::AscendingOrder );
1120 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1121
1122 layout->addWidget( dialogButtonBox );
1123 if ( orderByDlg.exec() )
1124 {
1125 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1126 if ( sortingGroupBox->isChecked() )
1127 {
1128 setSortExpression( expressionBuilder->expressionText(), sortOrder );
1129 config.setSortExpression( expressionBuilder->expressionText() );
1130 config.setSortOrder( sortOrder );
1131 }
1132 else
1133 {
1134 setSortExpression( QString(), sortOrder );
1135 config.setSortExpression( QString() );
1136 }
1137
1138 setAttributeTableConfig( config );
1139 return true;
1140 }
1141 else
1142 {
1143 return false;
1144 }
1145
1146}
1147
1149{
1150 QSet<int> attributes;
1151
1152 const QgsAttributeTableConfig config { layer->attributeTableConfig() };
1153
1154 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.columns() };
1155 for ( const QgsAttributeTableConfig::ColumnConfig &columnConfig : std::as_const( constColumnconfigs ) )
1156 {
1157 if ( columnConfig.type == QgsAttributeTableConfig::Type::Field && ! columnConfig.hidden )
1158 {
1159 attributes.insert( layer->fields().lookupField( columnConfig.name ) );
1160 }
1161 }
1162
1163 const QSet<int> colAttrs { attributes };
1164 for ( const int attrIdx : std::as_const( colAttrs ) )
1165 {
1166 if ( layer->fields().fieldOrigin( attrIdx ) == QgsFields::FieldOrigin::OriginExpression )
1167 {
1168 attributes += QgsExpression( layer->expressionField( attrIdx ) ).referencedAttributeIndexes( layer->fields() );
1169 }
1170 }
1171
1172 QgsAttributeList attrs { attributes.values() };
1173 std::sort( attrs.begin(), attrs.end() );
1174 return attrs;
1175}
1176
1177void QgsDualView::zoomToCurrentFeature()
1178{
1179 const QModelIndex currentIndex = mTableView->currentIndex();
1180 if ( !currentIndex.isValid() )
1181 {
1182 return;
1183 }
1184
1185 QgsFeatureIds ids;
1186 ids.insert( mFilterModel->rowToId( currentIndex ) );
1187 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1188 if ( canvas )
1189 {
1190 canvas->zoomToFeatureIds( mLayer, ids );
1191 }
1192}
1193
1194void QgsDualView::panToCurrentFeature()
1195{
1196 const QModelIndex currentIndex = mTableView->currentIndex();
1197 if ( !currentIndex.isValid() )
1198 {
1199 return;
1200 }
1201
1202 QgsFeatureIds ids;
1203 ids.insert( mFilterModel->rowToId( currentIndex ) );
1204 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1205 if ( canvas )
1206 {
1207 canvas->panToFeatureIds( mLayer, ids );
1208 }
1209}
1210
1211void QgsDualView::flashCurrentFeature()
1212{
1213 const QModelIndex currentIndex = mTableView->currentIndex();
1214 if ( !currentIndex.isValid() )
1215 {
1216 return;
1217 }
1218
1219 QgsFeatureIds ids;
1220 ids.insert( mFilterModel->rowToId( currentIndex ) );
1221 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1222 if ( canvas )
1223 {
1224 canvas->flashFeatureIds( mLayer, ids );
1225 }
1226}
1227
1228void QgsDualView::rebuildFullLayerCache()
1229{
1230 connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
1231 connect( mLayerCache, &QgsVectorLayerCache::finished, this, &QgsDualView::finished, Qt::UniqueConnection );
1232
1233 mLayerCache->setFullCache( true );
1234}
1235
1236void QgsDualView::previewExpressionChanged( const QString &expression )
1237{
1238 mLayer->setDisplayExpression( expression );
1239}
1240
1241void QgsDualView::onSortColumnChanged()
1242{
1244 if ( cfg.sortExpression() != mFilterModel->sortExpression() ||
1245 cfg.sortOrder() != mFilterModel->sortOrder() )
1246 {
1247 cfg.setSortExpression( mFilterModel->sortExpression() );
1248 cfg.setSortOrder( mFilterModel->sortOrder() );
1250 }
1251}
1252
1253void QgsDualView::updateSelectedFeatures()
1254{
1255 QgsFeatureRequest r = mMasterModel->request();
1257 return; // already requested all features
1258
1259 r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
1260 mMasterModel->setRequest( r );
1261 mMasterModel->loadLayer();
1262 emit filterChanged();
1263}
1264
1265void QgsDualView::updateEditedAddedFeatures()
1266{
1267 QgsFeatureRequest r = mMasterModel->request();
1269 return; // already requested all features
1270
1271 r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
1272 mMasterModel->setRequest( r );
1273 mMasterModel->loadLayer();
1274 emit filterChanged();
1275}
1276
1277void QgsDualView::extentChanged()
1278{
1279 QgsFeatureRequest r = mMasterModel->request();
1280 if ( mFilterModel->mapCanvas() && ( r.filterType() != Qgis::FeatureRequestFilterType::NoFilter || !r.filterRect().isNull() ) )
1281 {
1282 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1283 r.setFilterRect( rect );
1284 mMasterModel->setRequest( r );
1285 mMasterModel->loadLayer();
1286 }
1287 emit filterChanged();
1288}
1289
1290void QgsDualView::featureFormAttributeChanged( const QString &attribute, const QVariant &value, bool attributeChanged )
1291{
1292 Q_UNUSED( attribute )
1293 Q_UNUSED( value )
1294 if ( attributeChanged )
1295 {
1296 mFeatureListView->setCurrentFeatureEdited( true );
1297 mAttributeForm->save();
1298 }
1299}
1300
1302{
1303 mFilterModel->setFilteredFeatures( filteredFeatures );
1304}
1305
1306void QgsDualView::filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context )
1307{
1308 mFilterModel->setFilterExpression( filterExpression, context );
1309 mFilterModel->filterFeatures();
1310}
1311
1312
1314{
1315 mMasterModel->setRequest( request );
1316}
1317
1319{
1320 mTableView->setFeatureSelectionManager( featureSelectionManager );
1321 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1322
1323 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
1324 delete mFeatureSelectionManager;
1325
1326 mFeatureSelectionManager = featureSelectionManager;
1327}
1328
1330{
1331 mConfig = config;
1332 mConfig.update( mLayer->fields() );
1333 mLayer->setAttributeTableConfig( mConfig );
1334 mFilterModel->setAttributeTableConfig( mConfig );
1335 mTableView->setAttributeTableConfig( mConfig );
1336 const QgsAttributeList attributes { requiredAttributes( mLayer ) };
1337 QgsFeatureRequest request { mMasterModel->request() };
1338 request.setSubsetOfAttributes( attributes );
1339 mMasterModel->setRequest( request );
1340 mLayerCache->setCacheSubsetOfAttributes( attributes );
1341}
1342
1343void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder )
1344{
1345 if ( sortExpression.isNull() )
1346 mFilterModel->sort( -1 );
1347 else
1348 mFilterModel->sort( sortExpression, sortOrder );
1349
1351 mConfig.setSortOrder( sortOrder );
1352 setAttributeTableConfig( mConfig );
1353}
1354
1356{
1357 return mFilterModel->sortExpression();
1358}
1359
1361{
1362 return mConfig;
1363}
1364
1365void QgsDualView::progress( int i, bool &cancel )
1366{
1367 if ( !mProgressDlg )
1368 {
1369 mProgressDlg = new QProgressDialog( tr( "Loading features…" ), tr( "Abort" ), 0, 0, this );
1370 mProgressDlg->setWindowTitle( tr( "Attribute Table" ) );
1371 mProgressDlg->setWindowModality( Qt::WindowModal );
1372 mProgressDlg->show();
1373 }
1374
1375 mProgressDlg->setLabelText( tr( "%L1 features loaded." ).arg( i ) );
1376 QCoreApplication::processEvents();
1377
1378 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1379}
1380
1381void QgsDualView::finished()
1382{
1383 delete mProgressDlg;
1384 mProgressDlg = nullptr;
1385}
1386
1387/*
1388 * QgsAttributeTableAction
1389 */
1390
1392{
1393 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1394}
1395
1397{
1398 QgsFeatureIds editedIds;
1399 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1400 mDualView->setCurrentEditSelection( editedIds );
1402}
1403
1404/*
1405 * QgsAttributeTableMapLayerAction
1406 */
1407
1409{
1411 mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx, context );
1412}
@ NoFilter
No filter is applied.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFilter
No spatial filtering of features.
@ MultipleFeatures
Action targets multiple features from a layer.
@ Layer
Action targets a complete layer.
@ SingleFeature
Action targets a single feature from a layer.
This class is a menu that is populated automatically with the actions defined for a given layer.
Definition: qgsactionmenu.h:39
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:37
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
This class contains context information for attribute editor widgets.
QgsMessageBar * mainMessageBar()
Returns the main message bar.
@ SearchMode
Form values are used for searching/filtering the layer.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
const QgsAttributeEditorContext * parentContext() const
void parentFormValueChanged(const QString &attribute, const QVariant &newValue)
Is called in embedded forms when an attribute value in the parent form has changed to newValue.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
void refreshFeature()
reload current feature
bool needsGeometry() const
Returns true if any of the form widgets need feature geometry.
bool save()
Save all the values from the editors to the layer.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the form.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
void flashFeatures(const QString &filter)
Emitted when the user chooses to flash a filtered set of features.
void modeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
void zoomToFeatures(const QString &filter)
Emitted when the user chooses to zoom to a filtered set of features.
void setMode(QgsAttributeEditorContext::Mode mode)
Sets the current mode of the form.
This is a container for configuration of the attribute table.
void setSortExpression(const QString &sortExpression)
Set the sort expression used for sorting.
Qt::SortOrder sortOrder() const
Gets the sort order.
QVector< QgsAttributeTableConfig::ColumnConfig > columns() const
Gets the list with all columns and their configuration.
int mapVisibleColumnToIndex(int visibleColumn) const
Maps a visible column index to its original column index.
void update(const QgsFields &fields)
Update the configuration with the given fields.
void setSortOrder(Qt::SortOrder sortOrder)
Set the sort order.
int columnWidth(int column) const
Returns the width of a column, or -1 if column should use default width.
void setColumnHidden(int column, bool hidden)
Sets whether the specified column should be hidden.
QString sortExpression() const
Gets the expression used for sorting.
void setColumnWidth(int column, int width)
Sets the width of a column.
QString sortExpression() const
The expression which is used to sort the attribute table.
FilterMode filterMode()
The current filterModel.
void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override
Sort by the given column using the given order.
QgsMapCanvas * mapCanvas() const
Returns the map canvas.
void setFilterMode(FilterMode filterMode)
Set the filter mode the filter will use.
QString filterExpression() const
Returns the stored filter expression string.
void setAttributeTableConfig(const QgsAttributeTableConfig &config, bool force=false)
Set the attribute table configuration to control which fields are shown, in which order they are show...
void disconnectFilterModeConnections()
Disconnect the connections set for the current filterMode.
FilterMode
The filter mode defines how the rows should be filtered.
@ ShowFilteredList
Show only features whose ids are on the filter list. {.
@ ShowVisible
Show only visible features (depends on the map canvas)
@ ShowSelected
Show only selected features.
@ ShowInvalid
Show only features not respecting constraints (since QGIS 3.30)
@ ShowEdited
Show only features which have unsaved changes.
void filterError(const QString &errorMessage)
Emitted when an error occurred while filtering features.
void filterFeatures()
Updates the filtered features in the filter model.
void setFilterExpression(const QgsExpression &expression, const QgsExpressionContext &context)
Set the expression and the context to be stored in case of the features need to be filtered again (li...
virtual void setFilteredFeatures(const QgsFeatureIds &ids)
Specify a list of features, which the filter will accept.
QgsFeatureId rowToId(const QModelIndex &row)
Returns the feature id for a given model index.
void featuresFiltered()
Emitted when the filtering of the features has been done.
void visibleReloaded()
Emitted when the the visible features on extend are reloaded (the list is created)
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
void setSelectedOnTop(bool selectedOnTop)
Changes the sort order of the features.
void sortColumnChanged(int column, Qt::SortOrder order)
Emitted whenever the sort column is changed.
A model backed by a QgsVectorLayerCache which is able to provide feature/attribute information to a Q...
const QgsFeatureRequest & request() const
Gets the the feature request.
void fieldConditionalStyleChanged(const QString &fieldName)
Handles updating the model when the conditional style for a field changes.
void modelChanged()
Model has been changed.
void executeMapLayerAction(QgsMapLayerAction *action, const QModelIndex &idx, const QgsMapLayerActionContext &context=QgsMapLayerActionContext()) const
Execute a QgsMapLayerAction.
void progress(int i, bool &cancel)
void setRequest(const QgsFeatureRequest &request)
Set a request that will be used to fill this attribute table model.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows.
void setEditorContext(const QgsAttributeEditorContext &context)
Sets the context in which this table is shown.
void setShowValidityState(bool show)
Sets whether the attribute table will add a visual feedback to cells when an attribute constraint is ...
QgsFeatureId rowToId(int row) const
Maps row to feature id.
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
void setExtraColumns(int extraColumns)
Empty extra columns to announce from this model.
void executeAction(QUuid action, const QModelIndex &idx) const
Execute an action.
QVariant data(const QModelIndex &index, int role) const override
Returns data on the given index.
void willShowContextMenu(QMenu *menu, const QModelIndex &atIndex)
Emitted in order to provide a hook to add additional* menu entries to the context menu.
void columnResized(int column, int width)
Emitted when a column in the view has been resized.
void showContextMenuExternally(QgsActionMenu *menu, QgsFeatureId fid)
Emitted when selecting context menu on the feature list to create the context menu individually.
void copyCellContent() const
Copy the content of the selected cell in the clipboard.
ViewMode
The view modes, in which this widget can present information.
Definition: qgsdualview.h:56
@ AttributeEditor
Show a list of the features, where one can be chosen and the according attribute dialog will be prese...
Definition: qgsdualview.h:68
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
Set the feature selection model.
~QgsDualView() override
static QgsAttributeList requiredAttributes(const QgsVectorLayer *layer)
Returns the list of required attributes according to the attribute table configuration of the layer,...
QgsAttributeTableFilterModel::FilterMode filterMode()
Gets the filter mode.
Definition: qgsdualview.h:133
void setMultiEditEnabled(bool enabled)
Sets whether multi edit mode is enabled.
QgsFeatureIds filteredFeatures()
Gets a list of currently visible feature ids.
Definition: qgsdualview.h:178
void cancelProgress()
Cancel the progress dialog (if any)
void filterChanged()
Emitted whenever the filter changes.
QgsDualView(QWidget *parent=nullptr)
Constructor.
Definition: qgsdualview.cpp:51
void setAttributeTableConfig(const QgsAttributeTableConfig &config)
Set the attribute table config which should be used to control the appearance of the attribute table.
ViewMode view() const
Returns the current view mode.
int featureCount()
Returns the number of features on the layer.
Q_DECL_DEPRECATED void setFilteredFeatures(const QgsFeatureIds &filteredFeatures)
Set a list of currently visible features.
void formModeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
FeatureListBrowsingAction
Action on the map canvas when browsing the list of features.
Definition: qgsdualview.h:75
@ NoAction
No action is done.
Definition: qgsdualview.h:76
@ PanToFeature
The map is panned to the center of the feature bounding-box.
Definition: qgsdualview.h:77
@ ZoomToFeature
The map is zoomed to contained the feature bounding-box.
Definition: qgsdualview.h:78
void hideEvent(QHideEvent *event) override
QgsAttributeTableConfig attributeTableConfig() const
The config used for the attribute table.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the view.
void init(QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request=QgsFeatureRequest(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), bool loadFeatures=true, bool showFirstFeature=true)
Has to be called to initialize the dual view.
bool saveEditChanges()
saveEditChanges
void openConditionalStyles()
void toggleSearchMode(bool enabled)
Toggles whether search mode should be enabled in the form.
void displayExpressionChanged(const QString &expression)
Emitted whenever the display expression is successfully changed.
void setSortExpression(const QString &sortExpression, Qt::SortOrder sortOrder=Qt::AscendingOrder)
Set the expression used for sorting the table and feature list.
void setRequest(const QgsFeatureRequest &request)
Set the request.
void parentFormValueChanged(const QString &attribute, const QVariant &value)
Called in embedded forms when an attribute value in the parent form has changed.
QgsAttributeTableModel * masterModel() const
Returns the model which has the information about all features (not only filtered)
Definition: qgsdualview.h:185
void setCurrentEditSelection(const QgsFeatureIds &fids)
Set the current edit selection in the AttributeEditor mode.
int filteredFeatureCount()
Returns the number of features which are currently visible, according to the filter restrictions.
QString sortExpression() const
Gets the expression used for sorting the table and feature list.
void setFilterMode(QgsAttributeTableFilterModel::FilterMode filterMode)
Set the filter mode.
void setView(ViewMode view)
Change the current view mode.
void setSelectedOnTop(bool selectedOnTop)
Toggle the selectedOnTop flag.
void filterFeatures(const QgsExpression &filterExpression, const QgsExpressionContext &context)
Sets the expression and Updates the filtered features in the filter model.
A generic dialog for building expression strings.
A reusable widget that can be used to build a expression string.
QString expressionText()
Gets the expression string that has been set in the expression area.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
void initWithLayer(QgsVectorLayer *layer, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with a layer.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Class for parsing and evaluation of expressions (formerly called "search strings").
QSet< int > referencedAttributeIndexes(const QgsFields &fields) const
Returns a list of field name indexes obtained from the provided fields.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void setSortByDisplayExpression(bool sortByDisplayExpression, Qt::SortOrder order=Qt::AscendingOrder)
Sort this model by its display expression.
QVariant data(const QModelIndex &index, int role) const override
Shows a list of features and renders a edit button next to each feature.
void currentEditSelectionProgressChanged(int progress, int count)
Emitted whenever the current edit selection has been changed.
void editNextFeature()
editNextFeature will try to edit next feature of the list
void editLastFeature()
editLastFeature will try to edit the last feature of the list
void editFirstFeature()
editFirstFeature will try to edit the first feature of the list
void displayExpressionChanged(const QString &expression)
Emitted whenever the display expression is successfully changed.
void editPreviousFeature()
editPreviousFeature will try to edit previous feature of the list
void willShowContextMenu(QgsActionMenu *menu, const QModelIndex &atIndex)
Emitted when the context menu is created to add the specific actions to it.
void currentEditSelectionChanged(QgsFeature &feat)
Emitted whenever the current edit selection has been changed.
void aboutToChangeEditSelection(bool &ok)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
Qgis::FeatureRequestFlags flags() const
Returns the flags which affect how features are fetched.
QgsFeatureRequest & disableFilter()
Disables any attribute/ID filtering.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
Qgis::SpatialFilterType spatialFilterType() const
Returns the spatial filter type which is currently set on this request.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
A widget for customizing conditional formatting options.
void rulesUpdated(const QString &fieldName)
Emitted when the conditional styling rules are updated.
void setLayer(QgsVectorLayer *layer)
Sets the vector layer associated with the widget.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:89
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition: qgsgui.cpp:119
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition: qgsgui.cpp:129
Is an interface class to abstract feature selection handling.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:93
void extentsChanged()
Emitted when the extents of the map change.
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
void flashFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of features with matching ids from a vector layer to flash within the canvas.
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
Encapsulates the context in which a QgsMapLayerAction action is executed.
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, Qgis::MapLayerActionTargets targets=Qgis::MapLayerActionTarget::AllActions, const QgsMapLayerActionContext &context=QgsMapLayerActionContext())
Returns the map layer actions which can run on the specified layer.
An action which can run on map layers The class can be used in two manners:
void layerModified()
Emitted when modifications has been done on layer.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void pushWarning(const QString &title, const QString &message)
Pushes a warning message that must be manually dismissed by the user.
Dialog for organising (hiding and reordering) columns in the attributes table.
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool isNull() const
Test if the rectangle is null (holding no spatial information).
Definition: qgsrectangle.h:505
A QScrollArea subclass with improved scrolling behavior.
Definition: qgsscrollarea.h:41
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setEnumValue(const QString &key, const T &value, const Section section=NoSection)
Set the value of a setting based on an enum.
Definition: qgssettings.h:315
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection)
Returns the setting value for a setting based on an enum.
Definition: qgssettings.h:263
QShortcut * shortcutByName(const QString &name) const
Returns a shortcut by its name, or nullptr if nothing found.
@ SelectAtId
Fast access to features using their ID.
This class caches features of a given QgsVectorLayer.
void setFullCache(bool fullCache)
This enables or disables full caching.
void finished()
When filling the cache, this signal gets emitted once the cache is fully initialized.
void invalidated()
The cache has been invalidated and cleared.
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the list (possibly a subset) of attributes to be cached.
void progress(int i, bool &cancel)
When filling the cache, this signal gets emitted periodically to notify about the progress and to be ...
void setCacheGeometry(bool cacheGeometry)
Enable or disable the caching of geometries.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
QgsAttributeTableConfig attributeTableConfig() const
Returns the attribute table configuration object.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QList< int > QgsAttributeList
Definition: qgsfield.h:27
Defines the configuration of a column in the attribute table.