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