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