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