QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
28 #include "qgsattributetablemodel.h"
29 #include "qgsdualview.h"
31 #include "qgsfeaturelistmodel.h"
33 #include "qgsmapcanvas.h"
35 #include "qgsmessagelog.h"
36 #include "qgsvectordataprovider.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 
51 QgsDualView::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 
121 void 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 = !( request.flags() & QgsFeatureRequest::NoGeometry )
145  || emptyForm.needsGeometry();
146 
147  initLayerCache( needsGeometry );
148  initModels( mapCanvas, request, loadFeatures );
149 
150  mConditionalFormatWidget->setLayer( mLayer );
151 
152  mTableView->setModel( mFilterModel );
153  mFeatureListView->setModel( mFeatureListModel );
154 
155  connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged );
156 
157  if ( mFeatureListPreviewButton->defaultAction() )
158  mFeatureListView->setDisplayExpression( mDisplayExpression );
159  else
160  columnBoxInit();
161 
162  // This slows down load of the attribute table heaps and uses loads of memory.
163  //mTableView->resizeColumnsToContents();
164 
165  if ( showFirstFeature && mFeatureListModel->rowCount( ) > 0 )
166  mFeatureListView->setEditSelection( QgsFeatureIds() << mFeatureListModel->data( mFeatureListModel->index( 0, 0 ), QgsFeatureListModel::Role::FeatureRole ).value<QgsFeature>().id() );
167 }
168 
169 void QgsDualView::initAttributeForm( const QgsFeature &feature )
170 {
171  Q_ASSERT( !mAttributeForm );
172 
173  mAttributeForm = new QgsAttributeForm( mLayer, feature, mEditorContext );
174  if ( !mEditorContext.parentContext() )
175  {
176  mAttributeEditorScrollArea = new QgsScrollArea();
177  mAttributeEditorScrollArea->setWidgetResizable( true );
178  mAttributeEditor->layout()->addWidget( mAttributeEditorScrollArea );
179  mAttributeEditorScrollArea->setWidget( mAttributeForm );
180  }
181  else
182  {
183  mAttributeEditor->layout()->addWidget( mAttributeForm );
184  }
185 
186  setAttributeTableConfig( mLayer->attributeTableConfig() );
187 
188  connect( mAttributeForm, &QgsAttributeForm::widgetValueChanged, this, &QgsDualView::featureFormAttributeChanged );
189  connect( mAttributeForm, &QgsAttributeForm::modeChanged, this, &QgsDualView::formModeChanged );
191  connect( mAttributeForm, &QgsAttributeForm::flashFeatures, this, [ = ]( const QString & filter )
192  {
193  if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
194  {
195  QgsMapCanvasUtils::flashMatchingFeatures( canvas, mLayer, filter );
196  }
197  } );
198  connect( mAttributeForm, &QgsAttributeForm::zoomToFeatures, this, [ = ]( const QString & filter )
199  {
200  if ( QgsMapCanvas *canvas = mFilterModel->mapCanvas() )
201  {
202  QgsMapCanvasUtils::zoomToMatchingFeatures( canvas, mLayer, filter );
203  }
204  } );
205 
206  connect( mMasterModel, &QgsAttributeTableModel::modelChanged, mAttributeForm, &QgsAttributeForm::refreshFeature );
207 }
208 
209 void QgsDualView::columnBoxInit()
210 {
211  // load fields
212  const QList<QgsField> fields = mLayer->fields().toList();
213 
214  const QString defaultField;
215 
216  mFeatureListPreviewButton->addAction( mActionExpressionPreview );
217  mFeatureListPreviewButton->addAction( mActionPreviewColumnsMenu );
218 
219  const auto constFields = fields;
220  for ( const QgsField &field : constFields )
221  {
222  const int fieldIndex = mLayer->fields().lookupField( field.name() );
223  if ( fieldIndex == -1 )
224  continue;
225 
226  const QString fieldName = field.name();
227  if ( QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldName ).type() != QLatin1String( "Hidden" ) )
228  {
229  const QIcon icon = mLayer->fields().iconForField( fieldIndex );
230  const QString text = mLayer->attributeDisplayName( fieldIndex );
231 
232  // Generate action for the preview popup button of the feature list
233  QAction *previewAction = new QAction( icon, text, mFeatureListPreviewButton );
234  connect( previewAction, &QAction::triggered, this, [ = ] { previewColumnChanged( previewAction, fieldName ); } );
235  mPreviewColumnsMenu->addAction( previewAction );
236 
237  if ( text == defaultField )
238  {
239  mFeatureListPreviewButton->setDefaultAction( previewAction );
240  }
241  }
242  }
243 
244  QMenu *sortMenu = new QMenu( this );
245  QAction *sortMenuAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "Sort…" ), this );
246  sortMenuAction->setMenu( sortMenu );
247 
248  QAction *sortByPreviewExpressionAsc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort.svg" ) ), tr( "By Preview Expression (ascending)" ), this );
249  connect( sortByPreviewExpressionAsc, &QAction::triggered, this, [ = ]()
250  {
251  mFeatureListModel->setSortByDisplayExpression( true, Qt::AscendingOrder );
252  } );
253  sortMenu->addAction( sortByPreviewExpressionAsc );
254  QAction *sortByPreviewExpressionDesc = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "sort-reverse.svg" ) ), tr( "By Preview Expression (descending)" ), this );
255  connect( sortByPreviewExpressionDesc, &QAction::triggered, this, [ = ]()
256  {
257  mFeatureListModel->setSortByDisplayExpression( true, Qt::DescendingOrder );
258  } );
259  sortMenu->addAction( sortByPreviewExpressionDesc );
260  QAction *sortByPreviewExpressionCustom = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "mIconExpressionPreview.svg" ) ), tr( "By Custom Expression" ), this );
261  connect( sortByPreviewExpressionCustom, &QAction::triggered, this, [ = ]()
262  {
263  if ( modifySort() )
264  mFeatureListModel->setSortByDisplayExpression( false );
265  } );
266  sortMenu->addAction( sortByPreviewExpressionCustom );
267 
268  mFeatureListPreviewButton->addAction( sortMenuAction );
269 
270  QAction *separator = new QAction( mFeatureListPreviewButton );
271  separator->setSeparator( true );
272  mFeatureListPreviewButton->addAction( separator );
273  restoreRecentDisplayExpressions();
274 
275  // If there is no single field found as preview
276  if ( !mFeatureListPreviewButton->defaultAction() )
277  {
278  mFeatureListView->setDisplayExpression( mLayer->displayExpression() );
279  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
280  const QString displayExpression = mFeatureListView->displayExpression();
281  setDisplayExpression( displayExpression.isEmpty() ? tr( "'[Please define preview text]'" ) : displayExpression );
282  }
283  else
284  {
285  mFeatureListPreviewButton->defaultAction()->trigger();
286  }
287 }
288 
290 {
291  setCurrentIndex( view );
292 }
293 
295 {
296  return static_cast< QgsDualView::ViewMode >( currentIndex() );
297 }
298 
300 {
301  // cleanup any existing connections
302  switch ( mFilterModel->filterMode() )
303  {
305  disconnect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
307  break;
308 
312  disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
313  break;
314 
317  disconnect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
318  disconnect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
319  break;
320 
322  disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
323  break;
324  }
325 
326  QgsFeatureRequest r = mMasterModel->request();
327  const bool needsGeometry = filterMode == QgsAttributeTableFilterModel::ShowVisible;
328 
329  const bool requiresTableReload = ( r.filterType() != QgsFeatureRequest::FilterNone || r.spatialFilterType() != Qgis::SpatialFilterType::NoFilter ) // previous request was subset
330  || ( needsGeometry && r.flags() & QgsFeatureRequest::NoGeometry ) // no geometry for last request
331  || ( mMasterModel->rowCount() == 0 ); // no features
332 
333  if ( !needsGeometry )
335  else
339  r.disableFilter();
340 
341  // setup new connections and filter request parameters
342  switch ( filterMode )
343  {
345  connect( mFilterModel->mapCanvas(), &QgsMapCanvas::extentsChanged, this, &QgsDualView::extentChanged );
346  if ( mFilterModel->mapCanvas() )
347  {
348  const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
349  r.setFilterRect( rect );
350  }
352  break;
353 
355  r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
357  connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
358  connect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
359  break;
360 
364  connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
365  break;
366 
368  connect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
369  r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
370  break;
371  }
372 
373  // disable the browsing auto pan/scale if the list only shows visible items
374  switch ( filterMode )
375  {
377  setBrowsingAutoPanScaleAllowed( false );
378  break;
379 
384  setBrowsingAutoPanScaleAllowed( true );
385  break;
386  }
387 
388  if ( requiresTableReload )
389  {
390  //disconnect the connections of the current (old) filtermode before reload
391  mFilterModel->disconnectFilterModeConnections();
392 
393  mMasterModel->setRequest( r );
394  whileBlocking( mLayerCache )->setCacheGeometry( needsGeometry );
395  mMasterModel->loadLayer();
396  }
397 
398  //update filter model
399  mFilterModel->setFilterMode( filterMode );
400  emit filterChanged();
401 }
402 
403 void QgsDualView::setSelectedOnTop( bool selectedOnTop )
404 {
405  mFilterModel->setSelectedOnTop( selectedOnTop );
406 }
407 
408 void QgsDualView::initLayerCache( bool cacheGeometry )
409 {
410  // Initialize the cache
411  const QgsSettings settings;
412  const int cacheSize = settings.value( QStringLiteral( "qgis/attributeTableRowCache" ), "10000" ).toInt();
413  mLayerCache = new QgsVectorLayerCache( mLayer, cacheSize, this );
414  mLayerCache->setCacheGeometry( cacheGeometry );
415  if ( 0 == cacheSize || 0 == ( QgsVectorDataProvider::SelectAtId & mLayer->dataProvider()->capabilities() ) )
416  {
417  connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsDualView::rebuildFullLayerCache );
418  rebuildFullLayerCache();
419  }
420 }
421 
422 void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, bool loadFeatures )
423 {
424  delete mFeatureListModel;
425  delete mFilterModel;
426  delete mMasterModel;
427 
428  mMasterModel = new QgsAttributeTableModel( mLayerCache, this );
429  mMasterModel->setRequest( request );
430  mMasterModel->setEditorContext( mEditorContext );
431  mMasterModel->setExtraColumns( 1 ); // Add one extra column which we can "abuse" as an action column
432 
433  connect( mMasterModel, &QgsAttributeTableModel::progress, this, &QgsDualView::progress );
434  connect( mMasterModel, &QgsAttributeTableModel::finished, this, &QgsDualView::finished );
435 
437 
438  if ( loadFeatures )
439  mMasterModel->loadLayer();
440 
441  mFilterModel = new QgsAttributeTableFilterModel( mapCanvas, mMasterModel, mMasterModel );
442 
443  // The following connections to invalidate() are necessary to keep the filter model in sync
444  // see regression https://github.com/qgis/QGIS/issues/23890
445  connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
446  connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
447 
449 
450  mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
451  mFeatureListModel->setSortByDisplayExpression( true );
452 }
453 
454 void QgsDualView::restoreRecentDisplayExpressions()
455 {
456  const QVariantList previewExpressions = mLayer->customProperty( QStringLiteral( "dualview/previewExpressions" ) ).toList();
457 
458  for ( const QVariant &previewExpression : previewExpressions )
459  insertRecentlyUsedDisplayExpression( previewExpression.toString() );
460 }
461 
462 void QgsDualView::saveRecentDisplayExpressions() const
463 {
464  if ( ! mLayer )
465  {
466  return;
467  }
468  const QList<QAction *> actions = mFeatureListPreviewButton->actions();
469 
470  // Remove existing same action
471  int index = actions.indexOf( mLastDisplayExpressionAction );
472  if ( index != -1 )
473  {
474  QVariantList previewExpressions;
475  for ( ; index < actions.length(); ++index )
476  {
477  QAction *action = actions.at( index );
478  previewExpressions << action->property( "previewExpression" );
479  }
480 
481  mLayer->setCustomProperty( QStringLiteral( "dualview/previewExpressions" ), previewExpressions );
482  }
483 }
484 
485 void QgsDualView::setDisplayExpression( const QString &expression )
486 {
487  mDisplayExpression = expression;
488  insertRecentlyUsedDisplayExpression( expression );
489 }
490 
491 void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression )
492 {
493  const QList<QAction *> actions = mFeatureListPreviewButton->actions();
494 
495  // Remove existing same action
496  const int index = actions.indexOf( mLastDisplayExpressionAction );
497  if ( index != -1 )
498  {
499  for ( int i = 0; index + i < actions.length(); ++i )
500  {
501  QAction *action = actions.at( index );
502  if ( action->text() == expression || i >= 9 )
503  {
504  if ( action == mLastDisplayExpressionAction )
505  mLastDisplayExpressionAction = nullptr;
506  mFeatureListPreviewButton->removeAction( action );
507  }
508  else
509  {
510  if ( !mLastDisplayExpressionAction )
511  mLastDisplayExpressionAction = action;
512  }
513  }
514  }
515 
516  QString name = expression;
517  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionPreview.svg" ) );
518  if ( expression.startsWith( QLatin1String( "COALESCE( \"" ) ) && expression.endsWith( QLatin1String( ", '<NULL>' )" ) ) )
519  {
520  name = expression.mid( 11, expression.length() - 24 ); // Numbers calculated from the COALESCE / <NULL> parts
521 
522  const int fieldIndex = mLayer->fields().indexOf( name );
523  if ( fieldIndex != -1 )
524  {
525  name = mLayer->attributeDisplayName( fieldIndex );
526  icon = mLayer->fields().iconForField( fieldIndex );
527  }
528  else
529  {
530  name = expression;
531  }
532  }
533 
534  QAction *previewAction = new QAction( icon, name, mFeatureListPreviewButton );
535  previewAction->setProperty( "previewExpression", expression );
536  connect( previewAction, &QAction::triggered, this, [expression, this]( bool )
537  {
538  setDisplayExpression( expression );
539  mFeatureListPreviewButton->setText( expression );
540  }
541  );
542 
543  mFeatureListPreviewButton->insertAction( mLastDisplayExpressionAction, previewAction );
544  mLastDisplayExpressionAction = previewAction;
545 }
546 
547 void QgsDualView::updateEditSelectionProgress( int progress, int count )
548 {
549  mProgressCount->setText( QStringLiteral( "%1 / %2" ).arg( progress + 1 ).arg( count ) );
550  mPreviousFeatureButton->setEnabled( progress > 0 );
551  mNextFeatureButton->setEnabled( progress + 1 < count );
552  mFirstFeatureButton->setEnabled( progress > 0 );
553  mLastFeatureButton->setEnabled( progress + 1 < count );
554  if ( mAttributeForm )
555  {
556  mAttributeForm->setVisible( count > 0 );
557  }
558 }
559 
560 void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
561 {
562  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
563  if ( canvas && view() == AttributeEditor && featureset != mLastFeatureSet )
564  {
565  if ( mBrowsingAutoPanScaleAllowed )
566  {
567  if ( mAutoPanButton->isChecked() )
568  QTimer::singleShot( 0, this, [ = ]()
569  {
570  canvas->panToFeatureIds( mLayer, featureset, false );
571  } );
572  else if ( mAutoZoomButton->isChecked() )
573  QTimer::singleShot( 0, this, [ = ]()
574  {
575  canvas->zoomToFeatureIds( mLayer, featureset );
576  } );
577  }
578  if ( mFlashButton->isChecked() )
579  QTimer::singleShot( 0, this, [ = ]()
580  {
581  canvas->flashFeatureIds( mLayer, featureset );
582  } );
583  mLastFeatureSet = featureset;
584  }
585 }
586 
587 void QgsDualView::setBrowsingAutoPanScaleAllowed( bool allowed )
588 {
589  if ( mBrowsingAutoPanScaleAllowed == allowed )
590  return;
591 
592  mBrowsingAutoPanScaleAllowed = allowed;
593 
594  mAutoPanButton->setEnabled( allowed );
595  mAutoZoomButton->setEnabled( allowed );
596 
597  const QString disabledHint = tr( "(disabled when attribute table only shows features visible in the current map canvas extent)" );
598 
599  mAutoPanButton->setToolTip( tr( "Automatically pan to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
600  mAutoZoomButton->setToolTip( tr( "Automatically zoom to the current feature" ) + ( allowed ? QString() : QString( ' ' ) + disabledHint ) );
601 }
602 
603 void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
604 {
605  if ( button == mAutoPanButton && checked )
606  {
607  QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
608  mAutoZoomButton->setChecked( false );
609  }
610  else if ( button == mAutoZoomButton && checked )
611  {
612  QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
613  mAutoPanButton->setChecked( false );
614  }
615  else
616  {
617  QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
618  }
619 
620  if ( checked && mLayer->isSpatial() )
621  panOrZoomToFeature( mFeatureListView->currentEditSelection() );
622 }
623 
624 void QgsDualView::flashButtonClicked( bool clicked )
625 {
626  QgsSettings().setValue( QStringLiteral( "/qgis/attributeTable/featureListHighlightFeature" ), clicked );
627  if ( !clicked )
628  return;
629 
630  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
631 
632  if ( canvas )
633  canvas->flashFeatureIds( mLayer, mFeatureListView->currentEditSelection() );
634 }
635 
636 void QgsDualView::filterError( const QString &errorMessage )
637 {
638  if ( mEditorContext.mainMessageBar() )
639  {
640  mEditorContext.mainMessageBar()->pushWarning( tr( "An error occurred while filtering features" ), errorMessage );
641  }
642 }
643 
644 void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
645 {
646  if ( !mAttributeForm )
647  return;
648 
649  if ( mLayer->isEditable() && !mAttributeForm->save() )
650  ok = false;
651 }
652 
653 void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
654 {
655  if ( !mAttributeForm )
656  {
657  initAttributeForm( feat );
658  }
659  else if ( !mLayer->isEditable() || mAttributeForm->save() )
660  {
661  mAttributeForm->setFeature( feat );
662  QgsFeatureIds featureset;
663  featureset << feat.id();
664  setCurrentEditSelection( featureset );
665 
666  if ( mLayer->isSpatial() )
667  panOrZoomToFeature( featureset );
668 
669  }
670  else
671  {
672  // Couldn't save feature
673  }
674 }
675 
677 {
678  mFeatureListView->setCurrentFeatureEdited( false );
679  mFeatureListView->setEditSelection( fids );
680 }
681 
683 {
684  return mAttributeForm ? mAttributeForm->save() : false;
685 }
686 
688 {
689  mConditionalFormatWidgetStack->setVisible( !mConditionalFormatWidgetStack->isVisible() );
690 }
691 
693 {
694  if ( !mAttributeForm )
695  return;
696 
697  if ( enabled )
698  {
699  mPreviousView = view();
701  }
702  else
703  {
704  setView( mPreviousView );
705  }
706 
708 }
709 
710 void QgsDualView::toggleSearchMode( bool enabled )
711 {
712  if ( !mAttributeForm )
713  return;
714 
715  if ( enabled )
716  {
719  mAttributeForm->setVisible( true );
720  }
721  else
722  {
724  mAttributeForm->setVisible( mFilterModel->rowCount( ) > 0 );
725  }
726 
727 }
728 
729 void QgsDualView::previewExpressionBuilder()
730 {
731  // Show expression builder
733 
734  QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
735  dlg.setWindowTitle( tr( "Expression Based Preview" ) );
736  dlg.setExpressionText( mFeatureListView->displayExpression() );
737 
738  if ( dlg.exec() == QDialog::Accepted )
739  {
740  mFeatureListView->setDisplayExpression( dlg.expressionText() );
741  mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
742  mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
743  }
744 
745  setDisplayExpression( mFeatureListView->displayExpression() );
746 }
747 
748 void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
749 {
750  if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
751  {
752  QMessageBox::warning( this,
753  tr( "Column Preview" ),
754  tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
755  .arg( previewAction->text(), mFeatureListView->parserErrorString() )
756  );
757  }
758  else
759  {
760  mFeatureListPreviewButton->setText( previewAction->text() );
761  mFeatureListPreviewButton->setIcon( previewAction->icon() );
762  mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
763  }
764 
765  setDisplayExpression( mFeatureListView->displayExpression() );
766 }
767 
769 {
770  return mMasterModel->rowCount();
771 }
772 
774 {
775  return mFilterModel->rowCount();
776 }
777 
779 {
780  const QModelIndex currentIndex = mTableView->currentIndex();
781  if ( !currentIndex.isValid() )
782  {
783  return;
784  }
785 
786  const QVariant var = mMasterModel->data( currentIndex, Qt::DisplayRole );
787  QApplication::clipboard()->setText( var.toString() );
788 }
789 
791 {
792  if ( mProgressDlg )
793  mProgressDlg->cancel();
794 }
795 
796 void QgsDualView::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
797 {
798  if ( mAttributeForm )
799  {
800  mAttributeForm->parentFormValueChanged( attribute, newValue );
801  }
802 }
803 
804 void QgsDualView::hideEvent( QHideEvent *event )
805 {
806  Q_UNUSED( event )
807  saveRecentDisplayExpressions();
808 }
809 
810 void QgsDualView::viewWillShowContextMenu( QMenu *menu, const QModelIndex &masterIndex )
811 {
812  if ( !menu )
813  {
814  return;
815  }
816 
817  QAction *copyContentAction = menu->addAction( tr( "Copy Cell Content" ) );
818  menu->addAction( copyContentAction );
819  connect( copyContentAction, &QAction::triggered, this, [masterIndex, this]
820  {
821  const QVariant var = mMasterModel->data( masterIndex, Qt::DisplayRole );
822  QApplication::clipboard()->setText( var.toString() );
823  } );
824 
825  QgsVectorLayer *vl = mFilterModel->layer();
826  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
827  if ( canvas && vl && vl->geometryType() != QgsWkbTypes::NullGeometry )
828  {
829  QAction *zoomToFeatureAction = menu->addAction( tr( "Zoom to Feature" ) );
830  connect( zoomToFeatureAction, &QAction::triggered, this, &QgsDualView::zoomToCurrentFeature );
831 
832  QAction *panToFeatureAction = menu->addAction( tr( "Pan to Feature" ) );
833  connect( panToFeatureAction, &QAction::triggered, this, &QgsDualView::panToCurrentFeature );
834 
835  QAction *flashFeatureAction = menu->addAction( tr( "Flash Feature" ) );
836  connect( flashFeatureAction, &QAction::triggered, this, &QgsDualView::flashCurrentFeature );
837  }
838 
839  //add user-defined actions to context menu
840  const QList<QgsAction> actions = mLayer->actions()->actions( QStringLiteral( "Field" ) );
841  if ( !actions.isEmpty() )
842  {
843  QAction *a = menu->addAction( tr( "Run Layer Action" ) );
844  a->setEnabled( false );
845 
846  for ( const QgsAction &action : actions )
847  {
848  if ( !action.runable() )
849  continue;
850 
851  if ( vl && !vl->isEditable() && action.isEnabledOnlyWhenEditable() )
852  continue;
853 
854  QgsAttributeTableAction *a = new QgsAttributeTableAction( action.name(), this, action.id(), masterIndex );
855  menu->addAction( action.name(), a, &QgsAttributeTableAction::execute );
856  }
857  }
858  const QModelIndex rowSourceIndex = mMasterModel->index( masterIndex.row(), 0 );
859  if ( ! rowSourceIndex.isValid() )
860  {
861  return;
862  }
863 
864  //add actions from QgsMapLayerActionRegistry to context menu
865  const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, QgsMapLayerAction::Layer | QgsMapLayerAction::SingleFeature );
866  if ( !registeredActions.isEmpty() )
867  {
868  //add a separator between user defined and standard actions
869  menu->addSeparator();
870 
871  for ( QgsMapLayerAction *action : registeredActions )
872  {
873  QgsAttributeTableMapLayerAction *a = new QgsAttributeTableMapLayerAction( action->text(), this, action, rowSourceIndex );
874  menu->addAction( action->text(), a, &QgsAttributeTableMapLayerAction::execute );
875  }
876  }
877 
878  // entries for multiple features layer actions
879  // only show if the context menu is shown over a selected row
880  const QgsFeatureId currentFid = mMasterModel->rowToId( masterIndex.row() );
881  if ( mLayer->selectedFeatureCount() > 1 && mLayer->selectedFeatureIds().contains( currentFid ) )
882  {
883  const QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( mLayer, QgsMapLayerAction::MultipleFeatures );
884  if ( !registeredActions.isEmpty() )
885  {
886  menu->addSeparator();
887  QAction *action = menu->addAction( tr( "Actions on Selection (%1)" ).arg( mLayer->selectedFeatureCount() ) );
888  action->setEnabled( false );
889 
890  for ( QgsMapLayerAction *action : registeredActions )
891  {
892  menu->addAction( action->text(), action, [ = ]() {action->triggerForFeatures( mLayer, mLayer->selectedFeatures() );} );
893  }
894  }
895  }
896 
897  menu->addSeparator();
898  QgsAttributeTableAction *a = new QgsAttributeTableAction( tr( "Open Form" ), this, QUuid(), rowSourceIndex );
899  menu->addAction( tr( "Open Form…" ), a, &QgsAttributeTableAction::featureForm );
900 }
901 
902 
903 void QgsDualView::widgetWillShowContextMenu( QgsActionMenu *menu, const QModelIndex &atIndex )
904 {
905  emit showContextMenuExternally( menu, mFilterModel->rowToId( atIndex ) );
906 }
907 
908 
909 void QgsDualView::showViewHeaderMenu( QPoint point )
910 {
911  const int col = mTableView->columnAt( point.x() );
912 
913  delete mHorizontalHeaderMenu;
914  mHorizontalHeaderMenu = new QMenu( this );
915 
916  QAction *hide = new QAction( tr( "&Hide Column" ), mHorizontalHeaderMenu );
917  connect( hide, &QAction::triggered, this, &QgsDualView::hideColumn );
918  hide->setData( col );
919  mHorizontalHeaderMenu->addAction( hide );
920  QAction *setWidth = new QAction( tr( "&Set Width…" ), mHorizontalHeaderMenu );
921  connect( setWidth, &QAction::triggered, this, &QgsDualView::resizeColumn );
922  setWidth->setData( col );
923  mHorizontalHeaderMenu->addAction( setWidth );
924 
925  QAction *setWidthAllColumns = new QAction( tr( "&Set All Column Widths…" ), mHorizontalHeaderMenu );
926  connect( setWidthAllColumns, &QAction::triggered, this, &QgsDualView::resizeAllColumns );
927  setWidthAllColumns->setData( col );
928  mHorizontalHeaderMenu->addAction( setWidthAllColumns );
929 
930  QAction *optimizeWidth = new QAction( tr( "&Autosize" ), mHorizontalHeaderMenu );
931  connect( optimizeWidth, &QAction::triggered, this, &QgsDualView::autosizeColumn );
932  optimizeWidth->setData( col );
933  mHorizontalHeaderMenu->addAction( optimizeWidth );
934 
935  QAction *optimizeWidthAllColumns = new QAction( tr( "&Autosize All Columns" ), mHorizontalHeaderMenu );
936  connect( optimizeWidthAllColumns, &QAction::triggered, this, &QgsDualView::autosizeAllColumns );
937  mHorizontalHeaderMenu->addAction( optimizeWidthAllColumns );
938 
939 
940  mHorizontalHeaderMenu->addSeparator();
941  QAction *organize = new QAction( tr( "&Organize Columns…" ), mHorizontalHeaderMenu );
942  connect( organize, &QAction::triggered, this, &QgsDualView::organizeColumns );
943  mHorizontalHeaderMenu->addAction( organize );
944  QAction *sort = new QAction( tr( "&Sort…" ), mHorizontalHeaderMenu );
945  connect( sort, &QAction::triggered, this, [ = ]() {modifySort();} );
946  mHorizontalHeaderMenu->addAction( sort );
947 
948  mHorizontalHeaderMenu->popup( mTableView->horizontalHeader()->mapToGlobal( point ) );
949 }
950 
951 void QgsDualView::organizeColumns()
952 {
953  if ( !mLayer )
954  {
955  return;
956  }
957 
958  QgsOrganizeTableColumnsDialog dialog( mLayer, attributeTableConfig(), this );
959  if ( dialog.exec() == QDialog::Accepted )
960  {
961  const QgsAttributeTableConfig config = dialog.config();
962  setAttributeTableConfig( config );
963  }
964 }
965 
966 void QgsDualView::tableColumnResized( int column, int width )
967 {
968  QgsAttributeTableConfig config = mConfig;
969  const int sourceCol = config.mapVisibleColumnToIndex( column );
970  if ( sourceCol >= 0 && config.columnWidth( sourceCol ) != width )
971  {
972  config.setColumnWidth( sourceCol, width );
973  setAttributeTableConfig( config );
974  }
975 }
976 
977 void QgsDualView::hideColumn()
978 {
979  QAction *action = qobject_cast<QAction *>( sender() );
980  const int col = action->data().toInt();
981  QgsAttributeTableConfig config = mConfig;
982  const int sourceCol = mConfig.mapVisibleColumnToIndex( col );
983  if ( sourceCol >= 0 )
984  {
985  config.setColumnHidden( sourceCol, true );
986  setAttributeTableConfig( config );
987  }
988 }
989 
990 void QgsDualView::resizeColumn()
991 {
992  QAction *action = qobject_cast<QAction *>( sender() );
993  const int col = action->data().toInt();
994  if ( col < 0 )
995  return;
996 
997  QgsAttributeTableConfig config = mConfig;
998  const int sourceCol = config.mapVisibleColumnToIndex( col );
999  if ( sourceCol >= 0 )
1000  {
1001  bool ok = false;
1002  const int width = QInputDialog::getInt( this, tr( "Set column width" ), tr( "Enter column width" ),
1003  mTableView->columnWidth( col ),
1004  0, 1000, 10, &ok );
1005  if ( ok )
1006  {
1007  config.setColumnWidth( sourceCol, width );
1008  setAttributeTableConfig( config );
1009  }
1010  }
1011 }
1012 
1013 void QgsDualView::resizeAllColumns()
1014 {
1015  QAction *action = qobject_cast<QAction *>( sender() );
1016  const int col = action->data().toInt();
1017  if ( col < 0 )
1018  return;
1019 
1020  QgsAttributeTableConfig config = mConfig;
1021 
1022  bool ok = false;
1023  const int width = QInputDialog::getInt( this, tr( "Set Column Width" ), tr( "Enter column width" ),
1024  mTableView->columnWidth( col ),
1025  1, 1000, 10, &ok );
1026  if ( ok )
1027  {
1028  const int colCount = mTableView->model()->columnCount();
1029  if ( colCount > 0 )
1030  {
1031  for ( int i = 0; i < colCount; i++ )
1032  {
1033  config.setColumnWidth( i, width );
1034  }
1035  setAttributeTableConfig( config );
1036  }
1037  }
1038 }
1039 
1040 void QgsDualView::autosizeColumn()
1041 {
1042  QAction *action = qobject_cast<QAction *>( sender() );
1043  const int col = action->data().toInt();
1044  mTableView->resizeColumnToContents( col );
1045 }
1046 
1047 void QgsDualView::autosizeAllColumns()
1048 {
1049  mTableView->resizeColumnsToContents();
1050 }
1051 
1052 bool QgsDualView::modifySort()
1053 {
1054  if ( !mLayer )
1055  return false;
1056 
1057  QgsAttributeTableConfig config = mConfig;
1058 
1059  QDialog orderByDlg;
1060  orderByDlg.setWindowTitle( tr( "Configure Attribute Table Sort Order" ) );
1061  QDialogButtonBox *dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1062  QGridLayout *layout = new QGridLayout();
1063  connect( dialogButtonBox, &QDialogButtonBox::accepted, &orderByDlg, &QDialog::accept );
1064  connect( dialogButtonBox, &QDialogButtonBox::rejected, &orderByDlg, &QDialog::reject );
1065  orderByDlg.setLayout( layout );
1066 
1067  QGroupBox *sortingGroupBox = new QGroupBox();
1068  sortingGroupBox->setTitle( tr( "Defined sort order in attribute table" ) );
1069  sortingGroupBox->setCheckable( true );
1070  sortingGroupBox->setChecked( !sortExpression().isEmpty() );
1071  layout->addWidget( sortingGroupBox );
1072  sortingGroupBox->setLayout( new QGridLayout() );
1073 
1074  QgsExpressionBuilderWidget *expressionBuilder = new QgsExpressionBuilderWidget();
1076 
1077  expressionBuilder->initWithLayer( mLayer, context, QStringLiteral( "generic" ) );
1078  expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() );
1079 
1080  sortingGroupBox->layout()->addWidget( expressionBuilder );
1081 
1082  QCheckBox *cbxSortAscending = new QCheckBox( tr( "Sort ascending" ) );
1083  cbxSortAscending->setChecked( config.sortOrder() == Qt::AscendingOrder );
1084  sortingGroupBox->layout()->addWidget( cbxSortAscending );
1085 
1086  layout->addWidget( dialogButtonBox );
1087  if ( orderByDlg.exec() )
1088  {
1089  const Qt::SortOrder sortOrder = cbxSortAscending->isChecked() ? Qt::AscendingOrder : Qt::DescendingOrder;
1090  if ( sortingGroupBox->isChecked() )
1091  {
1092  setSortExpression( expressionBuilder->expressionText(), sortOrder );
1093  config.setSortExpression( expressionBuilder->expressionText() );
1094  config.setSortOrder( sortOrder );
1095  }
1096  else
1097  {
1098  setSortExpression( QString(), sortOrder );
1099  config.setSortExpression( QString() );
1100  }
1101 
1102  setAttributeTableConfig( config );
1103  return true;
1104  }
1105  else
1106  {
1107  return false;
1108  }
1109 
1110 }
1111 
1112 void QgsDualView::zoomToCurrentFeature()
1113 {
1114  const QModelIndex currentIndex = mTableView->currentIndex();
1115  if ( !currentIndex.isValid() )
1116  {
1117  return;
1118  }
1119 
1120  QgsFeatureIds ids;
1121  ids.insert( mFilterModel->rowToId( currentIndex ) );
1122  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1123  if ( canvas )
1124  {
1125  canvas->zoomToFeatureIds( mLayer, ids );
1126  }
1127 }
1128 
1129 void QgsDualView::panToCurrentFeature()
1130 {
1131  const QModelIndex currentIndex = mTableView->currentIndex();
1132  if ( !currentIndex.isValid() )
1133  {
1134  return;
1135  }
1136 
1137  QgsFeatureIds ids;
1138  ids.insert( mFilterModel->rowToId( currentIndex ) );
1139  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1140  if ( canvas )
1141  {
1142  canvas->panToFeatureIds( mLayer, ids );
1143  }
1144 }
1145 
1146 void QgsDualView::flashCurrentFeature()
1147 {
1148  const QModelIndex currentIndex = mTableView->currentIndex();
1149  if ( !currentIndex.isValid() )
1150  {
1151  return;
1152  }
1153 
1154  QgsFeatureIds ids;
1155  ids.insert( mFilterModel->rowToId( currentIndex ) );
1156  QgsMapCanvas *canvas = mFilterModel->mapCanvas();
1157  if ( canvas )
1158  {
1159  canvas->flashFeatureIds( mLayer, ids );
1160  }
1161 }
1162 
1163 void QgsDualView::rebuildFullLayerCache()
1164 {
1165  connect( mLayerCache, &QgsVectorLayerCache::progress, this, &QgsDualView::progress, Qt::UniqueConnection );
1166  connect( mLayerCache, &QgsVectorLayerCache::finished, this, &QgsDualView::finished, Qt::UniqueConnection );
1167 
1168  mLayerCache->setFullCache( true );
1169 }
1170 
1171 void QgsDualView::previewExpressionChanged( const QString &expression )
1172 {
1173  mLayer->setDisplayExpression( expression );
1174 }
1175 
1176 void QgsDualView::onSortColumnChanged()
1177 {
1179  if ( cfg.sortExpression() != mFilterModel->sortExpression() ||
1180  cfg.sortOrder() != mFilterModel->sortOrder() )
1181  {
1182  cfg.setSortExpression( mFilterModel->sortExpression() );
1183  cfg.setSortOrder( mFilterModel->sortOrder() );
1184  setAttributeTableConfig( cfg );
1185  }
1186 }
1187 
1188 void QgsDualView::updateSelectedFeatures()
1189 {
1190  QgsFeatureRequest r = mMasterModel->request();
1192  return; // already requested all features
1193 
1194  r.setFilterFids( masterModel()->layer()->selectedFeatureIds() );
1195  mMasterModel->setRequest( r );
1196  mMasterModel->loadLayer();
1197  emit filterChanged();
1198 }
1199 
1200 void QgsDualView::updateEditedAddedFeatures()
1201 {
1202  QgsFeatureRequest r = mMasterModel->request();
1204  return; // already requested all features
1205 
1206  r.setFilterFids( masterModel()->layer()->editBuffer() ? masterModel()->layer()->editBuffer()->allAddedOrEditedFeatures() : QgsFeatureIds() );
1207  mMasterModel->setRequest( r );
1208  mMasterModel->loadLayer();
1209  emit filterChanged();
1210 }
1211 
1212 void QgsDualView::extentChanged()
1213 {
1214  QgsFeatureRequest r = mMasterModel->request();
1215  if ( mFilterModel->mapCanvas() && ( r.filterType() != QgsFeatureRequest::FilterNone || !r.filterRect().isNull() ) )
1216  {
1217  const QgsRectangle rect = mFilterModel->mapCanvas()->mapSettings().mapToLayerCoordinates( mLayer, mFilterModel->mapCanvas()->extent() );
1218  r.setFilterRect( rect );
1219  mMasterModel->setRequest( r );
1220  mMasterModel->loadLayer();
1221  }
1222  emit filterChanged();
1223 }
1224 
1225 void QgsDualView::featureFormAttributeChanged( const QString &attribute, const QVariant &value, bool attributeChanged )
1226 {
1227  Q_UNUSED( attribute )
1228  Q_UNUSED( value )
1229  if ( attributeChanged )
1230  {
1231  mFeatureListView->setCurrentFeatureEdited( true );
1232  mAttributeForm->save();
1233  }
1234 }
1235 
1236 void QgsDualView::setFilteredFeatures( const QgsFeatureIds &filteredFeatures )
1237 {
1238  mFilterModel->setFilteredFeatures( filteredFeatures );
1239 }
1240 
1241 void QgsDualView::filterFeatures( const QgsExpression &filterExpression, const QgsExpressionContext &context )
1242 {
1243  mFilterModel->setFilterExpression( filterExpression, context );
1244  mFilterModel->filterFeatures();
1245 }
1246 
1247 
1249 {
1250  mMasterModel->setRequest( request );
1251 }
1252 
1254 {
1255  mTableView->setFeatureSelectionManager( featureSelectionManager );
1256  mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
1257 
1258  if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
1259  delete mFeatureSelectionManager;
1260 
1261  mFeatureSelectionManager = featureSelectionManager;
1262 }
1263 
1265 {
1266  mConfig = config;
1267  mConfig.update( mLayer->fields() );
1268  mLayer->setAttributeTableConfig( mConfig );
1269  mFilterModel->setAttributeTableConfig( mConfig );
1270  mTableView->setAttributeTableConfig( mConfig );
1271 }
1272 
1273 void QgsDualView::setSortExpression( const QString &sortExpression, Qt::SortOrder sortOrder )
1274 {
1275  if ( sortExpression.isNull() )
1276  mFilterModel->sort( -1 );
1277  else
1278  mFilterModel->sort( sortExpression, sortOrder );
1279 
1280  mConfig.setSortExpression( sortExpression );
1281  mConfig.setSortOrder( sortOrder );
1282  setAttributeTableConfig( mConfig );
1283 }
1284 
1286 {
1287  return mFilterModel->sortExpression();
1288 }
1289 
1291 {
1292  return mConfig;
1293 }
1294 
1295 void QgsDualView::progress( int i, bool &cancel )
1296 {
1297  if ( !mProgressDlg )
1298  {
1299  mProgressDlg = new QProgressDialog( tr( "Loading features…" ), tr( "Abort" ), 0, 0, this );
1300  mProgressDlg->setWindowTitle( tr( "Attribute Table" ) );
1301  mProgressDlg->setWindowModality( Qt::WindowModal );
1302  mProgressDlg->show();
1303  }
1304 
1305  mProgressDlg->setLabelText( tr( "%L1 features loaded." ).arg( i ) );
1306  QCoreApplication::processEvents();
1307 
1308  cancel = mProgressDlg && mProgressDlg->wasCanceled();
1309 }
1310 
1311 void QgsDualView::finished()
1312 {
1313  delete mProgressDlg;
1314  mProgressDlg = nullptr;
1315 }
1316 
1317 /*
1318  * QgsAttributeTableAction
1319  */
1320 
1322 {
1323  mDualView->masterModel()->executeAction( mAction, mFieldIdx );
1324 }
1325 
1327 {
1328  QgsFeatureIds editedIds;
1329  editedIds << mDualView->masterModel()->rowToId( mFieldIdx.row() );
1330  mDualView->setCurrentEditSelection( editedIds );
1331  mDualView->setView( QgsDualView::AttributeEditor );
1332 }
1333 
1334 /*
1335  * QgsAttributeTableMapLayerAction
1336  */
1337 
1339 {
1340  mDualView->masterModel()->executeMapLayerAction( mAction, mFieldIdx );
1341 }
@ 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.
const QgsAttributeEditorContext * parentContext() const
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.
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.
QgsVectorLayer * layer() const
Returns the layer this filter acts on.
void setFilterMode(FilterMode filterMode)
Set the filter mode the filter will use.
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...
QgsMapCanvas * mapCanvas() const
Returns the map canvas.
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)
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
QgsAttributeTableModel * masterModel() const
Returns the model which has the information about all features (not only filtered)
Definition: qgsdualview.h:186
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.
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.
@ 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.
QgsFeatureRequest & disableFilter()
Disables any attribute/ID filtering.
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:84
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition: qgsgui.cpp:109
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition: qgsgui.cpp:119
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:305
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:253
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:1517
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