QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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<
57 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 );
58const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::attributeEditorSplitterState = std::make_unique<
59 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 );
60
61QgsDualView::QgsDualView( QWidget *parent )
62 : QStackedWidget( parent )
63{
64 setupUi( this );
65 connect( mFeatureListView, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::featureListAboutToChangeEditSelection );
66 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::featureListCurrentEditSelectionChanged );
67 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionProgressChanged, this, &QgsDualView::updateEditSelectionProgress );
68 connect( mFeatureListView, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
69
70 connect( mTableView, &QgsAttributeTableView::willShowContextMenu, this, &QgsDualView::viewWillShowContextMenu );
71 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
72 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &QgsDualView::showViewHeaderMenu );
73 connect( mTableView, &QgsAttributeTableView::columnResized, this, &QgsDualView::tableColumnResized );
74
75 mConditionalFormatWidgetStack->hide();
76 mConditionalFormatWidget = new QgsFieldConditionalFormatWidget( this );
77 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
78 mConditionalFormatWidget->setDockMode( true );
79
80 const QgsSettings settings;
81 // copy old setting
82 conditionalFormattingSplitterState->copyValueFromKey( u"/qgis/attributeTable/splitterState"_s, true );
83 mConditionalSplitter->restoreState( conditionalFormattingSplitterState->value().toByteArray() );
84 mAttributeEditorViewSplitter->restoreState( attributeEditorSplitterState->value().toByteArray() );
85
86 mPreviewColumnsMenu = new QMenu( this );
87 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
88
89 // Set preview icon
90 mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( u"/mIconExpressionPreview.svg"_s ) );
91
92 // Connect layer list preview signals
93 connect( mActionExpressionPreview, &QAction::triggered, this, &QgsDualView::previewExpressionBuilder );
94 connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
95
96 // browsing toolbar
97 connect( mNextFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editNextFeature );
98 connect( mPreviousFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editPreviousFeature );
99 connect( mFirstFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editFirstFeature );
100 connect( mLastFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editLastFeature );
101
102 auto createShortcuts = [this]( const QString &objectName, void ( QgsFeatureListView::*slot )() ) {
103 QShortcut *sc = QgsGui::shortcutsManager()->shortcutByName( objectName );
104 // do not assert for sc as it would lead to crashes in testing
105 // or when using custom widgets lib if built with Debug
106 if ( sc )
107 connect( sc, &QShortcut::activated, mFeatureListView, slot );
108 };
109 createShortcuts( u"mAttributeTableFirstEditedFeature"_s, &QgsFeatureListView::editFirstFeature );
110 createShortcuts( u"mAttributeTablePreviousEditedFeature"_s, &QgsFeatureListView::editPreviousFeature );
111 createShortcuts( u"mAttributeTableNextEditedFeature"_s, &QgsFeatureListView::editNextFeature );
112 createShortcuts( u"mAttributeTableLastEditedFeature"_s, &QgsFeatureListView::editLastFeature );
113
114 QButtonGroup *buttonGroup = new QButtonGroup( this );
115 buttonGroup->setExclusive( false );
116 buttonGroup->addButton( mAutoPanButton, PanToFeature );
117 buttonGroup->addButton( mAutoZoomButton, ZoomToFeature );
118 const FeatureListBrowsingAction action = QgsSettings().enumValue( u"/qgis/attributeTable/featureListBrowsingAction"_s, NoAction );
119 QAbstractButton *bt = buttonGroup->button( static_cast<int>( action ) );
120 if ( bt )
121 bt->setChecked( true );
122 connect( buttonGroup, qOverload<QAbstractButton *, bool>( &QButtonGroup::buttonToggled ), this, &QgsDualView::panZoomGroupButtonToggled );
123 mFlashButton->setChecked( QgsSettings().value( u"/qgis/attributeTable/featureListHighlightFeature"_s, true ).toBool() );
124 connect( mFlashButton, &QToolButton::clicked, this, &QgsDualView::flashButtonClicked );
125}
126
129
130void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context, bool loadFeatures, bool showFirstFeature )
131{
132 if ( !layer )
133 return;
134
135 delete mAttributeForm;
136 mAttributeForm = nullptr;
137
138 mLayer = layer;
139
140 // Keep fields order in sync: force config reset
141 connect( mLayer, &QgsVectorLayer::updatedFields, this, [this] { mFilterModel->setAttributeTableConfig( attributeTableConfig(), /* force */ true ); } );
142
143 mEditorContext = context;
144
145 // create an empty form to find out if it needs geometry or not
146 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
147
148 const QgsExpression sortingExpression = QgsExpression( mConfig.sortExpression() );
149
150 const bool needsGeometry = mLayer->conditionalStyles()->rulesNeedGeometry()
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]() { mFeatureListModel->setSortByDisplayExpression( true, Qt::AscendingOrder ); } );
273 sortMenu->addAction( sortByPreviewExpressionAsc );
274 QAction *sortByPreviewExpressionDesc = new QAction( QgsApplication::getThemeIcon( u"sort-reverse.svg"_s ), tr( "By Display Name (Descending)" ), this );
275 connect( sortByPreviewExpressionDesc, &QAction::triggered, this, [this]() { mFeatureListModel->setSortByDisplayExpression( true, Qt::DescendingOrder ); } );
276 sortMenu->addAction( sortByPreviewExpressionDesc );
277 QAction *sortByPreviewExpressionCustom = new QAction( QgsApplication::getThemeIcon( u"mIconExpressionPreview.svg"_s ), tr( "By Custom Expression" ), this );
278 connect( sortByPreviewExpressionCustom, &QAction::triggered, this, [this]() {
279 if ( modifySort() )
280 mFeatureListModel->setSortByDisplayExpression( false );
281 } );
282 sortMenu->addAction( sortByPreviewExpressionCustom );
283
284 mFeatureListPreviewButton->addAction( sortMenuAction );
285
286 QAction *separator = new QAction( mFeatureListPreviewButton );
287 separator->setSeparator( true );
288 mFeatureListPreviewButton->addAction( separator );
289 restoreRecentDisplayExpressions();
290
291 if ( defaultFieldAction )
292 {
293 mFeatureListPreviewButton->setDefaultAction( defaultFieldAction );
294 mFeatureListPreviewButton->defaultAction()->trigger();
295 }
296 else
297 {
298 mActionExpressionPreview->setText( displayExpression );
299 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
300
301 mFeatureListView->setDisplayExpression( displayExpression );
302 setDisplayExpression( displayExpression.isEmpty() ? tr( "'[Please define preview text]'" ) : displayExpression );
303 }
304}
305
307{
308 setCurrentIndex( view );
309}
310
312{
313 return static_cast<QgsDualView::ViewMode>( currentIndex() );
314}
315
317{
318 // cleanup any existing connections
319 switch ( mFilterModel->filterMode() )
320 {
322 disconnect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
324 break;
325
329 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
330 break;
331
334 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
335 disconnect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
336 break;
337
339 disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
340 break;
341
343 mMasterModel->setShowValidityState( false );
344 break;
345 }
346
347 QgsFeatureRequest request = mMasterModel->request();
348
349 // create an empty form to find out if it needs geometry or not
350 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
351 const bool needsGeometry = ( filterMode == QgsAttributeTableFilterModel::ShowVisible ) || emptyForm.needsGeometry() || QgsExpression( mConfig.sortExpression() ).needsGeometry();
352
353 const bool requiresTableReload = ( request.filterType() != Qgis::Qgis::FeatureRequestFilterType::NoFilter
354 || request.spatialFilterType() != Qgis::SpatialFilterType::NoFilter ) // previous request was subset
355 || ( needsGeometry && request.flags() & Qgis::FeatureRequestFlag::NoGeometry ) // no geometry for last request
356 || ( mMasterModel->rowCount() == 0 ); // no features
357
358 request.setFlags( request.flags().setFlag( Qgis::FeatureRequestFlag::NoGeometry, !needsGeometry ) );
359 request.setFilterFids( QgsFeatureIds() );
360 request.setFilterRect( QgsRectangle() );
361 request.disableFilter();
362
363 // setup new connections and filter request parameters
364 switch ( filterMode )
365 {
367 connect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
368 if ( mFilterModel->mapCanvas() )
369 {
370 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
371 request.setFilterRect( rect );
372 }
374 break;
375
377 request.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
379 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
380 connect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
381 break;
382
384 {
385 mMasterModel->setShowValidityState( true );
387 filterFeatures( u"is_feature_valid() = false"_s, context );
389 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
390 break;
391 }
392
395 {
396 const QString filterExpression = filterMode == QgsAttributeTableFilterModel::ShowFilteredList ? mFilterModel->filterExpression() : QString();
397 if ( !filterExpression.isEmpty() )
398 request.setFilterExpression( mFilterModel->filterExpression() );
400 connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
401 break;
402 }
403
405 connect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
406 request.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
407 break;
408 }
409
410 // disable the browsing auto pan/scale if the list only shows visible items
411 switch ( filterMode )
412 {
414 setBrowsingAutoPanScaleAllowed( false );
415 break;
416
422 setBrowsingAutoPanScaleAllowed( true );
423 break;
424 }
425
426 if ( requiresTableReload )
427 {
428 //disconnect the connections of the current (old) filtermode before reload
429 mFilterModel->disconnectFilterModeConnections();
430
431 mMasterModel->setRequest( request );
432 whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
433 mMasterModel->loadLayer();
434 }
435
436 //update filter model
437 mFilterModel->setFilterMode( filterMode );
438 emit filterChanged();
439}
440
441void QgsDualView::setSelectedOnTop( bool selectedOnTop )
442{
443 mFilterModel->setSelectedOnTop( selectedOnTop );
444}
445
446void QgsDualView::initLayerCache( bool cacheGeometry )
447{
448 // Initialize the cache
449 const QgsSettings settings;
450 const int cacheSize = settings.value( u"qgis/attributeTableRowCache"_s, "10000" ).toInt();
451 mLayerCache = new QgsVectorLayerCache( mLayer, cacheSize, this );
452 mLayerCache->setCacheSubsetOfAttributes( requiredAttributes( mLayer ) );
453 mLayerCache->setCacheGeometry( cacheGeometry );
454 if ( 0 == cacheSize || !mLayer->dataProvider()->capabilities().testFlag( Qgis::VectorProviderCapability::SelectAtId ) )
455 {
456 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsDualView::rebuildFullLayerCache );
457 rebuildFullLayerCache();
458 }
459}
460
461void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, bool loadFeatures )
462{
463 delete mFeatureListModel;
464 delete mFilterModel;
465 delete mMasterModel;
466
467 mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
468 mMasterModel->setRequest( request );
469 mMasterModel->setEditorContext( mEditorContext );
470 mMasterModel->setExtraColumns( 1 ); // Add one extra column which we can "abuse" as an action column
471
472 connect( mMasterModel, &QgsAttributeTableModel::progress, this, &QgsDualView::progress );
473 connect( mMasterModel, &QgsAttributeTableModel::finished, this, &QgsDualView::finished );
474
476
477 if ( loadFeatures )
478 mMasterModel->loadLayer();
479
480 mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
481
482 // The following connections to invalidate() are necessary to keep the filter model in sync
483 // see regression https://github.com/qgis/QGIS/issues/23890
484 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
485 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
486
488
489 mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
490}
491
492void QgsDualView::restoreRecentDisplayExpressions()
493{
494 const QVariantList previewExpressions = mLayer->customProperty( u"dualview/previewExpressions"_s ).toList();
495
496 for ( const QVariant &previewExpression : previewExpressions )
497 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
498}
499
500void QgsDualView::saveRecentDisplayExpressions() const
501{
502 if ( !mLayer )
503 {
504 return;
505 }
506 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
507
508 // Remove existing same action
509 int index = actions.indexOf( mLastDisplayExpressionAction );
510 if ( index != -1 )
511 {
512 QVariantList previewExpressions;
513 for ( ; index < actions.length(); ++index )
514 {
515 QAction *action = actions.at( index );
516 previewExpressions << action->property( "previewExpression" );
517 }
518
519 mLayer->setCustomProperty( u"dualview/previewExpressions"_s, previewExpressions );
520 }
521}
522
523void QgsDualView::setDisplayExpression( const QString &expression )
524{
525 mDisplayExpression = expression;
526 insertRecentlyUsedDisplayExpression( expression );
527}
528
529void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression )
530{
531 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
532
533 // Remove existing same action
534 const int index = actions.indexOf( mLastDisplayExpressionAction );
535 if ( index != -1 )
536 {
537 for ( int i = 0; index + i < actions.length(); ++i )
538 {
539 QAction *action = actions.at( index );
540 if ( action->text() == expression || i >= 9 )
541 {
542 if ( action == mLastDisplayExpressionAction )
543 {
544 mLastDisplayExpressionAction = nullptr;
545 }
546 mFeatureListPreviewButton->removeAction( action );
547 }
548 else
549 {
550 if ( !mLastDisplayExpressionAction )
551 {
552 mLastDisplayExpressionAction = action;
553 }
554 }
555 }
556 }
557
558 QString name = expression;
559 QIcon icon = QgsApplication::getThemeIcon( u"/mIconExpressionPreview.svg"_s );
560 if ( expression.startsWith( "COALESCE( \""_L1 ) && expression.endsWith( ", '<NULL>' )"_L1 ) )
561 {
562 name = expression.mid( 11, expression.length() - 24 ); // Numbers calculated from the COALESCE / <NULL> parts
563
564 const int fieldIndex = mLayer->fields().indexOf( name );
565 if ( fieldIndex != -1 )
566 {
567 name = mLayer->attributeDisplayName( fieldIndex );
568 icon = mLayer->fields().iconForField( fieldIndex );
569 }
570 else
571 {
572 name = expression;
573 }
574 }
575
576 QAction *previewAction = new QAction( icon, name, mFeatureListPreviewButton );
577 previewAction->setProperty( "previewExpression", expression );
578 connect( previewAction, &QAction::triggered, this, [expression, this]( bool ) {
579 setDisplayExpression( expression );
580 mFeatureListPreviewButton->setText( expression );
581 } );
582
583 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
584 mLastDisplayExpressionAction = previewAction;
585}
586
587void QgsDualView::updateEditSelectionProgress( int progress, int count )
588{
589 mProgressCount->setText( u"%1 / %2"_s.arg( progress + 1 ).arg( count ) );
590 mPreviousFeatureButton->setEnabled( progress > 0 );
591 mNextFeatureButton->setEnabled( progress + 1 < count );
592 mFirstFeatureButton->setEnabled( progress > 0 );
593 mLastFeatureButton->setEnabled( progress + 1 < count );
594 if ( mAttributeForm )
595 {
596 mAttributeForm->setVisible( count > 0 );
597 }
598}
599
600void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
601{
602 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
603 if ( canvas && view() == AttributeEditor && featureset != mLastFeatureSet )
604 {
605 if ( mBrowsingAutoPanScaleAllowed )
606 {
607 if ( mAutoPanButton->isChecked() )
608 QTimer::singleShot( 0, this, [this, featureset, canvas]() { canvas->panToFeatureIds( mLayer, featureset, false ); } );
609 else if ( mAutoZoomButton->isChecked() )
610 QTimer::singleShot( 0, this, [this, featureset, canvas]() { canvas->zoomToFeatureIds( mLayer, featureset ); } );
611 }
612 if ( mFlashButton->isChecked() )
613 QTimer::singleShot( 0, this, [this, featureset, canvas]() { canvas->flashFeatureIds( mLayer, featureset ); } );
614 mLastFeatureSet = featureset;
615 }
616}
617
618void QgsDualView::setBrowsingAutoPanScaleAllowed( bool allowed )
619{
620 if ( mBrowsingAutoPanScaleAllowed == allowed )
621 return;
622
623 mBrowsingAutoPanScaleAllowed = allowed;
624
625 mAutoPanButton->setEnabled( allowed );
626 mAutoZoomButton->setEnabled( allowed );
627
628 const QString disabledHint = tr( "(disabled when attribute table only shows features visible in the current map canvas extent)" );
629
630 mAutoPanButton->setToolTip( tr( "Automatically pan to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
631 mAutoZoomButton->setToolTip( tr( "Automatically zoom to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
632}
633
634void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
635{
636 if ( button == mAutoPanButton && checked )
637 {
638 QgsSettings().setEnumValue( u"/qgis/attributeTable/featureListBrowsingAction"_s, PanToFeature );
639 mAutoZoomButton->setChecked( false );
640 }
641 else if ( button == mAutoZoomButton && checked )
642 {
643 QgsSettings().setEnumValue( u"/qgis/attributeTable/featureListBrowsingAction"_s, ZoomToFeature );
644 mAutoPanButton->setChecked( false );
645 }
646 else
647 {
648 QgsSettings().setEnumValue( u"/qgis/attributeTable/featureListBrowsingAction"_s, NoAction );
649 }
650
651 if ( checked && mLayer->isSpatial() )
652 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
653}
654
655void QgsDualView::flashButtonClicked( bool clicked )
656{
657 QgsSettings().setValue( u"/qgis/attributeTable/featureListHighlightFeature"_s, clicked );
658 if ( !clicked )
659 return;
660
661 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
662
663 if ( canvas )
664 canvas->flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
665}
666
667void QgsDualView::filterError( const QString &errorMessage )
668{
669 if ( mEditorContext.mainMessageBar() )
670 {
671 mEditorContext.mainMessageBar()->pushWarning( tr( "An error occurred while filtering features" ), errorMessage );
672 }
673}
674
675void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
676{
677 if ( !mAttributeForm )
678 return;
679
680 if ( mLayer->isEditable() && !mAttributeForm->save() )
681 ok = false;
682}
683
684void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
685{
686 if ( !mAttributeForm )
687 {
688 initAttributeForm( feat );
689 }
690 else if ( !mLayer->isEditable() || mAttributeForm->save() )
691 {
692 mAttributeForm->setFeature( feat );
693 QgsFeatureIds featureset;
694 featureset << feat.id();
695 setCurrentEditSelection( featureset );
696
697 if ( mLayer->isSpatial() )
698 panOrZoomToFeature( featureset );
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 {
748 mAttributeForm->setMode( QgsAttributeEditorContext::SearchMode );
749 mAttributeForm->setVisible( true );
750 }
751 else
752 {
753 mAttributeForm->setMode( QgsAttributeEditorContext::SingleEditMode );
754 mAttributeForm->setVisible( mFilterModel->rowCount() > 0 );
755 }
756}
757
758void QgsDualView::previewExpressionBuilder()
759{
760 // Show expression builder
762
763 QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, u"generic"_s, context );
764 dlg.setWindowTitle( tr( "Expression Based Preview" ) );
765 dlg.setExpressionText( mFeatureListView->displayExpression() );
766
767 if ( dlg.exec() == QDialog::Accepted )
768 {
769 mFeatureListView->setDisplayExpression( dlg.expressionText() );
770 mActionExpressionPreview->setText( dlg.expressionText() );
771
772 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
773 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
774 }
775
776 setDisplayExpression( mFeatureListView->displayExpression() );
777}
778
779void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
780{
781 if ( !mFeatureListView->setDisplayExpression( u"COALESCE( \"%1\", '<NULL>' )"_s.arg( expression ) ) )
782 {
783 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() ) );
784 }
785 else
786 {
787 mActionExpressionPreview->setText( tr( "Expression" ) );
788 mFeatureListPreviewButton->setText( previewAction->text() );
789 mFeatureListPreviewButton->setIcon( previewAction->icon() );
790 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
791 }
792
793 setDisplayExpression( mFeatureListView->displayExpression() );
794}
795
797{
798 return mMasterModel->rowCount();
799}
800
802{
803 return mFilterModel->rowCount();
804}
805
807{
808 const QModelIndex currentIndex = mTableView->currentIndex();
809 if ( !currentIndex.isValid() )
810 {
811 return;
812 }
813
814 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
815 QApplication::clipboard()->setText( var.toString() );
816}
817
819{
820 if ( mProgressDlg )
821 mProgressDlg->cancel();
822}
823
824void QgsDualView::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
825{
826 if ( mAttributeForm )
827 {
828 mAttributeForm->parentFormValueChanged( attribute, newValue );
829 }
830}
831
832void QgsDualView::hideEvent( QHideEvent *event )
833{
834 Q_UNUSED( event )
835 saveRecentDisplayExpressions();
836
837 // Better to save settings here than in destructor. This last can be called after a new
838 // project is loaded. So, when Qgis::ProjectFlag::RememberAttributeTableWindowsBetweenSessions is set,
839 // a new QgsDualView is created at project loading and we restore the old settings before saving the
840 // new one.
841 // And also, we override close event to just hide in QgsDockableWidgetHelper::eventFilter, that's why hideEvent
842 conditionalFormattingSplitterState->setValue( mConditionalSplitter->saveState() );
843 attributeEditorSplitterState->setValue( mAttributeEditorViewSplitter->saveState() );
844}
845
846void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &masterIndex )
847{
848 if ( !menu )
849 {
850 return;
851 }
852
853 const QVariant displayValue = mMasterModel->data( masterIndex, Qt::DisplayRole );
854
855 if ( displayValue.isValid() )
856 {
857 // get values for preview in menu
858 QString previewDisplayValue = displayValue.toString();
859 if ( previewDisplayValue.length() > 12 )
860 {
861 previewDisplayValue.truncate( 12 );
862 previewDisplayValue.append( u"…"_s );
863 }
864
865
866 QAction *copyContentAction = menu->addAction( tr( "Copy Cell Content (%1)" ).arg( previewDisplayValue ) );
867 menu->addAction( copyContentAction );
868 connect( copyContentAction, &QAction::triggered, this, [displayValue] { QApplication::clipboard()->setText( displayValue.toString() ); } );
869 }
870
871 const QVariant rawValue = mMasterModel->data( masterIndex, Qt::EditRole );
872 if ( rawValue.isValid() )
873 {
874 // get values for preview in menu
875 QString previewRawValue = rawValue.toString();
876 if ( previewRawValue.length() > 12 )
877 {
878 previewRawValue.truncate( 12 );
879 previewRawValue.append( u"…"_s );
880 }
881
882 QAction *copyRawContentAction = menu->addAction( tr( "Copy Raw Value (%1)" ).arg( previewRawValue ) );
883 menu->addAction( copyRawContentAction );
884 connect( copyRawContentAction, &QAction::triggered, this, [rawValue] { QApplication::clipboard()->setText( rawValue.toString() ); } );
885 }
886
887 QgsVectorLayer *vl = mFilterModel->layer();
888 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
889 if ( canvas && vl && vl->isSpatial() )
890 {
891 QAction *zoomToFeatureAction = menu->addAction( tr( "Zoom to Feature" ) );
892 connect( zoomToFeatureAction, &QAction::triggered, this, &QgsDualView::zoomToCurrentFeature );
893
894 QAction *panToFeatureAction = menu->addAction( tr( "Pan to Feature" ) );
895 connect( panToFeatureAction, &QAction::triggered, this, &QgsDualView::panToCurrentFeature );
896
897 QAction *flashFeatureAction = menu->addAction( tr( "Flash Feature" ) );
898 connect( flashFeatureAction, &QAction::triggered, this, &QgsDualView::flashCurrentFeature );
899 }
900
901 //add user-defined actions to context menu
902 const QList<QgsAction> actions = mLayer->actions()->actions( u"Field"_s );
903 if ( !actions.isEmpty() )
904 {
905 QAction *a = menu->addAction( tr( "Run Layer Action" ) );
906 a->setEnabled( false );
907
908 for ( const QgsAction &action : actions )
909 {
910 if ( !action.runable() )
911 continue;
912
913 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
914 continue;
915
916 QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, action.id(), masterIndex );
917 menu->addAction( action.name(), a, &QgsAttributeTableAction::execute );
918 }
919 }
920 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
921 if ( !rowSourceIndex.isValid() )
922 {
923 return;
924 }
925
926 //add actions from QgsMapLayerActionRegistry to context menu
927 QgsMapLayerActionContext context;
928 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::Layer | Qgis::MapLayerActionTarget::SingleFeature, context );
929 if ( !registeredActions.isEmpty() )
930 {
931 //add a separator between user defined and standard actions
932 menu->addSeparator();
933
934 for ( QgsMapLayerAction *action : registeredActions )
935 {
936 QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction( action->text(), this, action, rowSourceIndex );
937 menu->addAction( action->text(), a, &QgsAttributeTableMapLayerAction::execute );
938 }
939 }
940
941 // entries for multiple features layer actions
942 // only show if the context menu is shown over a selected row
943 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
944 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
945 {
946 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::MultipleFeatures, context );
947 if ( !registeredActions.isEmpty() )
948 {
949 menu->addSeparator();
950 QAction *action = menu->addAction( tr( "Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
951 action->setEnabled( false );
952
953 QgsMapLayerActionContext context;
954 for ( QgsMapLayerAction *action : registeredActions )
955 {
956 menu->addAction( action->text(), action, [this, action, context]() {
957 Q_NOWARN_DEPRECATED_PUSH
958 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
959 Q_NOWARN_DEPRECATED_POP
960 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
961 } );
962 }
963 }
964 }
965
966 menu->addSeparator();
967 QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open Form" ), this, QUuid(), rowSourceIndex );
968 menu->addAction( tr( "Open Form…" ), a, &QgsAttributeTableAction::featureForm );
969}
970
971
972void QgsDualView::widgetWillShowContextMenu( QgsActionMenu *menu, const QModelIndex &atIndex )
973{
974 emit showContextMenuExternally( menu, mFilterModel->rowToId( atIndex ) );
975}
976
977
978void QgsDualView::showViewHeaderMenu( QPoint point )
979{
980 const int col = mTableView->columnAt( point.x() );
981
982 delete mHorizontalHeaderMenu;
983 mHorizontalHeaderMenu = new QMenu( this );
984
985 QAction *hide = new QAction( tr( "&Hide Column" ), mHorizontalHeaderMenu );
986 connect( hide, &QAction::triggered, this, &QgsDualView::hideColumn );
987 hide->setData( col );
988 mHorizontalHeaderMenu->addAction( hide );
989 QAction *setWidth = new QAction( tr( "&Set Width…" ), mHorizontalHeaderMenu );
990 connect( setWidth, &QAction::triggered, this, &QgsDualView::resizeColumn );
991 setWidth->setData( col );
992 mHorizontalHeaderMenu->addAction( setWidth );
993
994 QAction *setWidthAllColumns = new QAction( tr( "&Set All Column Widths…" ), mHorizontalHeaderMenu );
995 connect( setWidthAllColumns, &QAction::triggered, this, &QgsDualView::resizeAllColumns );
996 setWidthAllColumns->setData( col );
997 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
998
999 QAction *optimizeWidth = new QAction( tr( "&Autosize" ), mHorizontalHeaderMenu );
1000 connect( optimizeWidth, &QAction::triggered, this, &QgsDualView::autosizeColumn );
1001 optimizeWidth->setData( col );
1002 mHorizontalHeaderMenu->addAction( optimizeWidth );
1003
1004 QAction *optimizeWidthAllColumns = new QAction( tr( "&Autosize All Columns" ), mHorizontalHeaderMenu );
1005 connect( optimizeWidthAllColumns, &QAction::triggered, this, &QgsDualView::autosizeAllColumns );
1006 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
1007
1008
1009 mHorizontalHeaderMenu->addSeparator();
1010 QAction *organize = new QAction( tr( "&Organize Columns…" ), mHorizontalHeaderMenu );
1011 connect( organize, &QAction::triggered, this, &QgsDualView::organizeColumns );
1012 mHorizontalHeaderMenu->addAction( organize );
1013 QAction *sort = new QAction( tr( "&Sort…" ), mHorizontalHeaderMenu );
1014 connect( sort, &QAction::triggered, this, [this]() { modifySort(); } );
1015 mHorizontalHeaderMenu->addAction( sort );
1016
1017 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
1018}
1019
1020void QgsDualView::organizeColumns()
1021{
1022 if ( !mLayer )
1023 {
1024 return;
1025 }
1026
1027 QgsOrganizeTableColumnsDialog dialog( mLayer, attributeTableConfig(), this );
1028 if ( dialog.exec() == QDialog::Accepted )
1029 {
1030 const QgsAttributeTableConfig config = dialog.config();
1031 setAttributeTableConfig( config );
1032 }
1033}
1034
1035void QgsDualView::tableColumnResized( int column, int width )
1036{
1037 QgsAttributeTableConfig config = mConfig;
1038 const int sourceCol = config.mapVisibleColumnToIndex( column );
1039 if ( sourceCol >= 0 && config.columnWidth( sourceCol ) != width )
1040 {
1041 config.setColumnWidth( sourceCol, width );
1042 setAttributeTableConfig( config );
1043 }
1044}
1045
1046void QgsDualView::hideColumn()
1047{
1048 QAction *action = qobject_cast<QAction *>( sender() );
1049 const int col = action->data().toInt();
1050 QgsAttributeTableConfig config = mConfig;
1051 const int sourceCol = mConfig.mapVisibleColumnToIndex( col );
1052 if ( sourceCol >= 0 )
1053 {
1054 config.setColumnHidden( sourceCol, true );
1055 setAttributeTableConfig( config );
1056 }
1057}
1058
1059void QgsDualView::resizeColumn()
1060{
1061 QAction *action = qobject_cast<QAction *>( sender() );
1062 const int col = action->data().toInt();
1063 if ( col < 0 )
1064 return;
1065
1066 QgsAttributeTableConfig config = mConfig;
1067 const int sourceCol = config.mapVisibleColumnToIndex( col );
1068 if ( sourceCol >= 0 )
1069 {
1070 bool ok = false;
1071 const int width = QInputDialog::getInt( this, tr( "Set column width" ), tr( "Enter column width" ), mTableView->columnWidth( col ), 0, 1000, 10, &ok );
1072 if ( ok )
1073 {
1074 config.setColumnWidth( sourceCol, width );
1075 setAttributeTableConfig( config );
1076 }
1077 }
1078}
1079
1080void QgsDualView::resizeAllColumns()
1081{
1082 QAction *action = qobject_cast<QAction *>( sender() );
1083 const int col = action->data().toInt();
1084 if ( col < 0 )
1085 return;
1086
1087 QgsAttributeTableConfig config = mConfig;
1088
1089 bool ok = false;
1090 const int width = QInputDialog::getInt( this, tr( "Set Column Width" ), tr( "Enter column width" ), mTableView->columnWidth( col ), 1, 1000, 10, &ok );
1091 if ( ok )
1092 {
1093 const int colCount = mTableView->model()->columnCount();
1094 if ( colCount > 0 )
1095 {
1096 for ( int i = 0; i < colCount; i++ )
1097 {
1098 config.setColumnWidth( i, width );
1099 }
1100 setAttributeTableConfig( config );
1101 }
1102 }
1103}
1104
1105void QgsDualView::autosizeColumn()
1106{
1107 QAction *action = qobject_cast<QAction *>( sender() );
1108 const int col = action->data().toInt();
1109 mTableView->resizeColumnToContents( col );
1110}
1111
1112void QgsDualView::autosizeAllColumns()
1113{
1114 mTableView->resizeColumnsToContents();
1115}
1116
1117bool QgsDualView::modifySort()
1118{
1119 if ( !mLayer )
1120 return false;
1121
1122 QgsAttributeTableConfig config = mConfig;
1123
1124 QDialog orderByDlg;
1125 orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) );
1126 QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1127 QGridLayout *layout = new QGridLayout();
1128 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1129 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1130 orderByDlg.setLayout( layout );
1131
1132 QGroupBox *sortingGroupBox = new QGroupBox();
1133 sortingGroupBox->setTitle( tr( "Defined sort order in attribute table" ) );
1134 sortingGroupBox->setCheckable( true );
1135 sortingGroupBox->setChecked( !sortExpression().isEmpty() );
1136 layout->addWidget( sortingGroupBox );
1137 sortingGroupBox->setLayout( new QGridLayout() );
1138
1139 QgsExpressionBuilderWidget *expressionBuilder = new QgsExpressionBuilderWidget();
1140 const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
1141
1142 expressionBuilder->initWithLayer( mLayer, context, u"generic"_s );
1143 expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
1144
1145 sortingGroupBox->layout()->addWidget( expressionBuilder );
1146
1147 QCheckBox *cbxSortAscending = new QCheckBox( tr( "Sort ascending" ) );
1148 cbxSortAscending->setChecked( config.sortOrder() == Qt::AscendingOrder );
1149 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1150
1151 layout->addWidget( dialogButtonBox );
1152 if ( orderByDlg.exec() )
1153 {
1154 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1155 if ( sortingGroupBox->isChecked() )
1156 {
1157 setSortExpression( expressionBuilder->expressionText(), sortOrder );
1158 config.setSortExpression( expressionBuilder->expressionText() );
1159 config.setSortOrder( sortOrder );
1160 }
1161 else
1162 {
1163 setSortExpression( QString(), sortOrder );
1164 config.setSortExpression( QString() );
1165 }
1166
1167 return true;
1168 }
1169 else
1170 {
1171 return false;
1172 }
1173}
1174
1176{
1177 QSet<int> attributes;
1178
1179 const QgsAttributeTableConfig config { layer->attributeTableConfig() };
1180
1181 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.columns() };
1182 for ( const QgsAttributeTableConfig::ColumnConfig &columnConfig : std::as_const( constColumnconfigs ) )
1183 {
1184 if ( columnConfig.type == QgsAttributeTableConfig::Type::Field && !columnConfig.hidden )
1185 {
1186 attributes.insert( layer->fields().lookupField( columnConfig.name ) );
1187 }
1188 }
1189
1190 const QSet<int> colAttrs { attributes };
1191 for ( const int attrIdx : std::as_const( colAttrs ) )
1192 {
1193 if ( layer->fields().fieldOrigin( attrIdx ) == Qgis::FieldOrigin::Expression )
1194 {
1195 attributes += QgsExpression( layer->expressionField( attrIdx ) ).referencedAttributeIndexes( layer->fields() );
1196 }
1197 }
1198
1199 QgsAttributeList attrs { attributes.values() };
1200 std::sort( attrs.begin(), attrs.end() );
1201 return attrs;
1202}
1203
1204void QgsDualView::zoomToCurrentFeature()
1205{
1206 const QModelIndex currentIndex = mTableView->currentIndex();
1207 if ( !currentIndex.isValid() )
1208 {
1209 return;
1210 }
1211
1212 QgsFeatureIds ids;
1213 ids.insert( mFilterModel->rowToId( currentIndex ) );
1214 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1215 if ( canvas )
1216 {
1217 canvas->zoomToFeatureIds( mLayer, ids );
1218 }
1219}
1220
1221void QgsDualView::panToCurrentFeature()
1222{
1223 const QModelIndex currentIndex = mTableView->currentIndex();
1224 if ( !currentIndex.isValid() )
1225 {
1226 return;
1227 }
1228
1229 QgsFeatureIds ids;
1230 ids.insert( mFilterModel->rowToId( currentIndex ) );
1231 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1232 if ( canvas )
1233 {
1234 canvas->panToFeatureIds( mLayer, ids );
1235 }
1236}
1237
1238void QgsDualView::flashCurrentFeature()
1239{
1240 const QModelIndex currentIndex = mTableView->currentIndex();
1241 if ( !currentIndex.isValid() )
1242 {
1243 return;
1244 }
1245
1246 QgsFeatureIds ids;
1247 ids.insert( mFilterModel->rowToId( currentIndex ) );
1248 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1249 if ( canvas )
1250 {
1251 canvas->flashFeatureIds( mLayer, ids );
1252 }
1253}
1254
1255void QgsDualView::rebuildFullLayerCache()
1256{
1257 connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
1258 connect( mLayerCache, &QgsVectorLayerCache::finished, this, &QgsDualView::finished, Qt::UniqueConnection );
1259
1260 mLayerCache->setFullCache( true );
1261}
1262
1263void QgsDualView::previewExpressionChanged( const QString &expression )
1264{
1265 mLayer->setDisplayExpression( expression );
1266}
1267
1268void QgsDualView::onSortColumnChanged()
1269{
1270 QgsAttributeTableConfig cfg = attributeTableConfig();
1271 if ( cfg.sortExpression() != mFilterModel->sortExpression() || cfg.sortOrder() != mFilterModel->sortOrder() )
1272 {
1273 cfg.setSortExpression( mFilterModel->sortExpression() );
1274 cfg.setSortOrder( mFilterModel->sortOrder() );
1276 }
1277}
1278
1279void QgsDualView::updateSelectedFeatures()
1280{
1281 QgsFeatureRequest r = mMasterModel->request();
1283 return; // already requested all features
1284
1285 r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
1286 mMasterModel->setRequest( r );
1287 mMasterModel->loadLayer();
1288 emit filterChanged();
1289}
1290
1291void QgsDualView::updateEditedAddedFeatures()
1292{
1293 QgsFeatureRequest r = mMasterModel->request();
1295 return; // already requested all features
1296
1297 r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
1298 mMasterModel->setRequest( r );
1299 mMasterModel->loadLayer();
1300 emit filterChanged();
1301}
1302
1303void QgsDualView::extentChanged()
1304{
1305 QgsFeatureRequest r = mMasterModel->request();
1306 if ( mFilterModel->mapCanvas() && ( r.filterType() != Qgis::FeatureRequestFilterType::NoFilter || !r.filterRect().isNull() ) )
1307 {
1308 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1309 r.setFilterRect( rect );
1310 mMasterModel->setRequest( r );
1311 mMasterModel->loadLayer();
1312 }
1313 emit filterChanged();
1314}
1315
1316void QgsDualView::featureFormAttributeChanged( const QString &attribute, const QVariant &value, bool attributeChanged )
1317{
1318 Q_UNUSED( attribute )
1319 Q_UNUSED( value )
1320 if ( attributeChanged )
1321 {
1322 mFeatureListView->setCurrentFeatureEdited( true );
1323 mAttributeForm->save();
1324 }
1325}
1326
1328{
1329 mFilterModel->setFilteredFeatures( filteredFeatures );
1330}
1331
1332void QgsDualView::filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context )
1333{
1334 mFilterModel->setFilterExpression( filterExpression, context );
1335 mFilterModel->filterFeatures();
1336}
1337
1338
1340{
1341 mMasterModel->setRequest( request );
1342}
1343
1345{
1346 mTableView->setFeatureSelectionManager( featureSelectionManager );
1347 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1348
1349 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
1350 delete mFeatureSelectionManager;
1351
1352 mFeatureSelectionManager = featureSelectionManager;
1353}
1354
1356{
1357 mConfig = config;
1358 mConfig.update( mLayer->fields() );
1359 mLayer->setAttributeTableConfig( mConfig );
1360 mFilterModel->setAttributeTableConfig( mConfig );
1361 mTableView->setAttributeTableConfig( mConfig );
1362 const QgsAttributeList attributes { requiredAttributes( mLayer ) };
1363 QgsFeatureRequest request = mMasterModel->request();
1364 // if the sort expression needs geometry reset the flag
1365 if ( QgsExpression( config.sortExpression() ).needsGeometry() )
1366 {
1367 mLayerCache->setCacheGeometry( true );
1368 request.setFlags( request.flags().setFlag( Qgis::FeatureRequestFlag::NoGeometry, false ) );
1369 }
1370 request.setSubsetOfAttributes( attributes );
1371 mMasterModel->setRequest( request );
1372 mLayerCache->setCacheSubsetOfAttributes( attributes );
1373}
1374
1375void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder )
1376{
1377 if ( sortExpression.isNull() )
1378 mFilterModel->sort( -1 );
1379 else
1380 mFilterModel->sort( sortExpression, sortOrder );
1381
1382 mConfig.setSortExpression( sortExpression );
1383 mConfig.setSortOrder( sortOrder );
1384 setAttributeTableConfig( mConfig );
1385}
1386
1388{
1389 return mFilterModel->sortExpression();
1390}
1391
1393{
1394 return mConfig;
1395}
1396
1397void QgsDualView::progress( int i, bool &cancel )
1398{
1399 if ( !mProgressDlg )
1400 {
1401 mProgressDlg = new QProgressDialog( tr( "Loading features…" ), tr( "Abort" ), 0, 0, this );
1402 mProgressDlg->setWindowTitle( tr( "Attribute Table" ) );
1403 mProgressDlg->setWindowModality( Qt::WindowModal );
1404 mProgressDlg->show();
1405 }
1406
1407 mProgressDlg->setLabelText( tr( "%L1 features loaded." ).arg( i ) );
1408 QCoreApplication::processEvents();
1409
1410 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1411}
1412
1413void QgsDualView::finished()
1414{
1415 delete mProgressDlg;
1416 mProgressDlg = nullptr;
1417}
1418
1419/*
1420 * QgsAttributeTableAction
1421 */
1422
1424{
1425 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1426}
1427
1429{
1430 QgsFeatureIds editedIds;
1431 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1432 mDualView->setCurrentEditSelection( editedIds );
1433 mDualView->setView( QgsDualView::AttributeEditor );
1434}
1435
1436/*
1437 * QgsAttributeTableMapLayerAction
1438 */
1439
1441{
1443 mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx, context );
1444}
@ SelectAtId
Fast access to features using their ID.
Definition qgis.h:533
@ NoFilter
No filter is applied.
Definition qgis.h:2304
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2276
@ NoFilter
No spatial filtering of features.
Definition qgis.h:2333
@ MultipleFeatures
Action targets multiple features from a layer.
Definition qgis.h:4785
@ Layer
Action targets a complete layer.
Definition qgis.h:4783
@ SingleFeature
Action targets a single feature from a layer.
Definition qgis.h:4784
@ Expression
Field is calculated from an expression.
Definition qgis.h:1788
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.
A variant settings entry.
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:6880
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.