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