QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 r = mMasterModel->request();
336 const bool needsGeometry = filterMode == QgsAttributeTableFilterModel::ShowVisible;
337
338 const bool requiresTableReload = ( r.filterType() != QgsFeatureRequest::FilterNone || r.spatialFilterType() != Qgis::SpatialFilterType::NoFilter ) // previous request was subset
339 || ( needsGeometry && r.flags() & QgsFeatureRequest::NoGeometry ) // no geometry for last request
340 || ( mMasterModel->rowCount() == 0 ); // no features
341
342 if ( !needsGeometry )
344 else
348 r.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 r.setFilterRect( rect );
359 }
361 break;
362
364 r.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 r.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 r.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( r );
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->setCacheGeometry( cacheGeometry );
440 if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayer->dataProvider()->capabilities() ) )
441 {
442 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsDualView::rebuildFullLayerCache );
443 rebuildFullLayerCache();
444 }
445}
446
447void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, bool loadFeatures )
448{
449 delete mFeatureListModel;
450 delete mFilterModel;
451 delete mMasterModel;
452
453 mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
454 mMasterModel->setRequest( request );
455 mMasterModel->setEditorContext( mEditorContext );
456 mMasterModel->setExtraColumns( 1 ); // Add one extra column which we can "abuse" as an action column
457
458 connect( mMasterModel, &QgsAttributeTableModel::progress, this, &QgsDualView::progress );
459 connect( mMasterModel, &QgsAttributeTableModel::finished, this, &QgsDualView::finished );
460
462
463 if ( loadFeatures )
464 mMasterModel->loadLayer();
465
466 mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
467
468 // The following connections to invalidate() are necessary to keep the filter model in sync
469 // see regression https://github.com/qgis/QGIS/issues/23890
470 connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
471 connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
472
474
475 mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
476 mFeatureListModel->setSortByDisplayExpression( true );
477}
478
479void QgsDualView::restoreRecentDisplayExpressions()
480{
481 const QVariantList previewExpressions = mLayer->customProperty( QStringLiteral( "dualview/previewExpressions" ) ).toList();
482
483 for ( const QVariant &previewExpression : previewExpressions )
484 insertRecentlyUsedDisplayExpression( previewExpression.toString() );
485}
486
487void QgsDualView::saveRecentDisplayExpressions() const
488{
489 if ( ! mLayer )
490 {
491 return;
492 }
493 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
494
495 // Remove existing same action
496 int index = actions.indexOf( mLastDisplayExpressionAction );
497 if ( index != -1 )
498 {
499 QVariantList previewExpressions;
500 for ( ; index < actions.length(); ++index )
501 {
502 QAction *action = actions.at( index );
503 previewExpressions << action->property( "previewExpression" );
504 }
505
506 mLayer->setCustomProperty( QStringLiteral( "dualview/previewExpressions" ), previewExpressions );
507 }
508}
509
510void QgsDualView::setDisplayExpression( const QString &expression )
511{
512 mDisplayExpression = expression;
513 insertRecentlyUsedDisplayExpression( expression );
514}
515
516void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression )
517{
518 const QList<QAction *> actions = mFeatureListPreviewButton->actions();
519
520 // Remove existing same action
521 const int index = actions.indexOf( mLastDisplayExpressionAction );
522 if ( index != -1 )
523 {
524 for ( int i = 0; index + i < actions.length(); ++i )
525 {
526 QAction *action = actions.at( index );
527 if ( action->text() == expression || i >= 9 )
528 {
529 if ( action == mLastDisplayExpressionAction )
530 mLastDisplayExpressionAction = nullptr;
531 mFeatureListPreviewButton->removeAction( action );
532 }
533 else
534 {
535 if ( !mLastDisplayExpressionAction )
536 mLastDisplayExpressionAction = action;
537 }
538 }
539 }
540
541 QString name = expression;
542 QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionPreview.svg" ) );
543 if ( expression.startsWith( QLatin1String( "COALESCE( \"" ) ) && expression.endsWith( QLatin1String( ", '<NULL>' )" ) ) )
544 {
545 name = expression.mid( 11, expression.length() - 24 ); // Numbers calculated from the COALESCE / <NULL> parts
546
547 const int fieldIndex = mLayer->fields().indexOf( name );
548 if ( fieldIndex != -1 )
549 {
550 name = mLayer->attributeDisplayName( fieldIndex );
551 icon = mLayer->fields().iconForField( fieldIndex );
552 }
553 else
554 {
555 name = expression;
556 }
557 }
558
559 QAction *previewAction = new QAction( icon, name, mFeatureListPreviewButton );
560 previewAction->setProperty( "previewExpression", expression );
561 connect( previewAction, &QAction::triggered, this, [expression, this]( bool )
562 {
563 setDisplayExpression( expression );
564 mFeatureListPreviewButton->setText( expression );
565 }
566 );
567
568 mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
569 mLastDisplayExpressionAction = previewAction;
570}
571
572void QgsDualView::updateEditSelectionProgress( int progress, int count )
573{
574 mProgressCount->setText( QStringLiteral( "%1 / %2" ).arg( progress + 1 ).arg( count ) );
575 mPreviousFeatureButton->setEnabled( progress > 0 );
576 mNextFeatureButton->setEnabled( progress + 1 < count );
577 mFirstFeatureButton->setEnabled( progress > 0 );
578 mLastFeatureButton->setEnabled( progress + 1 < count );
579 if ( mAttributeForm )
580 {
581 mAttributeForm->setVisible( count > 0 );
582 }
583}
584
585void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
586{
587 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
588 if ( canvas && view() == AttributeEditor && featureset != mLastFeatureSet )
589 {
590 if ( mBrowsingAutoPanScaleAllowed )
591 {
592 if ( mAutoPanButton->isChecked() )
593 QTimer::singleShot( 0, this, [ = ]()
594 {
595 canvas->panToFeatureIds( mLayer, featureset, false );
596 } );
597 else if ( mAutoZoomButton->isChecked() )
598 QTimer::singleShot( 0, this, [ = ]()
599 {
600 canvas->zoomToFeatureIds( mLayer, featureset );
601 } );
602 }
603 if ( mFlashButton->isChecked() )
604 QTimer::singleShot( 0, this, [ = ]()
605 {
606 canvas->flashFeatureIds( mLayer, featureset );
607 } );
608 mLastFeatureSet = featureset;
609 }
610}
611
612void QgsDualView::setBrowsingAutoPanScaleAllowed( bool allowed )
613{
614 if ( mBrowsingAutoPanScaleAllowed == allowed )
615 return;
616
617 mBrowsingAutoPanScaleAllowed = allowed;
618
619 mAutoPanButton->setEnabled( allowed );
620 mAutoZoomButton->setEnabled( allowed );
621
622 const QString disabledHint = tr( "(disabled when attribute table only shows features visible in the current map canvas extent)" );
623
624 mAutoPanButton->setToolTip( tr( "Automatically pan to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
625 mAutoZoomButton->setToolTip( tr( "Automatically zoom to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
626}
627
628void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
629{
630 if ( button == mAutoPanButton && checked )
631 {
632 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
633 mAutoZoomButton->setChecked( false );
634 }
635 else if ( button == mAutoZoomButton && checked )
636 {
637 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
638 mAutoPanButton->setChecked( false );
639 }
640 else
641 {
642 QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
643 }
644
645 if ( checked && mLayer->isSpatial() )
646 panOrZoomToFeature( mFeatureListView->currentEditSelection() );
647}
648
649void QgsDualView::flashButtonClicked( bool clicked )
650{
651 QgsSettings().setValue( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), clicked );
652 if ( !clicked )
653 return;
654
655 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
656
657 if ( canvas )
658 canvas->flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
659}
660
661void QgsDualView::filterError( const QString &errorMessage )
662{
663 if ( mEditorContext.mainMessageBar() )
664 {
665 mEditorContext.mainMessageBar()->pushWarning( tr( "An error occurred while filtering features" ), errorMessage );
666 }
667}
668
669void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
670{
671 if ( !mAttributeForm )
672 return;
673
674 if ( mLayer->isEditable() && !mAttributeForm->save() )
675 ok = false;
676}
677
678void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
679{
680 if ( !mAttributeForm )
681 {
682 initAttributeForm( feat );
683 }
684 else if ( !mLayer->isEditable() || mAttributeForm->save() )
685 {
686 mAttributeForm->setFeature( feat );
687 QgsFeatureIds featureset;
688 featureset << feat.id();
689 setCurrentEditSelection( featureset );
690
691 if ( mLayer->isSpatial() )
692 panOrZoomToFeature( featureset );
693
694 }
695 else
696 {
697 // Couldn't save feature
698 }
699}
700
702{
703 mFeatureListView->setCurrentFeatureEdited( false );
704 mFeatureListView->setEditSelection( fids );
705}
706
708{
709 return mAttributeForm ? mAttributeForm->save() : false;
710}
711
713{
714 mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
715}
716
718{
719 if ( !mAttributeForm )
720 return;
721
722 if ( enabled )
723 {
724 mPreviousView = view();
726 }
727 else
728 {
729 setView( mPreviousView );
730 }
731
733}
734
736{
737 if ( !mAttributeForm )
738 return;
739
740 if ( enabled )
741 {
744 mAttributeForm->setVisible( true );
745 }
746 else
747 {
749 mAttributeForm->setVisible( mFilterModel->rowCount( ) > 0 );
750 }
751
752}
753
754void QgsDualView::previewExpressionBuilder()
755{
756 // Show expression builder
758
759 QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
760 dlg.setWindowTitle( tr( "Expression Based Preview" ) );
761 dlg.setExpressionText( mFeatureListView->displayExpression() );
762
763 if ( dlg.exec() == QDialog::Accepted )
764 {
765 mFeatureListView->setDisplayExpression( dlg.expressionText() );
766 mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
767 mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
768 }
769
770 setDisplayExpression( mFeatureListView->displayExpression() );
771}
772
773void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
774{
775 if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
776 {
777 QMessageBox::warning( this,
778 tr( "Column Preview" ),
779 tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
780 .arg( previewAction->text(), mFeatureListView->parserErrorString() )
781 );
782 }
783 else
784 {
785 mFeatureListPreviewButton->setText( previewAction->text() );
786 mFeatureListPreviewButton->setIcon( previewAction->icon() );
787 mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
788 }
789
790 setDisplayExpression( mFeatureListView->displayExpression() );
791}
792
794{
795 return mMasterModel->rowCount();
796}
797
799{
800 return mFilterModel->rowCount();
801}
802
804{
805 const QModelIndex currentIndex = mTableView->currentIndex();
806 if ( !currentIndex.isValid() )
807 {
808 return;
809 }
810
811 const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
812 QApplication::clipboard()->setText( var.toString() );
813}
814
816{
817 if ( mProgressDlg )
818 mProgressDlg->cancel();
819}
820
821void QgsDualView::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
822{
823 if ( mAttributeForm )
824 {
825 mAttributeForm->parentFormValueChanged( attribute, newValue );
826 }
827}
828
829void QgsDualView::hideEvent( QHideEvent *event )
830{
831 Q_UNUSED( event )
832 saveRecentDisplayExpressions();
833}
834
835void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &masterIndex )
836{
837 if ( !menu )
838 {
839 return;
840 }
841
842 QAction *copyContentAction = menu->addAction( tr( "Copy Cell Content" ) );
843 menu->addAction( copyContentAction );
844 connect( copyContentAction, &QAction::triggered, this, [masterIndex, this]
845 {
846 const QVariant var = mMasterModel->data( masterIndex, Qt::DisplayRole );
847 QApplication::clipboard()->setText( var.toString() );
848 } );
849
850 QgsVectorLayer *vl = mFilterModel->layer();
851 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
852 if ( canvas && vl && vl->isSpatial() )
853 {
854 QAction *zoomToFeatureAction = menu->addAction( tr( "Zoom to Feature" ) );
855 connect( zoomToFeatureAction, &QAction::triggered, this, &QgsDualView::zoomToCurrentFeature );
856
857 QAction *panToFeatureAction = menu->addAction( tr( "Pan to Feature" ) );
858 connect( panToFeatureAction, &QAction::triggered, this, &QgsDualView::panToCurrentFeature );
859
860 QAction *flashFeatureAction = menu->addAction( tr( "Flash Feature" ) );
861 connect( flashFeatureAction, &QAction::triggered, this, &QgsDualView::flashCurrentFeature );
862 }
863
864 //add user-defined actions to context menu
865 const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Field" ) );
866 if ( !actions.isEmpty() )
867 {
868 QAction *a = menu->addAction( tr( "Run Layer Action" ) );
869 a->setEnabled( false );
870
871 for ( const QgsAction &action : actions )
872 {
873 if ( !action.runable() )
874 continue;
875
876 if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
877 continue;
878
879 QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, action.id(), masterIndex );
880 menu->addAction( action.name(), a, &QgsAttributeTableAction::execute );
881 }
882 }
883 const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
884 if ( ! rowSourceIndex.isValid() )
885 {
886 return;
887 }
888
889 //add actions from QgsMapLayerActionRegistry to context menu
891 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::Layer | Qgis::MapLayerActionTarget::SingleFeature, context );
892 if ( !registeredActions.isEmpty() )
893 {
894 //add a separator between user defined and standard actions
895 menu->addSeparator();
896
897 for ( QgsMapLayerAction *action : registeredActions )
898 {
899 QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction( action->text(), this, action, rowSourceIndex );
900 menu->addAction( action->text(), a, &QgsAttributeTableMapLayerAction::execute );
901 }
902 }
903
904 // entries for multiple features layer actions
905 // only show if the context menu is shown over a selected row
906 const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
907 if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
908 {
909 const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, Qgis::MapLayerActionTarget::MultipleFeatures, context );
910 if ( !registeredActions.isEmpty() )
911 {
912 menu->addSeparator();
913 QAction *action = menu->addAction( tr( "Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
914 action->setEnabled( false );
915
917 for ( QgsMapLayerAction *action : registeredActions )
918 {
919 menu->addAction( action->text(), action, [ = ]()
920 {
921 Q_NOWARN_DEPRECATED_PUSH
922 action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );
923 Q_NOWARN_DEPRECATED_POP
924 action->triggerForFeatures( mLayer, mLayer->selectedFeatures(), context );
925 } );
926 }
927 }
928 }
929
930 menu->addSeparator();
931 QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open Form" ), this, QUuid(), rowSourceIndex );
932 menu->addAction( tr( "Open Form…" ), a, &QgsAttributeTableAction::featureForm );
933}
934
935
936void QgsDualView::widgetWillShowContextMenu( QgsActionMenu *menu, const QModelIndex &atIndex )
937{
938 emit showContextMenuExternally( menu, mFilterModel->rowToId( atIndex ) );
939}
940
941
942void QgsDualView::showViewHeaderMenu( QPoint point )
943{
944 const int col = mTableView->columnAt( point.x() );
945
946 delete mHorizontalHeaderMenu;
947 mHorizontalHeaderMenu = new QMenu( this );
948
949 QAction *hide = new QAction( tr( "&Hide Column" ), mHorizontalHeaderMenu );
950 connect( hide, &QAction::triggered, this, &QgsDualView::hideColumn );
951 hide->setData( col );
952 mHorizontalHeaderMenu->addAction( hide );
953 QAction *setWidth = new QAction( tr( "&Set Width…" ), mHorizontalHeaderMenu );
954 connect( setWidth, &QAction::triggered, this, &QgsDualView::resizeColumn );
955 setWidth->setData( col );
956 mHorizontalHeaderMenu->addAction( setWidth );
957
958 QAction *setWidthAllColumns = new QAction( tr( "&Set All Column Widths…" ), mHorizontalHeaderMenu );
959 connect( setWidthAllColumns, &QAction::triggered, this, &QgsDualView::resizeAllColumns );
960 setWidthAllColumns->setData( col );
961 mHorizontalHeaderMenu->addAction( setWidthAllColumns );
962
963 QAction *optimizeWidth = new QAction( tr( "&Autosize" ), mHorizontalHeaderMenu );
964 connect( optimizeWidth, &QAction::triggered, this, &QgsDualView::autosizeColumn );
965 optimizeWidth->setData( col );
966 mHorizontalHeaderMenu->addAction( optimizeWidth );
967
968 QAction *optimizeWidthAllColumns = new QAction( tr( "&Autosize All Columns" ), mHorizontalHeaderMenu );
969 connect( optimizeWidthAllColumns, &QAction::triggered, this, &QgsDualView::autosizeAllColumns );
970 mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
971
972
973 mHorizontalHeaderMenu->addSeparator();
974 QAction *organize = new QAction( tr( "&Organize Columns…" ), mHorizontalHeaderMenu );
975 connect( organize, &QAction::triggered, this, &QgsDualView::organizeColumns );
976 mHorizontalHeaderMenu->addAction( organize );
977 QAction *sort = new QAction( tr( "&Sort…" ), mHorizontalHeaderMenu );
978 connect( sort, &QAction::triggered, this, [ = ]() {modifySort();} );
979 mHorizontalHeaderMenu->addAction( sort );
980
981 mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
982}
983
984void QgsDualView::organizeColumns()
985{
986 if ( !mLayer )
987 {
988 return;
989 }
990
991 QgsOrganizeTableColumnsDialog dialog( mLayer, attributeTableConfig(), this );
992 if ( dialog.exec() == QDialog::Accepted )
993 {
994 const QgsAttributeTableConfig config = dialog.config();
995 setAttributeTableConfig( config );
996 }
997}
998
999void QgsDualView::tableColumnResized( int column, int width )
1000{
1001 QgsAttributeTableConfig config = mConfig;
1002 const int sourceCol = config.mapVisibleColumnToIndex( column );
1003 if ( sourceCol >= 0 && config.columnWidth( sourceCol ) != width )
1004 {
1005 config.setColumnWidth( sourceCol, width );
1006 setAttributeTableConfig( config );
1007 }
1008}
1009
1010void QgsDualView::hideColumn()
1011{
1012 QAction *action = qobject_cast<QAction *>( sender() );
1013 const int col = action->data().toInt();
1014 QgsAttributeTableConfig config = mConfig;
1015 const int sourceCol = mConfig.mapVisibleColumnToIndex( col );
1016 if ( sourceCol >= 0 )
1017 {
1018 config.setColumnHidden( sourceCol, true );
1019 setAttributeTableConfig( config );
1020 }
1021}
1022
1023void QgsDualView::resizeColumn()
1024{
1025 QAction *action = qobject_cast<QAction *>( sender() );
1026 const int col = action->data().toInt();
1027 if ( col < 0 )
1028 return;
1029
1030 QgsAttributeTableConfig config = mConfig;
1031 const int sourceCol = config.mapVisibleColumnToIndex( col );
1032 if ( sourceCol >= 0 )
1033 {
1034 bool ok = false;
1035 const int width = QInputDialog::getInt( this, tr( "Set column width" ), tr( "Enter column width" ),
1036 mTableView->columnWidth( col ),
1037 0, 1000, 10, &ok );
1038 if ( ok )
1039 {
1040 config.setColumnWidth( sourceCol, width );
1041 setAttributeTableConfig( config );
1042 }
1043 }
1044}
1045
1046void QgsDualView::resizeAllColumns()
1047{
1048 QAction *action = qobject_cast<QAction *>( sender() );
1049 const int col = action->data().toInt();
1050 if ( col < 0 )
1051 return;
1052
1053 QgsAttributeTableConfig config = mConfig;
1054
1055 bool ok = false;
1056 const int width = QInputDialog::getInt( this, tr( "Set Column Width" ), tr( "Enter column width" ),
1057 mTableView->columnWidth( col ),
1058 1, 1000, 10, &ok );
1059 if ( ok )
1060 {
1061 const int colCount = mTableView->model()->columnCount();
1062 if ( colCount > 0 )
1063 {
1064 for ( int i = 0; i < colCount; i++ )
1065 {
1066 config.setColumnWidth( i, width );
1067 }
1068 setAttributeTableConfig( config );
1069 }
1070 }
1071}
1072
1073void QgsDualView::autosizeColumn()
1074{
1075 QAction *action = qobject_cast<QAction *>( sender() );
1076 const int col = action->data().toInt();
1077 mTableView->resizeColumnToContents( col );
1078}
1079
1080void QgsDualView::autosizeAllColumns()
1081{
1082 mTableView->resizeColumnsToContents();
1083}
1084
1085bool QgsDualView::modifySort()
1086{
1087 if ( !mLayer )
1088 return false;
1089
1090 QgsAttributeTableConfig config = mConfig;
1091
1092 QDialog orderByDlg;
1093 orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) );
1094 QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1095 QGridLayout *layout = new QGridLayout();
1096 connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1097 connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1098 orderByDlg.setLayout( layout );
1099
1100 QGroupBox *sortingGroupBox = new QGroupBox();
1101 sortingGroupBox->setTitle( tr( "Defined sort order in attribute table" ) );
1102 sortingGroupBox->setCheckable( true );
1103 sortingGroupBox->setChecked( !sortExpression().isEmpty() );
1104 layout->addWidget( sortingGroupBox );
1105 sortingGroupBox->setLayout( new QGridLayout() );
1106
1107 QgsExpressionBuilderWidget *expressionBuilder = new QgsExpressionBuilderWidget();
1109
1110 expressionBuilder->initWithLayer( mLayer, context, QStringLiteral( "generic" ) );
1111 expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
1112
1113 sortingGroupBox->layout()->addWidget( expressionBuilder );
1114
1115 QCheckBox *cbxSortAscending = new QCheckBox( tr( "Sort ascending" ) );
1116 cbxSortAscending->setChecked( config.sortOrder() == Qt::AscendingOrder );
1117 sortingGroupBox->layout()->addWidget( cbxSortAscending );
1118
1119 layout->addWidget( dialogButtonBox );
1120 if ( orderByDlg.exec() )
1121 {
1122 const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1123 if ( sortingGroupBox->isChecked() )
1124 {
1125 setSortExpression( expressionBuilder->expressionText(), sortOrder );
1126 config.setSortExpression( expressionBuilder->expressionText() );
1127 config.setSortOrder( sortOrder );
1128 }
1129 else
1130 {
1131 setSortExpression( QString(), sortOrder );
1132 config.setSortExpression( QString() );
1133 }
1134
1135 setAttributeTableConfig( config );
1136 return true;
1137 }
1138 else
1139 {
1140 return false;
1141 }
1142
1143}
1144
1145void QgsDualView::zoomToCurrentFeature()
1146{
1147 const QModelIndex currentIndex = mTableView->currentIndex();
1148 if ( !currentIndex.isValid() )
1149 {
1150 return;
1151 }
1152
1153 QgsFeatureIds ids;
1154 ids.insert( mFilterModel->rowToId( currentIndex ) );
1155 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1156 if ( canvas )
1157 {
1158 canvas->zoomToFeatureIds( mLayer, ids );
1159 }
1160}
1161
1162void QgsDualView::panToCurrentFeature()
1163{
1164 const QModelIndex currentIndex = mTableView->currentIndex();
1165 if ( !currentIndex.isValid() )
1166 {
1167 return;
1168 }
1169
1170 QgsFeatureIds ids;
1171 ids.insert( mFilterModel->rowToId( currentIndex ) );
1172 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1173 if ( canvas )
1174 {
1175 canvas->panToFeatureIds( mLayer, ids );
1176 }
1177}
1178
1179void QgsDualView::flashCurrentFeature()
1180{
1181 const QModelIndex currentIndex = mTableView->currentIndex();
1182 if ( !currentIndex.isValid() )
1183 {
1184 return;
1185 }
1186
1187 QgsFeatureIds ids;
1188 ids.insert( mFilterModel->rowToId( currentIndex ) );
1189 QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1190 if ( canvas )
1191 {
1192 canvas->flashFeatureIds( mLayer, ids );
1193 }
1194}
1195
1196void QgsDualView::rebuildFullLayerCache()
1197{
1198 connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
1199 connect( mLayerCache, &QgsVectorLayerCache::finished, this, &QgsDualView::finished, Qt::UniqueConnection );
1200
1201 mLayerCache->setFullCache( true );
1202}
1203
1204void QgsDualView::previewExpressionChanged( const QString &expression )
1205{
1206 mLayer->setDisplayExpression( expression );
1207}
1208
1209void QgsDualView::onSortColumnChanged()
1210{
1212 if ( cfg.sortExpression() != mFilterModel->sortExpression() ||
1213 cfg.sortOrder() != mFilterModel->sortOrder() )
1214 {
1215 cfg.setSortExpression( mFilterModel->sortExpression() );
1216 cfg.setSortOrder( mFilterModel->sortOrder() );
1218 }
1219}
1220
1221void QgsDualView::updateSelectedFeatures()
1222{
1223 QgsFeatureRequest r = mMasterModel->request();
1225 return; // already requested all features
1226
1227 r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
1228 mMasterModel->setRequest( r );
1229 mMasterModel->loadLayer();
1230 emit filterChanged();
1231}
1232
1233void QgsDualView::updateEditedAddedFeatures()
1234{
1235 QgsFeatureRequest r = mMasterModel->request();
1237 return; // already requested all features
1238
1239 r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
1240 mMasterModel->setRequest( r );
1241 mMasterModel->loadLayer();
1242 emit filterChanged();
1243}
1244
1245void QgsDualView::extentChanged()
1246{
1247 QgsFeatureRequest r = mMasterModel->request();
1248 if ( mFilterModel->mapCanvas() && ( r.filterType() != QgsFeatureRequest::FilterNone || !r.filterRect().isNull() ) )
1249 {
1250 const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1251 r.setFilterRect( rect );
1252 mMasterModel->setRequest( r );
1253 mMasterModel->loadLayer();
1254 }
1255 emit filterChanged();
1256}
1257
1258void QgsDualView::featureFormAttributeChanged( const QString &attribute, const QVariant &value, bool attributeChanged )
1259{
1260 Q_UNUSED( attribute )
1261 Q_UNUSED( value )
1262 if ( attributeChanged )
1263 {
1264 mFeatureListView->setCurrentFeatureEdited( true );
1265 mAttributeForm->save();
1266 }
1267}
1268
1270{
1271 mFilterModel->setFilteredFeatures( filteredFeatures );
1272}
1273
1274void QgsDualView::filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context )
1275{
1276 mFilterModel->setFilterExpression( filterExpression, context );
1277 mFilterModel->filterFeatures();
1278}
1279
1280
1282{
1283 mMasterModel->setRequest( request );
1284}
1285
1287{
1288 mTableView->setFeatureSelectionManager( featureSelectionManager );
1289 mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1290
1291 if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
1292 delete mFeatureSelectionManager;
1293
1294 mFeatureSelectionManager = featureSelectionManager;
1295}
1296
1298{
1299 mConfig = config;
1300 mConfig.update( mLayer->fields() );
1301 mLayer->setAttributeTableConfig( mConfig );
1302 mFilterModel->setAttributeTableConfig( mConfig );
1303 mTableView->setAttributeTableConfig( mConfig );
1304}
1305
1306void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder )
1307{
1308 if ( sortExpression.isNull() )
1309 mFilterModel->sort( -1 );
1310 else
1311 mFilterModel->sort( sortExpression, sortOrder );
1312
1314 mConfig.setSortOrder( sortOrder );
1315 setAttributeTableConfig( mConfig );
1316}
1317
1319{
1320 return mFilterModel->sortExpression();
1321}
1322
1324{
1325 return mConfig;
1326}
1327
1328void QgsDualView::progress( int i, bool &cancel )
1329{
1330 if ( !mProgressDlg )
1331 {
1332 mProgressDlg = new QProgressDialog( tr( "Loading features…" ), tr( "Abort" ), 0, 0, this );
1333 mProgressDlg->setWindowTitle( tr( "Attribute Table" ) );
1334 mProgressDlg->setWindowModality( Qt::WindowModal );
1335 mProgressDlg->show();
1336 }
1337
1338 mProgressDlg->setLabelText( tr( "%L1 features loaded." ).arg( i ) );
1339 QCoreApplication::processEvents();
1340
1341 cancel = mProgressDlg && mProgressDlg->wasCanceled();
1342}
1343
1344void QgsDualView::finished()
1345{
1346 delete mProgressDlg;
1347 mProgressDlg = nullptr;
1348}
1349
1350/*
1351 * QgsAttributeTableAction
1352 */
1353
1355{
1356 mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1357}
1358
1360{
1361 QgsFeatureIds editedIds;
1362 editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1363 mDualView->setCurrentEditSelection( editedIds );
1365}
1366
1367/*
1368 * QgsAttributeTableMapLayerAction
1369 */
1370
1372{
1374 mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx, context );
1375}
@ 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.
Definition: qgsactionmenu.h:39
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.
Qt::SortOrder sortOrder() const
Gets the sort order.
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
QgsAttributeTableFilterModel::FilterMode filterMode()
Gets the filter mode.
Definition: qgsdualview.h:134
void setMultiEditEnabled(bool enabled)
Sets whether multi edit mode is enabled.
QgsFeatureIds filteredFeatures()
Gets a list of currently visible feature ids.
Definition: qgsdualview.h:179
void cancelProgress()
Cancel the progress dialog (if any)
void filterChanged()
Emitted whenever the filter changes.
QgsDualView(QWidget *parent=nullptr)
Constructor.
Definition: qgsdualview.cpp:51
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)
Definition: qgsdualview.h:186
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").
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 & 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
Q_GADGET 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:52
QString name
Definition: qgsfield.h:61
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:83
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition: qgsgui.cpp:113
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition: qgsgui.cpp:123
Is an interface class to abstract feature selection handling.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
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.
Definition: qgsrectangle.h:42
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
A QScrollArea subclass with improved scrolling behavior.
Definition: qgsscrollarea.h:42
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.
Definition: qgssettings.h:314
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.
Definition: qgssettings.h:262
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 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.
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:3435
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
const QgsField & field
Definition: qgsfield.h:501