QGIS API Documentation  3.27.0-Master (597e8eebd4)
qgsattributetablemodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  QgsAttributeTableModel.cpp
3  --------------------------------------
4  Date : Feb 2009
5  Copyright : (C) 2009 Vita Cizek
6  Email : weetya (at) gmail.com
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 "qgsapplication.h"
17 #include "qgsattributetablemodel.h"
19 
20 #include "qgsactionmanager.h"
22 #include "qgseditorwidgetfactory.h"
23 #include "qgsexpression.h"
24 #include "qgsfeatureiterator.h"
25 #include "qgsconditionalstyle.h"
26 #include "qgsfields.h"
27 #include "qgsfieldformatter.h"
28 #include "qgslogger.h"
29 #include "qgsmapcanvas.h"
31 #include "qgsrenderer.h"
32 #include "qgsvectorlayer.h"
33 #include "qgsvectordataprovider.h"
34 #include "qgssymbollayerutils.h"
36 #include "qgsgui.h"
37 #include "qgsexpressionnodeimpl.h"
38 #include "qgsvectorlayerjoininfo.h"
40 #include "qgsfieldmodel.h"
43 #include "qgsstringutils.h"
44 #include "qgsvectorlayerutils.h"
45 #include "qgsvectorlayercache.h"
46 
47 #include <QVariant>
48 #include <QUuid>
49 
50 #include <limits>
51 
53  : QAbstractTableModel( parent )
54  , mLayer( layerCache->layer() )
55  , mLayerCache( layerCache )
56 {
58 
59  if ( mLayer->geometryType() == QgsWkbTypes::NullGeometry )
60  {
61  mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
62  }
63 
64  mFeat.setId( std::numeric_limits<int>::min() );
65 
66  if ( !mLayer->isSpatial() )
67  mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
68 
69  loadAttributes();
70 
71  connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted );
72  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted );
73  connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields );
74 
75  connect( mLayer, &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted );
76  connect( mLayer, &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted );
77  connect( mLayer, &QgsVectorLayer::afterRollBack, this, [ = ]
78  {
79  mIsCleaningUpAfterRollback = true;
80  bulkEditCommandEnded();
81  mIsCleaningUpAfterRollback = false;
82  } );
83 
84  connect( mLayer, &QgsVectorLayer::editCommandEnded, this, &QgsAttributeTableModel::editCommandEnded );
85  connect( mLayerCache, &QgsVectorLayerCache::attributeValueChanged, this, &QgsAttributeTableModel::attributeValueChanged );
86  connect( mLayerCache, &QgsVectorLayerCache::featureAdded, this, [ = ]( QgsFeatureId id ) { featureAdded( id ); } );
87  connect( mLayerCache, &QgsVectorLayerCache::cachedLayerDeleted, this, &QgsAttributeTableModel::layerDeleted );
88 }
89 
90 bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const
91 {
92  QgsDebugMsgLevel( QStringLiteral( "loading feature %1" ).arg( fid ), 3 );
93 
94  if ( fid == std::numeric_limits<int>::min() )
95  {
96  return false;
97  }
98 
99  return mLayerCache->featureAtId( fid, mFeat );
100 }
101 
103 {
104  return mExtraColumns;
105 }
106 
108 {
109  mExtraColumns = extraColumns;
110  loadAttributes();
111 }
112 
113 void QgsAttributeTableModel::featuresDeleted( const QgsFeatureIds &fids )
114 {
115  QList<int> rows;
116 
117  const auto constFids = fids;
118  for ( const QgsFeatureId fid : constFids )
119  {
120  QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1, size: %3" ).arg( fid ).arg( mFeatureRequest.filterType() ).arg( mIdRowMap.size() ), 4 );
121 
122  const int row = idToRow( fid );
123  if ( row != -1 )
124  rows << row;
125  }
126 
127  std::sort( rows.begin(), rows.end() );
128 
129  int lastRow = -1;
130  int beginRow = -1;
131  int currentRowCount = 0;
132  int removedRows = 0;
133  bool reset = false;
134 
135  const auto constRows = rows;
136  for ( const int row : constRows )
137  {
138 #if 0
139  qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows;
140 #endif
141  if ( lastRow == -1 )
142  {
143  beginRow = row;
144  }
145 
146  if ( row != lastRow + 1 && lastRow != -1 )
147  {
148  if ( rows.count() > 100 && currentRowCount < 10 )
149  {
150  reset = true;
151  break;
152  }
153  removeRows( beginRow - removedRows, currentRowCount );
154 
155  beginRow = row;
156  removedRows += currentRowCount;
157  currentRowCount = 0;
158  }
159 
160  currentRowCount++;
161 
162  lastRow = row;
163  }
164 
165  if ( !reset )
166  removeRows( beginRow - removedRows, currentRowCount );
167  else
168  resetModel();
169 }
170 
171 bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent )
172 {
173 
174  if ( row < 0 || count < 1 )
175  return false;
176 
177  if ( !mResettingModel )
178  {
179  beginRemoveRows( parent, row, row + count - 1 );
180  }
181 
182 #ifdef QGISDEBUG
183  if ( 3 <= QgsLogger::debugLevel() )
184  QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
185 #endif
186 
187  if ( mBulkEditCommandRunning && !mResettingModel )
188  {
189  for ( int i = row; i < row + count; i++ )
190  {
191  const QgsFeatureId fid { rowToId( row ) };
192  mInsertedRowsChanges.removeOne( fid );
193  }
194  }
195 
196  // clean old references
197  for ( int i = row; i < row + count; i++ )
198  {
199  for ( SortCache &cache : mSortCaches )
200  cache.sortCache.remove( mRowIdMap[i] );
201  mIdRowMap.remove( mRowIdMap[i] );
202  mRowIdMap.remove( i );
203  }
204 
205  // update maps
206  const int n = mRowIdMap.size() + count;
207  for ( int i = row + count; i < n; i++ )
208  {
209  const QgsFeatureId id = mRowIdMap[i];
210  mIdRowMap[id] -= count;
211  mRowIdMap[i - count] = id;
212  mRowIdMap.remove( i );
213  }
214 
215 #ifdef QGISDEBUG
216  if ( 4 <= QgsLogger::debugLevel() )
217  {
218  QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
219  QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 );
220  for ( QHash<QgsFeatureId, int>::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it )
221  QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
222 
223  QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 );
224  for ( QHash<int, QgsFeatureId>::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it )
225  QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
226  }
227 #endif
228 
229  Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
230 
231  if ( !mResettingModel )
232  endRemoveRows();
233 
234  return true;
235 }
236 
237 void QgsAttributeTableModel::featureAdded( QgsFeatureId fid )
238 {
239  QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 );
240  bool featOk = true;
241 
242  if ( mFeat.id() != fid )
243  featOk = loadFeatureAtId( fid );
244 
245  if ( featOk && mFeatureRequest.acceptFeature( mFeat ) )
246  {
247  for ( SortCache &cache : mSortCaches )
248  {
249  if ( cache.sortFieldIndex >= 0 )
250  {
251  QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
252  const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
253  const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
254  const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, mFeat.attribute( cache.sortFieldIndex ) );
255  cache.sortCache.insert( mFeat.id(), sortValue );
256  }
257  else if ( cache.sortCacheExpression.isValid() )
258  {
259  mExpressionContext.setFeature( mFeat );
260  cache.sortCache[mFeat.id()] = cache.sortCacheExpression.evaluate( &mExpressionContext );
261  }
262  }
263 
264  // Skip if the fid is already in the map (do not add twice)!
265  if ( ! mIdRowMap.contains( fid ) )
266  {
267  const int n = mRowIdMap.size();
268  if ( !mResettingModel )
269  beginInsertRows( QModelIndex(), n, n );
270  mIdRowMap.insert( fid, n );
271  mRowIdMap.insert( n, fid );
272  if ( !mResettingModel )
273  endInsertRows();
274  reload( index( rowCount() - 1, 0 ), index( rowCount() - 1, columnCount() ) );
275  if ( mBulkEditCommandRunning && !mResettingModel )
276  {
277  mInsertedRowsChanges.append( fid );
278  }
279  }
280  }
281 }
282 
283 void QgsAttributeTableModel::updatedFields()
284 {
285  loadAttributes();
286  emit modelChanged();
287 }
288 
289 void QgsAttributeTableModel::editCommandEnded()
290 {
291  // do not do reload(...) due would trigger (dataChanged) row sort
292  // giving issue: https://github.com/qgis/QGIS/issues/23892
293  bulkEditCommandEnded( );
294 }
295 
296 void QgsAttributeTableModel::attributeDeleted( int idx )
297 {
298  int cacheIndex = 0;
299  for ( const SortCache &cache : mSortCaches )
300  {
301  if ( cache.sortCacheAttributes.contains( idx ) )
302  {
303  prefetchSortData( QString(), cacheIndex );
304  }
305  cacheIndex++;
306  }
307 }
308 
309 void QgsAttributeTableModel::layerDeleted()
310 {
311  mLayerCache = nullptr;
312  mLayer = nullptr;
313  removeRows( 0, rowCount() );
314 
315  mAttributeWidgetCaches.clear();
316  mAttributes.clear();
317  mWidgetFactories.clear();
318  mWidgetConfigs.clear();
319  mFieldFormatters.clear();
320 }
321 
322 void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter )
323 {
324  for ( int i = 0; i < mFieldFormatters.size(); ++i )
325  {
326  if ( mFieldFormatters.at( i ) == fieldFormatter )
328  }
329 }
330 
331 void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
332 {
333  // Defer all updates if a bulk edit/rollback command is running
334  if ( mBulkEditCommandRunning )
335  {
336  mAttributeValueChanges.insert( QPair<QgsFeatureId, int>( fid, idx ), value );
337  return;
338  }
339  QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( mFeatureRequest.filterType() ), 2 );
340 
341  for ( SortCache &cache : mSortCaches )
342  {
343  if ( cache.sortCacheAttributes.contains( idx ) )
344  {
345  if ( cache.sortFieldIndex == -1 )
346  {
347  if ( loadFeatureAtId( fid ) )
348  {
349  mExpressionContext.setFeature( mFeat );
350  cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext );
351  }
352  }
353  else
354  {
355  QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
356  const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
357  const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
358  const QVariant sortValue = fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, value );
359  cache.sortCache.insert( fid, sortValue );
360  }
361  }
362  }
363  // No filter request: skip all possibly heavy checks
364  if ( mFeatureRequest.filterType() == QgsFeatureRequest::FilterNone )
365  {
366  if ( loadFeatureAtId( fid ) )
367  setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
368  }
369  else
370  {
371  if ( loadFeatureAtId( fid ) )
372  {
373  if ( mFeatureRequest.acceptFeature( mFeat ) )
374  {
375  if ( !mIdRowMap.contains( fid ) )
376  {
377  // Feature changed in such a way, it will be shown now
378  featureAdded( fid );
379  }
380  else
381  {
382  // Update representation
383  setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
384  }
385  }
386  else
387  {
388  if ( mIdRowMap.contains( fid ) )
389  {
390  // Feature changed such, that it is no longer shown
391  featuresDeleted( QgsFeatureIds() << fid );
392  }
393  // else: we don't care
394  }
395  }
396  }
397 }
398 
399 void QgsAttributeTableModel::loadAttributes()
400 {
401  if ( !mLayer )
402  {
403  return;
404  }
405 
406  bool ins = false, rm = false;
407 
408  QgsAttributeList attributes;
409  const QgsFields &fields = mLayer->fields();
410 
411  mWidgetFactories.clear();
412  mAttributeWidgetCaches.clear();
413  mWidgetConfigs.clear();
414  mFieldFormatters.clear();
415 
416  for ( int idx = 0; idx < fields.count(); ++idx )
417  {
418  const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fields[idx].name() );
419  QgsEditorWidgetFactory *widgetFactory = QgsGui::editorWidgetRegistry()->factory( setup.type() );
421 
422  mWidgetFactories.append( widgetFactory );
423  mWidgetConfigs.append( setup.config() );
424  mAttributeWidgetCaches.append( fieldFormatter->createCache( mLayer, idx, setup.config() ) );
425  mFieldFormatters.append( fieldFormatter );
426 
427  attributes << idx;
428  }
429 
430  if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
431  {
432  ins = true;
433  beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
434  }
435  else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
436  {
437  rm = true;
438  beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
439  }
440 
441  mFieldCount = attributes.size();
442  mAttributes = attributes;
443 
444  for ( SortCache &cache : mSortCaches )
445  {
446  if ( cache.sortFieldIndex >= mAttributes.count() )
447  cache.sortFieldIndex = -1;
448  }
449 
450  if ( ins )
451  {
452  endInsertColumns();
453  }
454  else if ( rm )
455  {
456  endRemoveColumns();
457  }
458 }
459 
461 {
462  // make sure attributes are properly updated before caching the data
463  // (emit of progress() signal may enter event loop and thus attribute
464  // table view may be updated with inconsistent model which may assume
465  // wrong number of attributes)
466 
467  loadAttributes();
468 
469  mResettingModel = true;
470  beginResetModel();
471 
472  if ( rowCount() != 0 )
473  {
474  removeRows( 0, rowCount() );
475  }
476 
477  // Layer might have been deleted and cache set to nullptr!
478  if ( mLayerCache )
479  {
480  QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
481 
482  int i = 0;
483 
484  QElapsedTimer t;
485  t.start();
486 
487  while ( features.nextFeature( mFeat ) )
488  {
489  ++i;
490 
491  if ( t.elapsed() > 1000 )
492  {
493  bool cancel = false;
494  emit progress( i, cancel );
495  if ( cancel )
496  break;
497 
498  t.restart();
499  }
500  featureAdded( mFeat.id() );
501  }
502 
503  emit finished();
504  connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
505  }
506 
507  endResetModel();
508 
509  mResettingModel = false;
510 }
511 
512 
514 {
515  if ( fieldName.isNull() )
516  {
517  mRowStylesMap.clear();
518  emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
519  return;
520  }
521 
522  const int fieldIndex = mLayer->fields().lookupField( fieldName );
523  if ( fieldIndex == -1 )
524  return;
525 
526  //whole column has changed
527  const int col = fieldCol( fieldIndex );
528  emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
529 }
530 
532 {
533  if ( a == b )
534  return;
535 
536  const int rowA = idToRow( a );
537  const int rowB = idToRow( b );
538 
539  //emit layoutAboutToBeChanged();
540 
541  mRowIdMap.remove( rowA );
542  mRowIdMap.remove( rowB );
543  mRowIdMap.insert( rowA, b );
544  mRowIdMap.insert( rowB, a );
545 
546  mIdRowMap.remove( a );
547  mIdRowMap.remove( b );
548  mIdRowMap.insert( a, rowB );
549  mIdRowMap.insert( b, rowA );
550  Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
551 
552 
553  //emit layoutChanged();
554 }
555 
557 {
558  if ( !mIdRowMap.contains( id ) )
559  {
560  QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
561  return -1;
562  }
563 
564  return mIdRowMap[id];
565 }
566 
568 {
569  return index( idToRow( id ), 0 );
570 }
571 
573 {
574  QModelIndexList indexes;
575 
576  const int row = idToRow( id );
577  const int columns = columnCount();
578  indexes.reserve( columns );
579  for ( int column = 0; column < columns; ++column )
580  {
581  indexes.append( index( row, column ) );
582  }
583 
584  return indexes;
585 }
586 
588 {
589  if ( !mRowIdMap.contains( row ) )
590  {
591  QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
592  // return negative infinite (to avoid collision with newly added features)
593  return std::numeric_limits<int>::min();
594  }
595 
596  return mRowIdMap[row];
597 }
598 
600 {
601  return mAttributes[col];
602 }
603 
605 {
606  return mAttributes.indexOf( idx );
607 }
608 
609 int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
610 {
611  Q_UNUSED( parent )
612  return mRowIdMap.size();
613 }
614 
615 int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
616 {
617  Q_UNUSED( parent )
618  return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
619 }
620 
621 QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
622 {
623  if ( !mLayer )
624  return QVariant();
625 
626  if ( role == Qt::DisplayRole )
627  {
628  if ( orientation == Qt::Vertical ) //row
629  {
630  return QVariant( section );
631  }
632  else if ( section >= 0 && section < mFieldCount )
633  {
634  const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
635  return QVariant( attributeName );
636  }
637  else
638  {
639  return tr( "extra column" );
640  }
641  }
642  else if ( role == Qt::ToolTipRole )
643  {
644  if ( orientation == Qt::Vertical )
645  {
646  // TODO show DisplayExpression
647  return tr( "Feature ID: %1" ).arg( rowToId( section ) );
648  }
649  else
650  {
651  const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
652  return QgsFieldModel::fieldToolTipExtended( field, mLayer );
653  }
654  }
655  else
656  {
657  return QVariant();
658  }
659 }
660 
661 QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
662 {
663  if ( !index.isValid() || !mLayer ||
664  ( role != Qt::TextAlignmentRole
665  && role != Qt::DisplayRole
666  && role != Qt::ToolTipRole
667  && role != Qt::EditRole
668  && role != FeatureIdRole
669  && role != FieldIndexRole
670 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
671  && role != Qt::BackgroundColorRole
672  && role != Qt::TextColorRole
673 #else
674  && role != Qt::BackgroundRole
675  && role != Qt::ForegroundRole
676 #endif
677  && role != Qt::DecorationRole
678  && role != Qt::FontRole
679  && role < SortRole
680  )
681  )
682  return QVariant();
683 
684  const QgsFeatureId rowId = rowToId( index.row() );
685 
686  if ( role == FeatureIdRole )
687  return rowId;
688 
689  if ( index.column() >= mFieldCount )
690  return QVariant();
691 
692  const int fieldId = mAttributes.at( index.column() );
693 
694  if ( role == FieldIndexRole )
695  return fieldId;
696 
697  if ( role >= SortRole )
698  {
699  const unsigned long cacheIndex = role - SortRole;
700  if ( cacheIndex < mSortCaches.size() )
701  return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
702  else
703  return QVariant();
704  }
705 
706  const QgsField field = mLayer->fields().at( fieldId );
707 
708  if ( role == Qt::TextAlignmentRole )
709  {
710  return static_cast<Qt::Alignment::Int>( mFieldFormatters.at( index.column() )->alignmentFlag( mLayer, fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter );
711  }
712 
713  if ( mFeat.id() != rowId || !mFeat.isValid() )
714  {
715  if ( !loadFeatureAtId( rowId ) )
716  return QVariant( "ERROR" );
717 
718  if ( mFeat.id() != rowId )
719  return QVariant( "ERROR" );
720  }
721 
722  QVariant val = mFeat.attribute( fieldId );
723 
724  switch ( role )
725  {
726  case Qt::DisplayRole:
727  return mFieldFormatters.at( index.column() )->representValue( mLayer,
728  fieldId,
729  mWidgetConfigs.at( index.column() ),
730  mAttributeWidgetCaches.at( index.column() ),
731  val );
732  case Qt::ToolTipRole:
733  {
734  QString tooltip = mFieldFormatters.at( index.column() )->representValue( mLayer,
735  fieldId,
736  mWidgetConfigs.at( index.column() ),
737  mAttributeWidgetCaches.at( index.column() ),
738  val );
739  if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
740  {
741  tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
742  }
743  return tooltip;
744  }
745  case Qt::EditRole:
746  return val;
747 
748  case Qt::BackgroundRole:
749 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
750  case Qt::TextColorRole:
751 #else
752  case Qt::ForegroundRole:
753 #endif
754  case Qt::DecorationRole:
755  case Qt::FontRole:
756  {
757  mExpressionContext.setFeature( mFeat );
758  QList<QgsConditionalStyle> styles;
759  if ( mRowStylesMap.contains( mFeat.id() ) )
760  {
761  styles = mRowStylesMap[mFeat.id()];
762  }
763  else
764  {
765  styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
766  mRowStylesMap.insert( mFeat.id(), styles );
767  }
768 
769  const QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
770  styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
771  styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
772  styles.insert( 0, rowstyle );
774 
775  if ( style.isValid() )
776  {
777  if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
778  return style.backgroundColor();
779 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
780  if ( role == Qt::TextColorRole && style.validTextColor() )
781 #else
782  if ( role == Qt::ForegroundRole )
783 #endif
784  return style.textColor();
785  if ( role == Qt::DecorationRole )
786  return style.icon();
787  if ( role == Qt::FontRole )
788  return style.font();
789  }
790  else if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
791  {
792  if ( role == Qt::ForegroundRole )
793  {
794  return QColor( Qt::blue );
795  }
796  else if ( role == Qt::FontRole )
797  {
798  QFont font;
799  font.setUnderline( true );
800  return font;
801  }
802  }
803 
804  return QVariant();
805  }
806  }
807 
808  return QVariant();
809 }
810 
811 bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
812 {
813  Q_UNUSED( value )
814 
815  if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
816  return false;
817 
818  mRowStylesMap.remove( mFeat.id() );
819 
820  if ( !mLayer->isModified() )
821  return false;
822 
823  return true;
824 }
825 
826 Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
827 {
828  if ( !index.isValid() )
829  return Qt::ItemIsEnabled;
830 
831  if ( index.column() >= mFieldCount || !mLayer )
832  return Qt::NoItemFlags;
833 
834  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
835 
836  const int fieldIndex = mAttributes[index.column()];
837  const QgsFeatureId fid = rowToId( index.row() );
838 
839  if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
840  flags |= Qt::ItemIsEditable;
841 
842  return flags;
843 }
844 
845 void QgsAttributeTableModel::bulkEditCommandStarted()
846 {
847  mBulkEditCommandRunning = true;
848  mAttributeValueChanges.clear();
849 }
850 
851 void QgsAttributeTableModel::bulkEditCommandEnded()
852 {
853  mBulkEditCommandRunning = false;
854  // Full model update if the changed rows are more than half the total rows
855  // or if their count is > layer cache size
856  const int changeCount( std::max( mAttributeValueChanges.count(), mInsertedRowsChanges.count() ) );
857  const bool fullModelUpdate = changeCount > mLayerCache->cacheSize() ||
858  changeCount > rowCount() * 0.5;
859 
860  QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended with %1 modified rows over (%4), cache size is %2, starting %3 update." )
861  .arg( changeCount )
862  .arg( mLayerCache->cacheSize() )
863  .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
864  .arg( rowCount() ),
865  3 );
866 
867  // Remove added rows on rollback
868  if ( mIsCleaningUpAfterRollback )
869  {
870  for ( const int fid : std::as_const( mInsertedRowsChanges ) )
871  {
872  const int row( idToRow( fid ) );
873  if ( row < 0 )
874  {
875  continue;
876  }
877  removeRow( row );
878  }
879  }
880 
881  // Invalidates the whole model
882  if ( fullModelUpdate )
883  {
884  // Invalidates the cache (there is no API for doing this directly)
885  emit mLayer->dataChanged();
886  emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
887  }
888  else
889  {
890 
891  int minRow = rowCount();
892  int minCol = columnCount();
893  int maxRow = 0;
894  int maxCol = 0;
895  const auto keys = mAttributeValueChanges.keys();
896  for ( const auto &key : keys )
897  {
898  attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
899  const int row( idToRow( key.first ) );
900  const int col( fieldCol( key.second ) );
901  minRow = std::min<int>( row, minRow );
902  minCol = std::min<int>( col, minCol );
903  maxRow = std::max<int>( row, maxRow );
904  maxCol = std::max<int>( col, maxCol );
905  }
906 
907  emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
908  }
909  mAttributeValueChanges.clear();
910 }
911 
912 void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
913 {
914  mFeat.setId( std::numeric_limits<int>::min() );
915  emit dataChanged( index1, index2 );
916 }
917 
918 
919 void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
920 {
921  const QgsFeature f = feature( idx );
922  mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
923 }
924 
925 void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const
926 {
927  const QgsFeature f = feature( idx );
928  action->triggerForFeature( mLayer, f );
929 }
930 
931 QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
932 {
933  QgsFeature f( mLayer->fields() );
934  f.initAttributes( mAttributes.size() );
935  f.setId( rowToId( idx.row() ) );
936  for ( int i = 0; i < mAttributes.size(); i++ )
937  {
938  f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
939  }
940 
941  return f;
942 }
943 
945 {
946  if ( column == -1 || column >= mAttributes.count() )
947  {
948  prefetchSortData( QString() );
949  }
950  else
951  {
952  prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
953  }
954 }
955 
956 void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
957 {
958  if ( cacheIndex >= mSortCaches.size() )
959  {
960  mSortCaches.resize( cacheIndex + 1 );
961  }
962  SortCache &cache = mSortCaches[cacheIndex];
963  cache.sortCache.clear();
964  cache.sortCacheAttributes.clear();
965  cache.sortFieldIndex = -1;
966  if ( !expressionString.isEmpty() )
967  cache.sortCacheExpression = QgsExpression( expressionString );
968  else
969  {
970  // no sorting
971  cache.sortCacheExpression = QgsExpression();
972  return;
973  }
974 
975  QgsFieldFormatter *fieldFormatter = nullptr;
976  QVariant widgetCache;
977  QVariantMap widgetConfig;
978 
979  if ( cache.sortCacheExpression.isField() )
980  {
981  const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
982  cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
983  }
984 
985  if ( cache.sortFieldIndex == -1 )
986  {
987  cache.sortCacheExpression.prepare( &mExpressionContext );
988 
989  const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
990 
991  for ( const QString &col : referencedColumns )
992  {
993  cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
994  }
995  }
996  else
997  {
998  cache.sortCacheAttributes.append( cache.sortFieldIndex );
999 
1000  widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
1001  widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
1002  fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
1003  }
1004 
1005  const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
1007  .setSubsetOfAttributes( cache.sortCacheAttributes );
1008  QgsFeatureIterator it = mLayerCache->getFeatures( request );
1009 
1010  QgsFeature f;
1011  while ( it.nextFeature( f ) )
1012  {
1013  if ( cache.sortFieldIndex == -1 )
1014  {
1015  mExpressionContext.setFeature( f );
1016  const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1017  cache.sortCache.insert( f.id(), cacheValue );
1018  }
1019  else
1020  {
1021  const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) );
1022  cache.sortCache.insert( f.id(), sortValue );
1023  }
1024  }
1025 }
1026 
1027 QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1028 {
1029  QString expressionString;
1030 
1031  if ( cacheIndex >= mSortCaches.size() )
1032  return expressionString;
1033 
1034  const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1035 
1036  if ( expression.isValid() )
1037  expressionString = expression.expression();
1038  else
1039  expressionString = QString();
1040 
1041  return expressionString;
1042 }
1043 
1045 {
1046  mFeatureRequest = request;
1047  if ( mLayer && !mLayer->isSpatial() )
1048  mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry );
1049 }
1050 
1052 {
1053  return mFeatureRequest;
1054 }
void doAction(QUuid actionId, const QgsFeature &feature, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
const QgsFeatureRequest & request() const
Gets the the feature request.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
Remove rows.
Qt::ItemFlags flags(const QModelIndex &index) const override
Returns item flags for the index.
QgsFeature feature(const QModelIndex &idx) const
Returns the feature attributes at given model index.
void resetModel()
Resets the model.
void fieldConditionalStyleChanged(const QString &fieldName)
Handles updating the model when the conditional style for a field changes.
QString sortCacheExpression(unsigned long cacheIndex=0) const
The expression which was used to fill the sorting cache at index cacheIndex.
int fieldIdx(int col) const
Gets field index from column.
QgsAttributeTableModel(QgsVectorLayerCache *layerCache, QObject *parent=nullptr)
Constructor.
void swapRows(QgsFeatureId a, QgsFeatureId b)
Swaps two rows.
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.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Updates data on given index.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows.
QModelIndex idToIndex(QgsFeatureId id) const
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
int extraColumns() const
Empty extra columns to announce from this model.
void executeMapLayerAction(QgsMapLayerAction *action, const QModelIndex &idx) const
Execute a QgsMapLayerAction.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of columns.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Returns header data.
QModelIndexList idToIndexList(QgsFeatureId id) const
void prefetchSortData(const QString &expression, unsigned long cacheIndex=0)
Prefetches the entire data for an expression.
QgsFeatureId rowToId(int row) const
Maps row to feature id.
int idToRow(QgsFeatureId id) const
Maps feature id to table row.
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
@ SortRole
Role used for sorting start here.
@ FeatureIdRole
Get the feature id of the feature in this row.
@ FieldIndexRole
Get the field index of this column.
void setExtraColumns(int extraColumns)
Empty extra columns to announce from this model.
void prefetchColumnData(int column)
Caches the entire data for one column.
int fieldCol(int idx) const
Gets column from field index.
void reload(const QModelIndex &index1, const QModelIndex &index2)
Reloads the model data between indices.
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.
QgsConditionalStyles rowStyles() const
Returns a list of row styles associated with the layer.
QList< QgsConditionalStyle > fieldStyles(const QString &fieldName) const
Returns the conditional styles set for the field with matching fieldName.
Conditional styling for a rule.
static QgsConditionalStyle compressStyles(const QList< QgsConditionalStyle > &styles)
Compress a list of styles into a single style.
static QList< QgsConditionalStyle > matchingConditionalStyles(const QList< QgsConditionalStyle > &styles, const QVariant &value, QgsExpressionContext &context)
Find and return the matching styles for the value and feature.
QColor backgroundColor() const
The background color for style.
QColor textColor() const
The text color set for style.
QFont font() const
The font for the style.
bool validTextColor() const
Check if the text color is valid for render.
bool isValid() const
isValid Check if this rule is valid.
QPixmap icon() const
The icon set for style generated from the set symbol.
bool validBackgroundColor() const
Check if the background color is valid for render.
Every attribute editor widget needs a factory, which inherits this class.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
QgsEditorWidgetFactory * factory(const QString &widgetId)
Gets a factory for the given widget type id.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
An expression node which takes it value from a feature's field.
Class for parsing and evaluation of expressions (formerly called "search strings").
QString expression() const
Returns the original, unmodified expression string.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
bool isValid() const
Checks if this expression is valid.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
Flags flags() const
Returns the flags which affect how features are fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
bool acceptFeature(const QgsFeature &feature)
Check if a feature is accepted by this requests filter.
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
@ FilterNone
No filter is applied.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:255
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
Definition: qgsfeature.cpp:228
void setId(QgsFeatureId id)
Sets the feature id for this feature.
Definition: qgsfeature.cpp:115
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:209
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
QgsFieldFormatter * fallbackFieldFormatter() const
Returns a basic fallback field formatter which can be used to represent any field in an unspectacular...
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
virtual QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const
Create a cache for a given field.
virtual QVariant sortValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
If the default sort order should be overwritten for this widget, you can transform the value in here.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
static QString fieldToolTipExtended(const QgsField &field, const QgsVectorLayer *layer)
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
QString displayName() const
Returns the name to use when displaying this field.
Definition: qgsfield.cpp:89
Container of fields for a vector layer.
Definition: qgsfields.h:45
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:85
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition: qgslogger.h:108
An action which can run on map layers The class can be used in two manners:
virtual void triggerForFeature(QgsMapLayer *layer, const QgsFeature &feature)
Triggers the action with the specified layer and feature.
void dataChanged()
Data of layer changed.
static bool isUrl(const QString &string)
Returns whether the string is a URL (http,https,ftp,file)
This class caches features of a given QgsVectorLayer.
void invalidated()
The cache has been invalidated and cleared.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer and this cache.
void cachedLayerDeleted()
Is emitted when the cached layer is deleted.
void attributeValueChanged(QgsFeatureId fid, int field, const QVariant &value)
Emitted when an attribute is changed.
QgsVectorLayer * layer()
Returns the layer to which this cache belongs.
int cacheSize()
Returns the maximum number of features this cache will hold.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
bool featureAtId(QgsFeatureId featureId, QgsFeature &feature, bool skipCache=false)
Gets the feature at the given feature id.
static bool fieldIsEditable(const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature)
Tests whether a field is editable for a particular feature.
bool isModified() const override
Returns true if the provider has been modified since the last commit.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
void editCommandStarted(const QString &text)
Signal emitted when a new edit command has been started.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void featuresDeleted(const QgsFeatureIds &fids)
Emitted when features have been deleted.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
void attributeDeleted(int idx)
Will be emitted, when an attribute has been deleted from this vector layer.
QgsActionManager * actions()
Returns all layer actions defined on this layer.
void editCommandEnded()
Signal emitted, when an edit command successfully ended.
void afterRollBack()
Emitted after changes are rolled back.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QgsConditionalLayerStyles * conditionalStyles() const
Returns the conditional styles that are set for this layer.
void beforeRollBack()
Emitted before changes are rolled back.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
#define FID_TO_STRING(fid)
Definition: qgsfeatureid.h:33
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38