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