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