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