QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <QTimer>
50
51#include "moc_qgsdualview.cpp"
52
53const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::conditionalFormattingSplitterState = std::make_unique<QgsSettingsEntryVariant>( QStringLiteral( "attribute-table-splitter-state" ), QgsSettingsTree::sTreeWindowState, QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), QStringLiteral( "State of conditional formatting splitter's layout so it could be restored when opening attribute table view." ) );
54const std::unique_ptr<QgsSettingsEntryVariant> QgsDualView::attributeEditorSplitterState = std::make_unique<QgsSettingsEntryVariant>( QStringLiteral( "attribute-editor-splitter-state" ), QgsSettingsTree::sTreeWindowState, QgsVariantUtils::createNullVariant( QMetaType::Type::QByteArray ), QStringLiteral( "State of attribute editor splitter's layout so it could be restored when opening attribute editor view." ) );
55
56QgsDualView::QgsDualView( QWidget *parent )
57 : QStackedWidget( parent )
58{
59 setupUi( this );
60 connect( mFeatureListView, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::featureListAboutToChangeEditSelection );
61 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::featureListCurrentEditSelectionChanged );
62 connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionProgressChanged, this, &QgsDualView::updateEditSelectionProgress );
63 connect( mFeatureListView, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
64
65 connect( mTableView, &QgsAttributeTableView::willShowContextMenu, this, &QgsDualView::viewWillShowContextMenu );
66 mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
67 connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &QgsDualView::showViewHeaderMenu );
68 connect( mTableView, &QgsAttributeTableView::columnResized, this, &QgsDualView::tableColumnResized );
69
70 mConditionalFormatWidgetStack->hide();
71 mConditionalFormatWidget = new QgsFieldConditionalFormatWidget( this );
72 mConditionalFormatWidgetStack->setMainPanel( mConditionalFormatWidget );
73 mConditionalFormatWidget->setDockMode( true );
74
75 const QgsSettings settings;
76 // copy old setting
77 conditionalFormattingSplitterState->copyValueFromKey( QStringLiteral( "/qgis/attributeTable/splitterState" ), true );
78 mConditionalSplitter->restoreState( conditionalFormattingSplitterState->value().toByteArray() );
79 mAttributeEditorViewSplitter->restoreState( attributeEditorSplitterState->value().toByteArray() );
80
81 mPreviewColumnsMenu = new QMenu( this );
82 mActionPreviewColumnsMenu->setMenu( mPreviewColumnsMenu );
83
84 // Set preview icon
85 mActionExpressionPreview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionPreview.svg" ) ) );
86
87 // Connect layer list preview signals
88 connect( mActionExpressionPreview, &QAction::triggered, this, &QgsDualView::previewExpressionBuilder );
89 connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
90
91 // browsing toolbar
92 connect( mNextFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editNextFeature );
93 connect( mPreviousFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editPreviousFeature );
94 connect( mFirstFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editFirstFeature );
95 connect( mLastFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editLastFeature );
96
97 auto createShortcuts = [this]( const QString &objectName, void ( QgsFeatureListView::*slot )() ) {
98 QShortcut *sc = QgsGui::shortcutsManager()->shortcutByName( objectName );
99 // do not assert for sc as it would lead to crashes in testing
100 // or when using custom widgets lib if built with Debug
101 if ( sc )
102 connect( sc, &QShortcut::activated, mFeatureListView, slot );
103 };
104 createShortcuts( QStringLiteral( "mAttributeTableFirstEditedFeature" ), &QgsFeatureListView::editFirstFeature );
105 createShortcuts( QStringLiteral( "mAttributeTablePreviousEditedFeature" ), &QgsFeatureListView::editPreviousFeature );
106 createShortcuts( QStringLiteral( "mAttributeTableNextEditedFeature" ), &QgsFeatureListView::editNextFeature );
107 createShortcuts( QStringLiteral( "mAttributeTableLastEditedFeature" ), &QgsFeatureListView::editLastFeature );
108
109 QButtonGroup *buttonGroup = new QButtonGroup( this );
110 buttonGroup->setExclusive( false );
111 buttonGroup->addButton( mAutoPanButton, PanToFeature );
112 buttonGroup->addButton( mAutoZoomButton, ZoomToFeature );
113 const FeatureListBrowsingAction action = QgsSettings().enumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
114 QAbstractButton *bt = buttonGroup->button( static_cast<int>( action ) );
115 if ( bt )
116 bt->setChecked( true );
117 connect( buttonGroup, qOverload<QAbstractButton *, bool>( &QButtonGroup::buttonToggled ), this, &QgsDualView::panZoomGroupButtonToggled );
118 mFlashButton->setChecked( QgsSettings().value( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), true ).toBool() );
119 connect( mFlashButton, &QToolButton::clicked, this, &QgsDualView::flashButtonClicked );
120}
121
125
126void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context, bool loadFeatures, bool showFirstFeature )
127{
128 if ( !layer )
129 return;
130
131 delete mAttributeForm;
132 mAttributeForm = nullptr;
133
134 mLayer = layer;
135
136 // Keep fields order in sync: force config reset
137 connect( mLayer, &QgsVectorLayer::updatedFields, this, [this] {
138 mFilterModel->setAttributeTableConfig( attributeTableConfig(), /* force */ true );
139 } );
140
141 mEditorContext = context;
142
143 // create an empty form to find out if it needs geometry or not
144 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
145
146 const QgsExpression sortingExpression = QgsExpression( mConfig.sortExpression() );
147
148 const bool needsGeometry = mLayer->conditionalStyles()->rulesNeedGeometry() || !( request.flags() & Qgis::FeatureRequestFlag::NoGeometry )
150 || emptyForm.needsGeometry()
151 || sortingExpression.needsGeometry();
152
153 initLayerCache( needsGeometry );
154 initModels( mapCanvas, request, loadFeatures );
155
156 mConditionalFormatWidget->setLayer( mLayer );
157
158 mTableView->setModel( mFilterModel );
159 mFeatureListView->setModel( mFeatureListModel );
160
161 connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged );
162 connect( mLayer, &QgsVectorLayer::afterCommitChanges, this, [this] { mFeatureListView->setCurrentFeatureEdited( false ); } );
163
164 if ( mFeatureListPreviewButton->defaultAction() )
165 {
166 mFeatureListView->setDisplayExpression( mDisplayExpression );
167 }
168 else
169 {
170 columnBoxInit();
171 }
172
173 // This slows down load of the attribute table heaps and uses loads of memory.
174 //mTableView->resizeColumnsToContents();
175
176 if ( showFirstFeature && mFeatureListModel->rowCount() > 0 )
177 {
178 mFeatureListView->setEditSelection( QgsFeatureIds() << mFeatureListModel->data( mFeatureListModel->index( 0, 0 ), QgsFeatureListModel::Role::FeatureRole ).value<QgsFeature>().id() );
179 }
180 else
181 {
182 // Set attribute table config to allow for proper sorting ordering in feature list view
183 setAttributeTableConfig( mLayer->attributeTableConfig() );
184 }
185}
186
187void QgsDualView::initAttributeForm( const QgsFeature &feature )
188{
189 Q_ASSERT( !mAttributeForm );
190
191 mAttributeForm = new QgsAttributeForm( mLayer, feature, mEditorContext );
192 if ( !mEditorContext.parentContext() )
193 {
194 mAttributeEditorScrollArea = new QgsScrollArea();
195 mAttributeEditorScrollArea->setWidgetResizable( true );
196 mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
197 mAttributeEditorScrollArea->setWidget( mAttributeForm );
198 }
199 else
200 {
201 mAttributeEditor->layout()->addWidget( mAttributeForm );
202 }
203
204 // This is an arbitrary yet small value to fix issue GH #50181
205 // the default value is 0.
206 mAttributeForm->setMinimumWidth( 200 );
207
208 setAttributeTableConfig( mLayer->attributeTableConfig() );
209
210 connect( mAttributeForm, &QgsAttributeForm::widgetValueChanged, this, &QgsDualView::featureFormAttributeChanged );
211 connect( mAttributeForm, &QgsAttributeForm::modeChanged, this, &QgsDualView::formModeChanged );
213 connect( mAttributeForm, &QgsAttributeForm::flashFeatures, this, [this]( const QString &filter ) {
214 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
215 {
216 QgsMapCanvasUtils::flashMatchingFeatures( canvas, mLayer, filter );
217 }
218 } );
219 connect( mAttributeForm, &QgsAttributeForm::zoomToFeatures, this, [this]( const QString &filter ) {
220 if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
221 {
222 QgsMapCanvasUtils::zoomToMatchingFeatures( canvas, mLayer, filter );
223 }
224 } );
225
226 connect( mMasterModel, &QgsAttributeTableModel::modelChanged, mAttributeForm, &QgsAttributeForm::refreshFeature );
227}
228
229void QgsDualView::columnBoxInit()
230{
231 // load fields
232 const QList<QgsField> fields = mLayer->fields().toList();
233 const QString displayExpression = mLayer->displayExpression();
234
235 mFeatureListPreviewButton->addAction( mActionExpressionPreview );
236 mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
237
238 QAction *defaultFieldAction = nullptr;
239 const auto constFields = fields;
240 for ( const QgsField &field : constFields )
241 {
242 const int fieldIndex = mLayer->fields().lookupField( field.name() );
243 if ( fieldIndex == -1 )
244 continue;
245
246 const QString fieldName = field.name();
247 if ( QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldName ).type() != QLatin1String( "Hidden" ) )
248 {
249 const QIcon icon = mLayer->fields().iconForField( fieldIndex );
250 const QString text = mLayer->attributeDisplayName( fieldIndex );
251
252 // Generate action for the preview popup button of the feature list
253 QAction *previewAction = new QAction( icon, text, mFeatureListPreviewButton );
254 connect( previewAction, &QAction::triggered, this, [this, previewAction, fieldName] { previewColumnChanged( previewAction, fieldName ); } );
255 mPreviewColumnsMenu->addAction( previewAction );
256
257 if ( text == displayExpression || QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( text ) == displayExpression || QStringLiteral( "\"%1\"" ).arg( text ) == displayExpression )
258 {
259 defaultFieldAction = previewAction;
260 }
261 }
262 }
263
264 QMenu *sortMenu = new QMenu( this );
265 QAction *sortMenuAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "Sort…" ), this );
266 sortMenuAction->setMenu( sortMenu );
267
268 QAction *sortByPreviewExpressionAsc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "By Display Name (Ascending)" ), this );
269 connect( sortByPreviewExpressionAsc, &QAction::triggered, this, [this]() {
270 mFeatureListModel->setSortByDisplayExpression( true, Qt::AscendingOrder );
271 } );
272 sortMenu->addAction( sortByPreviewExpressionAsc );
273 QAction *sortByPreviewExpressionDesc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort-reverse.svg" ) ), tr( "By Display Name (Descending)" ), this );
274 connect( sortByPreviewExpressionDesc, &QAction::triggered, this, [this]() {
275 mFeatureListModel->setSortByDisplayExpression( true, Qt::DescendingOrder );
276 } );
277 sortMenu->addAction( sortByPreviewExpressionDesc );
278 QAction *sortByPreviewExpressionCustom = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "mIconExpressionPreview.svg" ) ), tr( "By Custom Expression" ), this );
279 connect( sortByPreviewExpressionCustom, &QAction::triggered, this, [this]() {
280 if ( modifySort() )
281 mFeatureListModel->setSortByDisplayExpression( false );
282 } );
283 sortMenu->addAction( sortByPreviewExpressionCustom );
284
285 mFeatureListPreviewButton->addAction( sortMenuAction );
286
287 QAction *separator = new QAction( mFeatureListPreviewButton );
288 separator->setSeparator( true );
289 mFeatureListPreviewButton->addAction( separator );
290 restoreRecentDisplayExpressions();
291
292 if ( defaultFieldAction )
293 {
294 mFeatureListPreviewButton->setDefaultAction( defaultFieldAction );
295 mFeatureListPreviewButton->defaultAction()->trigger();
296 }
297 else
298 {
299 mActionExpressionPreview->setText( displayExpression );
300 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
301
302 mFeatureListView->setDisplayExpression( displayExpression );
303 setDisplayExpression( displayExpression.isEmpty() ? tr( "'[Please define preview text]'" ) : displayExpression );
304 }
305}
306
308{
309 setCurrentIndex( view );
310}
311
313{
314 return static_cast<QgsDualView::ViewMode>( currentIndex() );
315}
316
318{
319 // cleanup any existing connections
320 switch ( mFilterModel->filterMode() )
321 {
323 disconnect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
325 break;
326
330 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
331 break;
332
335 disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
336 disconnect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
337 break;
338
340 disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
341 break;
342
344 mMasterModel->setShowValidityState( false );
345 break;
346 }
347
348 QgsFeatureRequest request = mMasterModel->request();
349
350 // create an empty form to find out if it needs geometry or not
351 const QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );
352 const bool needsGeometry = ( filterMode == QgsAttributeTableFilterModel::ShowVisible ) || emptyForm.needsGeometry() || QgsExpression( mConfig.sortExpression() ).needsGeometry();
353
354 const bool requiresTableReload = ( request.filterType() != Qgis::Qgis::FeatureRequestFilterType::NoFilter || 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( QStringLiteral( "is_feature_valid() = false" ), 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( QStringLiteral( "qgis/attributeTableRowCache" ), "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( QStringLiteral( "dualview/previewExpressions" ) ).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( QStringLiteral( "dualview/previewExpressions" ), 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( QStringLiteral( "/mIconExpressionPreview.svg" ) );
560 if ( expression.startsWith( QLatin1String( "COALESCE( \"" ) ) && expression.endsWith( QLatin1String( ", '<NULL>' )" ) ) )
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( QStringLiteral( "%1 / %2" ).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]() {
609 canvas->panToFeatureIds( mLayer, featureset, false );
610 } );
611 else if ( mAutoZoomButton->isChecked() )
612 QTimer::singleShot( 0, this, [this, featureset, canvas]() {
613 canvas->zoomToFeatureIds( mLayer, featureset );
614 } );
615 }
616 if ( mFlashButton->isChecked() )
617 QTimer::singleShot( 0, this, [this, featureset, canvas]() {
618 canvas->flashFeatureIds( mLayer, featureset );
619 } );
620 mLastFeatureSet = featureset;
621 }
622}
623
624void QgsDualView::setBrowsingAutoPanScaleAllowed( bool allowed )
625{
626 if ( mBrowsingAutoPanScaleAllowed == allowed )
627 return;
628
629 mBrowsingAutoPanScaleAllowed = allowed;
630
631 mAutoPanButton->setEnabled( allowed );
632 mAutoZoomButton->setEnabled( allowed );
633
634 const QString disabledHint = tr( "(disabled when attribute table only shows features visible in the current map canvas extent)" );
635
636 mAutoPanButton->setToolTip( tr( "Automatically pan to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
637 mAutoZoomButton->setToolTip( tr( "Automatically zoom to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
638}
639
640void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
641{
642 if ( button == mAutoPanButton && checked )
643 {
644 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
645 mAutoZoomButton->setChecked( false );
646 }
647 else if ( button == mAutoZoomButton && checked )
648 {
649 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
650 mAutoPanButton->setChecked( false );
651 }
652 else
653 {
654 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
655 }
656
657 if ( checked && mLayer->isSpatial() )
658 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
659}
660
661void QgsDualView::flashButtonClicked( bool clicked )
662{
663 QgsSettings().setValue( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), clicked );
664 if ( !clicked )
665 return;
666
667 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
668
669 if ( canvas )
670 canvas->flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
671}
672
673void QgsDualView::filterError( const QString &errorMessage )
674{
675 if ( mEditorContext.mainMessageBar() )
676 {
677 mEditorContext.mainMessageBar()->pushWarning( tr( "An error occurred while filtering features" ), errorMessage );
678 }
679}
680
681void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
682{
683 if ( !mAttributeForm )
684 return;
685
686 if ( mLayer->isEditable() && !mAttributeForm->save() )
687 ok = false;
688}
689
690void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
691{
692 if ( !mAttributeForm )
693 {
694 initAttributeForm( feat );
695 }
696 else if ( !mLayer->isEditable() || mAttributeForm->save() )
697 {
698 mAttributeForm->setFeature( feat );
699 QgsFeatureIds featureset;
700 featureset << feat.id();
701 setCurrentEditSelection( featureset );
702
703 if ( mLayer->isSpatial() )
704 panOrZoomToFeature( featureset );
705 }
706 else
707 {
708 // Couldn't save feature
709 }
710}
711
713{
714 mFeatureListView->setCurrentFeatureEdited( false );
715 mFeatureListView->setEditSelection( fids );
716}
717
719{
720 return mAttributeForm ? mAttributeForm->save() : false;
721}
722
724{
725 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
726}
727
729{
730 if ( !mAttributeForm )
731 return;
732
733 if ( enabled )
734 {
735 mPreviousView = view();
737 }
738 else
739 {
740 setView( mPreviousView );
741 }
742
744}
745
747{
748 if ( !mAttributeForm )
749 return;
750
751 if ( enabled )
752 {
754 mAttributeForm->setMode( QgsAttributeEditorContext::SearchMode );
755 mAttributeForm->setVisible( true );
756 }
757 else
758 {
759 mAttributeForm->setMode( QgsAttributeEditorContext::SingleEditMode );
760 mAttributeForm->setVisible( mFilterModel->rowCount() > 0 );
761 }
762}
763
764void QgsDualView::previewExpressionBuilder()
765{
766 // Show expression builder
768
769 QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
770 dlg.setWindowTitle( tr( "Expression Based Preview" ) );
771 dlg.setExpressionText( mFeatureListView->displayExpression() );
772
773 if ( dlg.exec() == QDialog::Accepted )
774 {
775 mFeatureListView->setDisplayExpression( dlg.expressionText() );
776 mActionExpressionPreview->setText( dlg.expressionText() );
777
778 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
779 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
780 }
781
782 setDisplayExpression( mFeatureListView->displayExpression() );
783}
784
785void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
786{
787 if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
788 {
789 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() ) );
790 }
791 else
792 {
793 mActionExpressionPreview->setText( tr( "Expression" ) );
794 mFeatureListPreviewButton->setText( previewAction->text() );
795 mFeatureListPreviewButton->setIcon( previewAction->icon() );
796 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
797 }
798
799 setDisplayExpression( mFeatureListView->displayExpression() );
800}
801
803{
804 return mMasterModel->rowCount();
805}
806
808{
809 return mFilterModel->rowCount();
810}
811
813{
814 const QModelIndex currentIndex = mTableView->currentIndex();
815 if ( !currentIndex.isValid() )
816 {
817 return;
818 }
819
820 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
821 QApplication::clipboard()->setText( var.toString() );
822}
823
825{
826 if ( mProgressDlg )
827 mProgressDlg->cancel();
828}
829
830void QgsDualView::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
831{
832 if ( mAttributeForm )
833 {
834 mAttributeForm->parentFormValueChanged( attribute, newValue );
835 }
836}
837
838void QgsDualView::hideEvent( QHideEvent *event )
839{
840 Q_UNUSED( event )
841 saveRecentDisplayExpressions();
842
843 // Better to save settings here than in destructor. This last can be called after a new
844 // project is loaded. So, when Qgis::ProjectFlag::RememberAttributeTableWindowsBetweenSessions is set,
845 // a new QgsDualView is created at project loading and we restore the old settings before saving the
846 // new one.
847 // And also, we override close event to just hide in QgsDockableWidgetHelper::eventFilter, that's why hideEvent
848 conditionalFormattingSplitterState->setValue( mConditionalSplitter->saveState() );
849 attributeEditorSplitterState->setValue( mAttributeEditorViewSplitter->saveState() );
850}
851
852void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &masterIndex )
853{
854 if ( !menu )
855 {
856 return;
857 }
858
859 const QVariant displayValue = mMasterModel->data( masterIndex, Qt::DisplayRole );
860
861 if ( displayValue.isValid() )
862 {
863 // get values for preview in menu
864 QString previewDisplayValue = displayValue.toString();
865 if ( previewDisplayValue.length() > 12 )
866 {
867 previewDisplayValue.truncate( 12 );
868 previewDisplayValue.append( QStringLiteral( "…" ) );
869 }
870
871
872 QAction *copyContentAction = menu->addAction( tr( "Copy Cell Content (%1)" ).arg( previewDisplayValue ) );
873 menu->addAction( copyContentAction );
874 connect( copyContentAction, &QAction::triggered, this, [displayValue] {
875 QApplication::clipboard()->setText( displayValue.toString() );
876 } );
877 }
878
879 const QVariant rawValue = mMasterModel->data( masterIndex, Qt::EditRole );
880 if ( rawValue.isValid() )
881 {
882 // get values for preview in menu
883 QString previewRawValue = rawValue.toString();
884 if ( previewRawValue.length() > 12 )
885 {
886 previewRawValue.truncate( 12 );
887 previewRawValue.append( QStringLiteral( "…" ) );
888 }
889
890 QAction *copyRawContentAction = menu->addAction( tr( "Copy Raw Value (%1)" ).arg( previewRawValue ) );
891 menu->addAction( copyRawContentAction );
892 connect( copyRawContentAction, &QAction::triggered, this, [rawValue] {
893 QApplication::clipboard()->setText( rawValue.toString() );
894 } );
895 }
896
897 QgsVectorLayer *vl = mFilterModel->layer();
898 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
899 if ( canvas && vl && vl->isSpatial() )
900 {
901 QAction *zoomToFeatureAction = menu->addAction( tr( "Zoom to Feature" ) );
902 connect( zoomToFeatureAction, &QAction::triggered, this, &QgsDualView::zoomToCurrentFeature );
903
904 QAction *panToFeatureAction = menu->addAction( tr( "Pan to Feature" ) );
905 connect( panToFeatureAction, &QAction::triggered, this, &QgsDualView::panToCurrentFeature );
906
907 QAction *flashFeatureAction = menu->addAction( tr( "Flash Feature" ) );
908 connect( flashFeatureAction, &QAction::triggered, this, &QgsDualView::flashCurrentFeature );
909 }
910
911 //add user-defined actions to context menu
912 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Field" ) );
913 if ( !actions.isEmpty() )
914 {
915 QAction *a = menu->addAction( tr( "Run Layer Action" ) );
916 a->setEnabled( false );
917
918 for ( const QgsAction &action : actions )
919 {
920 if ( !action.runable() )
921 continue;
922
923 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
924 continue;
925
926 QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, action.id(), masterIndex );
927 menu->addAction( action.name(), a, &QgsAttributeTableAction::execute );
928 }
929 }
930 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
931 if ( !rowSourceIndex.isValid() )
932 {
933 return;
934 }
935
936 //add actions from QgsMapLayerActionRegistry to context menu
937 QgsMapLayerActionContext context;
938 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::Layer | Qgis::MapLayerActionTarget::SingleFeature, context );
939 if ( !registeredActions.isEmpty() )
940 {
941 //add a separator between user defined and standard actions
942 menu->addSeparator();
943
944 for ( QgsMapLayerAction *action : registeredActions )
945 {
946 QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction( action->text(), this, action, rowSourceIndex );
947 menu->addAction( action->text(), a, &QgsAttributeTableMapLayerAction::execute );
948 }
949 }
950
951 // entries for multiple features layer actions
952 // only show if the context menu is shown over a selected row
953 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
954 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
955 {
956 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::MultipleFeatures, context );
957 if ( !registeredActions.isEmpty() )
958 {
959 menu->addSeparator();
960 QAction *action = menu->addAction( tr( "Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
961 action->setEnabled( false );
962
963 QgsMapLayerActionContext context;
964 for ( QgsMapLayerAction *action : registeredActions )
965 {
966 menu->addAction( action->text(), action, [this, action, context]() {
967 Q_NOWARN_DEPRECATED_PUSH
968 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
969 Q_NOWARN_DEPRECATED_POP
970 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
971 } );
972 }
973 }
974 }
975
976 menu->addSeparator();
977 QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open Form" ), this, QUuid(), rowSourceIndex );
978 menu->addAction( tr( "Open Form…" ), a, &QgsAttributeTableAction::featureForm );
979}
980
981
982void QgsDualView::widgetWillShowContextMenu( QgsActionMenu *menu, const QModelIndex &atIndex )
983{
984 emit showContextMenuExternally( menu, mFilterModel->rowToId( atIndex ) );
985}
986
987
988void QgsDualView::showViewHeaderMenu( QPoint point )
989{
990 const int col = mTableView->columnAt( point.x() );
991
992 delete mHorizontalHeaderMenu;
993 mHorizontalHeaderMenu = new QMenu( this );
994
995 QAction *hide = new QAction( tr( "&Hide Column" ), mHorizontalHeaderMenu );
996 connect( hide, &QAction::triggered, this, &QgsDualView::hideColumn );
997 hide->setData( col );
998 mHorizontalHeaderMenu->addAction( hide );
999 QAction *setWidth = new QAction( tr( "&Set Width…" ), mHorizontalHeaderMenu );
1000 connect( setWidth, &QAction::triggered, this, &QgsDualView::resizeColumn );
1001 setWidth->setData( col );
1002 mHorizontalHeaderMenu->addAction( setWidth );
1003
1004 QAction *setWidthAllColumns = new QAction( tr( "&Set All Column Widths…" ), mHorizontalHeaderMenu );
1005 connect( setWidthAllColumns, &QAction::triggered, this, &QgsDualView::resizeAllColumns );
1006 setWidthAllColumns->setData( col );
1007 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
1008
1009 QAction *optimizeWidth = new QAction( tr( "&Autosize" ), mHorizontalHeaderMenu );
1010 connect( optimizeWidth, &QAction::triggered, this, &QgsDualView::autosizeColumn );
1011 optimizeWidth->setData( col );
1012 mHorizontalHeaderMenu->addAction( optimizeWidth );
1013
1014 QAction *optimizeWidthAllColumns = new QAction( tr( "&Autosize All Columns" ), mHorizontalHeaderMenu );
1015 connect( optimizeWidthAllColumns, &QAction::triggered, this, &QgsDualView::autosizeAllColumns );
1016 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
1017
1018
1019 mHorizontalHeaderMenu->addSeparator();
1020 QAction *organize = new QAction( tr( "&Organize Columns…" ), mHorizontalHeaderMenu );
1021 connect( organize, &QAction::triggered, this, &QgsDualView::organizeColumns );
1022 mHorizontalHeaderMenu->addAction( organize );
1023 QAction *sort = new QAction( tr( "&Sort…" ), mHorizontalHeaderMenu );
1024 connect( sort, &QAction::triggered, this, [this]() { modifySort(); } );
1025 mHorizontalHeaderMenu->addAction( sort );
1026
1027 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
1028}
1029
1030void QgsDualView::organizeColumns()
1031{
1032 if ( !mLayer )
1033 {
1034 return;
1035 }
1036
1037 QgsOrganizeTableColumnsDialog dialog( mLayer, attributeTableConfig(), this );
1038 if ( dialog.exec() == QDialog::Accepted )
1039 {
1040 const QgsAttributeTableConfig config = dialog.config();
1041 setAttributeTableConfig( config );
1042 }
1043}
1044
1045void QgsDualView::tableColumnResized( int column, int width )
1046{
1047 QgsAttributeTableConfig config = mConfig;
1048 const int sourceCol = config.mapVisibleColumnToIndex( column );
1049 if ( sourceCol >= 0 && config.columnWidth( sourceCol ) != width )
1050 {
1051 config.setColumnWidth( sourceCol, width );
1052 setAttributeTableConfig( config );
1053 }
1054}
1055
1056void QgsDualView::hideColumn()
1057{
1058 QAction *action = qobject_cast<QAction *>( sender() );
1059 const int col = action->data().toInt();
1060 QgsAttributeTableConfig config = mConfig;
1061 const int sourceCol = mConfig.mapVisibleColumnToIndex( col );
1062 if ( sourceCol >= 0 )
1063 {
1064 config.setColumnHidden( sourceCol, true );
1065 setAttributeTableConfig( config );
1066 }
1067}
1068
1069void QgsDualView::resizeColumn()
1070{
1071 QAction *action = qobject_cast<QAction *>( sender() );
1072 const int col = action->data().toInt();
1073 if ( col < 0 )
1074 return;
1075
1076 QgsAttributeTableConfig config = mConfig;
1077 const int sourceCol = config.mapVisibleColumnToIndex( col );
1078 if ( sourceCol >= 0 )
1079 {
1080 bool ok = false;
1081 const int width = QInputDialog::getInt( this, tr( "Set column width" ), tr( "Enter column width" ), mTableView->columnWidth( col ), 0, 1000, 10, &ok );
1082 if ( ok )
1083 {
1084 config.setColumnWidth( sourceCol, width );
1085 setAttributeTableConfig( config );
1086 }
1087 }
1088}
1089
1090void QgsDualView::resizeAllColumns()
1091{
1092 QAction *action = qobject_cast<QAction *>( sender() );
1093 const int col = action->data().toInt();
1094 if ( col < 0 )
1095 return;
1096
1097 QgsAttributeTableConfig config = mConfig;
1098
1099 bool ok = false;
1100 const int width = QInputDialog::getInt( this, tr( "Set Column Width" ), tr( "Enter column width" ), mTableView->columnWidth( col ), 1, 1000, 10, &ok );
1101 if ( ok )
1102 {
1103 const int colCount = mTableView->model()->columnCount();
1104 if ( colCount > 0 )
1105 {
1106 for ( int i = 0; i < colCount; i++ )
1107 {
1108 config.setColumnWidth( i, width );
1109 }
1110 setAttributeTableConfig( config );
1111 }
1112 }
1113}
1114
1115void QgsDualView::autosizeColumn()
1116{
1117 QAction *action = qobject_cast<QAction *>( sender() );
1118 const int col = action->data().toInt();
1119 mTableView->resizeColumnToContents( col );
1120}
1121
1122void QgsDualView::autosizeAllColumns()
1123{
1124 mTableView->resizeColumnsToContents();
1125}
1126
1127bool QgsDualView::modifySort()
1128{
1129 if ( !mLayer )
1130 return false;
1131
1132 QgsAttributeTableConfig config = mConfig;
1133
1134 QDialog orderByDlg;
1135 orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) );
1136 QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1137 QGridLayout *layout = new QGridLayout();
1138 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1139 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1140 orderByDlg.setLayout( layout );
1141
1142 QGroupBox *sortingGroupBox = new QGroupBox();
1143 sortingGroupBox->setTitle( tr( "Defined sort order in attribute table" ) );
1144 sortingGroupBox->setCheckable( true );
1145 sortingGroupBox->setChecked( !sortExpression().isEmpty() );
1146 layout->addWidget( sortingGroupBox );
1147 sortingGroupBox->setLayout( new QGridLayout() );
1148
1149 QgsExpressionBuilderWidget *expressionBuilder = new QgsExpressionBuilderWidget();
1150 const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
1151
1152 expressionBuilder->initWithLayer( mLayer, context, QStringLiteral( "generic" ) );
1153 expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
1154
1155 sortingGroupBox->layout()->addWidget( expressionBuilder );
1156
1157 QCheckBox *cbxSortAscending = new QCheckBox( tr( "Sort ascending" ) );
1158 cbxSortAscending->setChecked( config.sortOrder() == Qt::AscendingOrder );
1159 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1160
1161 layout->addWidget( dialogButtonBox );
1162 if ( orderByDlg.exec() )
1163 {
1164 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1165 if ( sortingGroupBox->isChecked() )
1166 {
1167 setSortExpression( expressionBuilder->expressionText(), sortOrder );
1168 config.setSortExpression( expressionBuilder->expressionText() );
1169 config.setSortOrder( sortOrder );
1170 }
1171 else
1172 {
1173 setSortExpression( QString(), sortOrder );
1174 config.setSortExpression( QString() );
1175 }
1176
1177 return true;
1178 }
1179 else
1180 {
1181 return false;
1182 }
1183}
1184
1186{
1187 QSet<int> attributes;
1188
1189 const QgsAttributeTableConfig config { layer->attributeTableConfig() };
1190
1191 const QVector<QgsAttributeTableConfig::ColumnConfig> constColumnconfigs { config.columns() };
1192 for ( const QgsAttributeTableConfig::ColumnConfig &columnConfig : std::as_const( constColumnconfigs ) )
1193 {
1194 if ( columnConfig.type == QgsAttributeTableConfig::Type::Field && !columnConfig.hidden )
1195 {
1196 attributes.insert( layer->fields().lookupField( columnConfig.name ) );
1197 }
1198 }
1199
1200 const QSet<int> colAttrs { attributes };
1201 for ( const int attrIdx : std::as_const( colAttrs ) )
1202 {
1203 if ( layer->fields().fieldOrigin( attrIdx ) == Qgis::FieldOrigin::Expression )
1204 {
1205 attributes += QgsExpression( layer->expressionField( attrIdx ) ).referencedAttributeIndexes( layer->fields() );
1206 }
1207 }
1208
1209 QgsAttributeList attrs { attributes.values() };
1210 std::sort( attrs.begin(), attrs.end() );
1211 return attrs;
1212}
1213
1214void QgsDualView::zoomToCurrentFeature()
1215{
1216 const QModelIndex currentIndex = mTableView->currentIndex();
1217 if ( !currentIndex.isValid() )
1218 {
1219 return;
1220 }
1221
1222 QgsFeatureIds ids;
1223 ids.insert( mFilterModel->rowToId( currentIndex ) );
1224 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1225 if ( canvas )
1226 {
1227 canvas->zoomToFeatureIds( mLayer, ids );
1228 }
1229}
1230
1231void QgsDualView::panToCurrentFeature()
1232{
1233 const QModelIndex currentIndex = mTableView->currentIndex();
1234 if ( !currentIndex.isValid() )
1235 {
1236 return;
1237 }
1238
1239 QgsFeatureIds ids;
1240 ids.insert( mFilterModel->rowToId( currentIndex ) );
1241 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1242 if ( canvas )
1243 {
1244 canvas->panToFeatureIds( mLayer, ids );
1245 }
1246}
1247
1248void QgsDualView::flashCurrentFeature()
1249{
1250 const QModelIndex currentIndex = mTableView->currentIndex();
1251 if ( !currentIndex.isValid() )
1252 {
1253 return;
1254 }
1255
1256 QgsFeatureIds ids;
1257 ids.insert( mFilterModel->rowToId( currentIndex ) );
1258 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1259 if ( canvas )
1260 {
1261 canvas->flashFeatureIds( mLayer, ids );
1262 }
1263}
1264
1265void QgsDualView::rebuildFullLayerCache()
1266{
1267 connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
1268 connect( mLayerCache, &QgsVectorLayerCache::finished, this, &QgsDualView::finished, Qt::UniqueConnection );
1269
1270 mLayerCache->setFullCache( true );
1271}
1272
1273void QgsDualView::previewExpressionChanged( const QString &expression )
1274{
1275 mLayer->setDisplayExpression( expression );
1276}
1277
1278void QgsDualView::onSortColumnChanged()
1279{
1280 QgsAttributeTableConfig cfg = attributeTableConfig();
1281 if ( cfg.sortExpression() != mFilterModel->sortExpression() || cfg.sortOrder() != mFilterModel->sortOrder() )
1282 {
1283 cfg.setSortExpression( mFilterModel->sortExpression() );
1284 cfg.setSortOrder( mFilterModel->sortOrder() );
1286 }
1287}
1288
1289void QgsDualView::updateSelectedFeatures()
1290{
1291 QgsFeatureRequest r = mMasterModel->request();
1293 return; // already requested all features
1294
1295 r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
1296 mMasterModel->setRequest( r );
1297 mMasterModel->loadLayer();
1298 emit filterChanged();
1299}
1300
1301void QgsDualView::updateEditedAddedFeatures()
1302{
1303 QgsFeatureRequest r = mMasterModel->request();
1305 return; // already requested all features
1306
1307 r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
1308 mMasterModel->setRequest( r );
1309 mMasterModel->loadLayer();
1310 emit filterChanged();
1311}
1312
1313void QgsDualView::extentChanged()
1314{
1315 QgsFeatureRequest r = mMasterModel->request();
1316 if ( mFilterModel->mapCanvas() && ( r.filterType() != Qgis::FeatureRequestFilterType::NoFilter || !r.filterRect().isNull() ) )
1317 {
1318 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1319 r.setFilterRect( rect );
1320 mMasterModel->setRequest( r );
1321 mMasterModel->loadLayer();
1322 }
1323 emit filterChanged();
1324}
1325
1326void QgsDualView::featureFormAttributeChanged( const QString &attribute, const QVariant &value, bool attributeChanged )
1327{
1328 Q_UNUSED( attribute )
1329 Q_UNUSED( value )
1330 if ( attributeChanged )
1331 {
1332 mFeatureListView->setCurrentFeatureEdited( true );
1333 mAttributeForm->save();
1334 }
1335}
1336
1338{
1339 mFilterModel->setFilteredFeatures( filteredFeatures );
1340}
1341
1342void QgsDualView::filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context )
1343{
1344 mFilterModel->setFilterExpression( filterExpression, context );
1345 mFilterModel->filterFeatures();
1346}
1347
1348
1350{
1351 mMasterModel->setRequest( request );
1352}
1353
1355{
1356 mTableView->setFeatureSelectionManager( featureSelectionManager );
1357 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1358
1359 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
1360 delete mFeatureSelectionManager;
1361
1362 mFeatureSelectionManager = featureSelectionManager;
1363}
1364
1366{
1367 mConfig = config;
1368 mConfig.update( mLayer->fields() );
1369 mLayer->setAttributeTableConfig( mConfig );
1370 mFilterModel->setAttributeTableConfig( mConfig );
1371 mTableView->setAttributeTableConfig( mConfig );
1372 const QgsAttributeList attributes { requiredAttributes( mLayer ) };
1373 QgsFeatureRequest request = mMasterModel->request();
1374 // if the sort expression needs geometry reset the flag
1375 if ( QgsExpression( config.sortExpression() ).needsGeometry() )
1376 {
1377 mLayerCache->setCacheGeometry( true );
1378 request.setFlags( request.flags().setFlag( Qgis::FeatureRequestFlag::NoGeometry, false ) );
1379 }
1380 request.setSubsetOfAttributes( attributes );
1381 mMasterModel->setRequest( request );
1382 mLayerCache->setCacheSubsetOfAttributes( attributes );
1383}
1384
1385void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder )
1386{
1387 if ( sortExpression.isNull() )
1388 mFilterModel->sort( -1 );
1389 else
1390 mFilterModel->sort( sortExpression, sortOrder );
1391
1392 mConfig.setSortExpression( sortExpression );
1393 mConfig.setSortOrder( sortOrder );
1394 setAttributeTableConfig( mConfig );
1395}
1396
1398{
1399 return mFilterModel->sortExpression();
1400}
1401
1403{
1404 return mConfig;
1405}
1406
1407void QgsDualView::progress( int i, bool &cancel )
1408{
1409 if ( !mProgressDlg )
1410 {
1411 mProgressDlg = new QProgressDialog( tr( "Loading features…" ), tr( "Abort" ), 0, 0, this );
1412 mProgressDlg->setWindowTitle( tr( "Attribute Table" ) );
1413 mProgressDlg->setWindowModality( Qt::WindowModal );
1414 mProgressDlg->show();
1415 }
1416
1417 mProgressDlg->setLabelText( tr( "%L1 features loaded." ).arg( i ) );
1418 QCoreApplication::processEvents();
1419
1420 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1421}
1422
1423void QgsDualView::finished()
1424{
1425 delete mProgressDlg;
1426 mProgressDlg = nullptr;
1427}
1428
1429/*
1430 * QgsAttributeTableAction
1431 */
1432
1434{
1435 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1436}
1437
1439{
1440 QgsFeatureIds editedIds;
1441 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1442 mDualView->setCurrentEditSelection( editedIds );
1443 mDualView->setView( QgsDualView::AttributeEditor );
1444}
1445
1446/*
1447 * QgsAttributeTableMapLayerAction
1448 */
1449
1451{
1453 mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx, context );
1454}
@ SelectAtId
Fast access to features using their ID.
Definition qgis.h:507
@ NoFilter
No filter is applied.
Definition qgis.h:2223
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
@ NoFilter
No spatial filtering of features.
Definition qgis.h:2252
@ MultipleFeatures
Action targets multiple features from a layer.
Definition qgis.h:4628
@ Layer
Action targets a complete layer.
Definition qgis.h:4626
@ SingleFeature
Action targets a single feature from a layer.
Definition qgis.h:4627
@ Expression
Field is calculated from an expression.
Definition qgis.h:1709
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=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with a layer.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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:58
QgsFeatureId id
Definition qgsfeature.h:66
A widget for customizing conditional formatting options.
void rulesUpdated(const QString &fieldName)
Emitted when the conditional styling rules are updated.
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:106
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition qgsgui.cpp:136
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition qgsgui.cpp:146
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:65
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:6511
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QList< int > QgsAttributeList
Definition qgsfield.h:28
Defines the configuration of a column in the attribute table.