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