QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 "qgsvectorlayerutils.h"
44 #include "qgsvectorlayercache.h"
45 
46 #include <QVariant>
47 #include <QUuid>
48 
49 #include <limits>
50 
52  : QAbstractTableModel( parent )
53  , mLayer( layerCache->layer() )
54  , mLayerCache( layerCache )
55 {
57 
58  if ( mLayer->geometryType() == QgsWkbTypes::NullGeometry )
59  {
60  mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
61  }
62 
63  mFeat.setId( std::numeric_limits<int>::min() );
64 
65  if ( !mLayer->isSpatial() )
66  mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
67 
68  loadAttributes();
69 
70  connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted );
71  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted );
72  connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields );
73 
74  connect( mLayer, &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted );
75  connect( mLayer, &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted );
76  connect( mLayer, &QgsVectorLayer::afterRollBack, this, &QgsAttributeTableModel::bulkEditCommandEnded );
77 
78  connect( mLayer, &QgsVectorLayer::editCommandEnded, this, &QgsAttributeTableModel::editCommandEnded );
79  connect( mLayerCache, &QgsVectorLayerCache::attributeValueChanged, this, &QgsAttributeTableModel::attributeValueChanged );
80  connect( mLayerCache, &QgsVectorLayerCache::featureAdded, this, [ = ]( QgsFeatureId id ) { featureAdded( id ); } );
81  connect( mLayerCache, &QgsVectorLayerCache::cachedLayerDeleted, this, &QgsAttributeTableModel::layerDeleted );
82 }
83 
84 bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const
85 {
86  QgsDebugMsgLevel( QStringLiteral( "loading feature %1" ).arg( fid ), 3 );
87 
88  if ( fid == std::numeric_limits<int>::min() )
89  {
90  return false;
91  }
92 
93  return mLayerCache->featureAtId( fid, mFeat );
94 }
95 
97 {
98  return mExtraColumns;
99 }
100 
102 {
103  mExtraColumns = extraColumns;
104  loadAttributes();
105 }
106 
107 void QgsAttributeTableModel::featuresDeleted( const QgsFeatureIds &fids )
108 {
109  QList<int> rows;
110 
111  const auto constFids = fids;
112  for ( QgsFeatureId fid : constFids )
113  {
114  QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1, size: %3" ).arg( fid ).arg( mFeatureRequest.filterType() ).arg( mIdRowMap.size() ), 4 );
115 
116  int row = idToRow( fid );
117  if ( row != -1 )
118  rows << row;
119  }
120 
121  std::sort( rows.begin(), rows.end() );
122 
123  int lastRow = -1;
124  int beginRow = -1;
125  int currentRowCount = 0;
126  int removedRows = 0;
127  bool reset = false;
128 
129  const auto constRows = rows;
130  for ( int row : constRows )
131  {
132 #if 0
133  qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows;
134 #endif
135  if ( lastRow == -1 )
136  {
137  beginRow = row;
138  }
139 
140  if ( row != lastRow + 1 && lastRow != -1 )
141  {
142  if ( rows.count() > 100 && currentRowCount < 10 )
143  {
144  reset = true;
145  break;
146  }
147  removeRows( beginRow - removedRows, currentRowCount );
148 
149  beginRow = row;
150  removedRows += currentRowCount;
151  currentRowCount = 0;
152  }
153 
154  currentRowCount++;
155 
156  lastRow = row;
157  }
158 
159  if ( !reset )
160  removeRows( beginRow - removedRows, currentRowCount );
161  else
162  resetModel();
163 }
164 
165 bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent )
166 {
167 
168  if ( row < 0 || count < 1 )
169  return false;
170 
171  if ( !mResettingModel )
172  beginRemoveRows( parent, row, row + count - 1 );
173 
174 #ifdef QGISDEBUG
175  if ( 3 <= QgsLogger::debugLevel() )
176  QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
177 #endif
178 
179  // clean old references
180  for ( int i = row; i < row + count; i++ )
181  {
182  for ( SortCache &cache : mSortCaches )
183  cache.sortCache.remove( mRowIdMap[i] );
184  mIdRowMap.remove( mRowIdMap[i] );
185  mRowIdMap.remove( i );
186  }
187 
188  // update maps
189  int n = mRowIdMap.size() + count;
190  for ( int i = row + count; i < n; i++ )
191  {
192  QgsFeatureId id = mRowIdMap[i];
193  mIdRowMap[id] -= count;
194  mRowIdMap[i - count] = id;
195  mRowIdMap.remove( i );
196  }
197 
198 #ifdef QGISDEBUG
199  if ( 4 <= QgsLogger::debugLevel() )
200  {
201  QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
202  QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 );
203  for ( QHash<QgsFeatureId, int>::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it )
204  QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
205 
206  QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 );
207  for ( QHash<int, QgsFeatureId>::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it )
208  QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
209  }
210 #endif
211 
212  Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
213 
214  if ( !mResettingModel )
215  endRemoveRows();
216 
217  return true;
218 }
219 
220 void QgsAttributeTableModel::featureAdded( QgsFeatureId fid )
221 {
222  QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 );
223  bool featOk = true;
224 
225  if ( mFeat.id() != fid )
226  featOk = loadFeatureAtId( fid );
227 
228  if ( featOk && mFeatureRequest.acceptFeature( mFeat ) )
229  {
230  for ( SortCache &cache : mSortCaches )
231  {
232  if ( cache.sortFieldIndex >= 0 )
233  {
234  QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
235  const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
236  const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
237  QVariant sortValue = fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, mFeat.attribute( cache.sortFieldIndex ) );
238  cache.sortCache.insert( mFeat.id(), sortValue );
239  }
240  else if ( cache.sortCacheExpression.isValid() )
241  {
242  mExpressionContext.setFeature( mFeat );
243  cache.sortCache[mFeat.id()] = cache.sortCacheExpression.evaluate( &mExpressionContext );
244  }
245  }
246 
247  // Skip if the fid is already in the map (do not add twice)!
248  if ( ! mIdRowMap.contains( fid ) )
249  {
250  int n = mRowIdMap.size();
251  if ( !mResettingModel )
252  beginInsertRows( QModelIndex(), n, n );
253  mIdRowMap.insert( fid, n );
254  mRowIdMap.insert( n, fid );
255  if ( !mResettingModel )
256  endInsertRows();
257  reload( index( rowCount() - 1, 0 ), index( rowCount() - 1, columnCount() ) );
258  }
259  }
260 }
261 
262 void QgsAttributeTableModel::updatedFields()
263 {
264  loadAttributes();
265  emit modelChanged();
266 }
267 
268 void QgsAttributeTableModel::editCommandEnded()
269 {
270  // do not do reload(...) due would trigger (dataChanged) row sort
271  // giving issue: https://github.com/qgis/QGIS/issues/23892
272  bulkEditCommandEnded( );
273 }
274 
275 void QgsAttributeTableModel::attributeDeleted( int idx )
276 {
277  int cacheIndex = 0;
278  for ( const SortCache &cache : mSortCaches )
279  {
280  if ( cache.sortCacheAttributes.contains( idx ) )
281  {
282  prefetchSortData( QString(), cacheIndex );
283  }
284  cacheIndex++;
285  }
286 }
287 
288 void QgsAttributeTableModel::layerDeleted()
289 {
290  mLayerCache = nullptr;
291  mLayer = nullptr;
292  removeRows( 0, rowCount() );
293 
294  mAttributeWidgetCaches.clear();
295  mAttributes.clear();
296  mWidgetFactories.clear();
297  mWidgetConfigs.clear();
298  mFieldFormatters.clear();
299 }
300 
301 void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter )
302 {
303  for ( int i = 0; i < mFieldFormatters.size(); ++i )
304  {
305  if ( mFieldFormatters.at( i ) == fieldFormatter )
307  }
308 }
309 
310 void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
311 {
312  // Defer all updates if a bulk edit/rollback command is running
313  if ( mBulkEditCommandRunning )
314  {
315  mAttributeValueChanges.insert( QPair<QgsFeatureId, int>( fid, idx ), value );
316  return;
317  }
318  QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( mFeatureRequest.filterType() ), 2 );
319 
320  for ( SortCache &cache : mSortCaches )
321  {
322  if ( cache.sortCacheAttributes.contains( idx ) )
323  {
324  if ( cache.sortFieldIndex == -1 )
325  {
326  if ( loadFeatureAtId( fid ) )
327  {
328  mExpressionContext.setFeature( mFeat );
329  cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext );
330  }
331  }
332  else
333  {
334  QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
335  const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
336  const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
337  QVariant sortValue = fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, value );
338  cache.sortCache.insert( fid, sortValue );
339  }
340  }
341  }
342  // No filter request: skip all possibly heavy checks
343  if ( mFeatureRequest.filterType() == QgsFeatureRequest::FilterNone )
344  {
345  if ( loadFeatureAtId( fid ) )
346  setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
347  }
348  else
349  {
350  if ( loadFeatureAtId( fid ) )
351  {
352  if ( mFeatureRequest.acceptFeature( mFeat ) )
353  {
354  if ( !mIdRowMap.contains( fid ) )
355  {
356  // Feature changed in such a way, it will be shown now
357  featureAdded( fid );
358  }
359  else
360  {
361  // Update representation
362  setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
363  }
364  }
365  else
366  {
367  if ( mIdRowMap.contains( fid ) )
368  {
369  // Feature changed such, that it is no longer shown
370  featuresDeleted( QgsFeatureIds() << fid );
371  }
372  // else: we don't care
373  }
374  }
375  }
376 }
377 
378 void QgsAttributeTableModel::loadAttributes()
379 {
380  if ( !mLayer )
381  {
382  return;
383  }
384 
385  bool ins = false, rm = false;
386 
387  QgsAttributeList attributes;
388  const QgsFields &fields = mLayer->fields();
389 
390  mWidgetFactories.clear();
391  mAttributeWidgetCaches.clear();
392  mWidgetConfigs.clear();
393 
394  for ( int idx = 0; idx < fields.count(); ++idx )
395  {
396  const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fields[idx].name() );
397  QgsEditorWidgetFactory *widgetFactory = QgsGui::editorWidgetRegistry()->factory( setup.type() );
399 
400  mWidgetFactories.append( widgetFactory );
401  mWidgetConfigs.append( setup.config() );
402  mAttributeWidgetCaches.append( fieldFormatter->createCache( mLayer, idx, setup.config() ) );
403  mFieldFormatters.append( fieldFormatter );
404 
405  attributes << idx;
406  }
407 
408  if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
409  {
410  ins = true;
411  beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
412  }
413  else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
414  {
415  rm = true;
416  beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
417  }
418 
419  mFieldCount = attributes.size();
420  mAttributes = attributes;
421 
422  for ( SortCache &cache : mSortCaches )
423  {
424  if ( cache.sortFieldIndex >= mAttributes.count() )
425  cache.sortFieldIndex = -1;
426  }
427 
428  if ( ins )
429  {
430  endInsertColumns();
431  }
432  else if ( rm )
433  {
434  endRemoveColumns();
435  }
436 }
437 
439 {
440  // make sure attributes are properly updated before caching the data
441  // (emit of progress() signal may enter event loop and thus attribute
442  // table view may be updated with inconsistent model which may assume
443  // wrong number of attributes)
444 
445  loadAttributes();
446 
447  mResettingModel = true;
448  beginResetModel();
449 
450  if ( rowCount() != 0 )
451  {
452  removeRows( 0, rowCount() );
453  }
454 
455  // Layer might have been deleted and cache set to nullptr!
456  if ( mLayerCache )
457  {
458  QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
459 
460  int i = 0;
461 
462  QElapsedTimer t;
463  t.start();
464 
465  while ( features.nextFeature( mFeat ) )
466  {
467  ++i;
468 
469  if ( t.elapsed() > 1000 )
470  {
471  bool cancel = false;
472  emit progress( i, cancel );
473  if ( cancel )
474  break;
475 
476  t.restart();
477  }
478  featureAdded( mFeat.id() );
479  }
480 
481  emit finished();
482  connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
483  }
484 
485  endResetModel();
486 
487  mResettingModel = false;
488 }
489 
490 
492 {
493  if ( fieldName.isNull() )
494  {
495  mRowStylesMap.clear();
496  emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
497  return;
498  }
499 
500  int fieldIndex = mLayer->fields().lookupField( fieldName );
501  if ( fieldIndex == -1 )
502  return;
503 
504  //whole column has changed
505  int col = fieldCol( fieldIndex );
506  emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
507 }
508 
510 {
511  if ( a == b )
512  return;
513 
514  int rowA = idToRow( a );
515  int rowB = idToRow( b );
516 
517  //emit layoutAboutToBeChanged();
518 
519  mRowIdMap.remove( rowA );
520  mRowIdMap.remove( rowB );
521  mRowIdMap.insert( rowA, b );
522  mRowIdMap.insert( rowB, a );
523 
524  mIdRowMap.remove( a );
525  mIdRowMap.remove( b );
526  mIdRowMap.insert( a, rowB );
527  mIdRowMap.insert( b, rowA );
528  Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
529 
530 
531  //emit layoutChanged();
532 }
533 
535 {
536  if ( !mIdRowMap.contains( id ) )
537  {
538  QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
539  return -1;
540  }
541 
542  return mIdRowMap[id];
543 }
544 
546 {
547  return index( idToRow( id ), 0 );
548 }
549 
551 {
552  QModelIndexList indexes;
553 
554  int row = idToRow( id );
555  int columns = columnCount();
556  indexes.reserve( columns );
557  for ( int column = 0; column < columns; ++column )
558  {
559  indexes.append( index( row, column ) );
560  }
561 
562  return indexes;
563 }
564 
566 {
567  if ( !mRowIdMap.contains( row ) )
568  {
569  QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
570  // return negative infinite (to avoid collision with newly added features)
571  return std::numeric_limits<int>::min();
572  }
573 
574  return mRowIdMap[row];
575 }
576 
578 {
579  return mAttributes[col];
580 }
581 
583 {
584  return mAttributes.indexOf( idx );
585 }
586 
587 int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
588 {
589  Q_UNUSED( parent )
590  return mRowIdMap.size();
591 }
592 
593 int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
594 {
595  Q_UNUSED( parent )
596  return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
597 }
598 
599 QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
600 {
601  if ( !mLayer )
602  return QVariant();
603 
604  if ( role == Qt::DisplayRole )
605  {
606  if ( orientation == Qt::Vertical ) //row
607  {
608  return QVariant( section );
609  }
610  else if ( section >= 0 && section < mFieldCount )
611  {
612  QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
613  return QVariant( attributeName );
614  }
615  else
616  {
617  return tr( "extra column" );
618  }
619  }
620  else if ( role == Qt::ToolTipRole )
621  {
622  if ( orientation == Qt::Vertical )
623  {
624  // TODO show DisplayExpression
625  return tr( "Feature ID: %1" ).arg( rowToId( section ) );
626  }
627  else
628  {
629  const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
630  return QgsFieldModel::fieldToolTipExtended( field, mLayer );
631  }
632  }
633  else
634  {
635  return QVariant();
636  }
637 }
638 
639 QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
640 {
641  if ( !index.isValid() || !mLayer ||
642  ( role != Qt::TextAlignmentRole
643  && role != Qt::DisplayRole
644  && role != Qt::ToolTipRole
645  && role != Qt::EditRole
646  && role != FeatureIdRole
647  && role != FieldIndexRole
648 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
649  && role != Qt::BackgroundColorRole
650  && role != Qt::TextColorRole
651 #else
652  && role != Qt::BackgroundRole
653  && role != Qt::ForegroundRole
654 #endif
655  && role != Qt::DecorationRole
656  && role != Qt::FontRole
657  && role < SortRole
658  )
659  )
660  return QVariant();
661 
662  QgsFeatureId rowId = rowToId( index.row() );
663 
664  if ( role == FeatureIdRole )
665  return rowId;
666 
667  if ( index.column() >= mFieldCount )
668  return QVariant();
669 
670  int fieldId = mAttributes.at( index.column() );
671 
672  if ( role == FieldIndexRole )
673  return fieldId;
674 
675  if ( role >= SortRole )
676  {
677  unsigned long cacheIndex = role - SortRole;
678  if ( cacheIndex < mSortCaches.size() )
679  return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
680  else
681  return QVariant();
682  }
683 
684  QgsField field = mLayer->fields().at( fieldId );
685 
686  if ( role == Qt::TextAlignmentRole )
687  {
688  return QVariant( mFieldFormatters.at( index.column() )->alignmentFlag( mLayer, fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter );
689  }
690 
691  if ( mFeat.id() != rowId || !mFeat.isValid() )
692  {
693  if ( !loadFeatureAtId( rowId ) )
694  return QVariant( "ERROR" );
695 
696  if ( mFeat.id() != rowId )
697  return QVariant( "ERROR" );
698  }
699 
700  QVariant val = mFeat.attribute( fieldId );
701 
702  switch ( role )
703  {
704  case Qt::DisplayRole:
705  case Qt::ToolTipRole:
706  return mFieldFormatters.at( index.column() )->representValue( mLayer,
707  fieldId,
708  mWidgetConfigs.at( index.column() ),
709  mAttributeWidgetCaches.at( index.column() ),
710  val );
711 
712  case Qt::EditRole:
713  return val;
714 
715  case Qt::BackgroundRole:
716 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
717  case Qt::TextColorRole:
718 #else
719  case Qt::ForegroundRole:
720 #endif
721  case Qt::DecorationRole:
722  case Qt::FontRole:
723  {
724  mExpressionContext.setFeature( mFeat );
725  QList<QgsConditionalStyle> styles;
726  if ( mRowStylesMap.contains( mFeat.id() ) )
727  {
728  styles = mRowStylesMap[mFeat.id()];
729  }
730  else
731  {
732  styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
733  mRowStylesMap.insert( mFeat.id(), styles );
734  }
735 
737  styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
738  styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
739  styles.insert( 0, rowstyle );
741 
742  if ( style.isValid() )
743  {
744  if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
745  return style.backgroundColor();
746 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
747  if ( role == Qt::TextColorRole && style.validTextColor() )
748 #else
749  if ( role == Qt::ForegroundRole && style.validTextColor() )
750 #endif
751  return style.textColor();
752  if ( role == Qt::DecorationRole )
753  return style.icon();
754  if ( role == Qt::FontRole )
755  return style.font();
756  }
757 
758  return QVariant();
759  }
760  }
761 
762  return QVariant();
763 }
764 
765 bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
766 {
767  Q_UNUSED( value )
768 
769  if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
770  return false;
771 
772  mRowStylesMap.remove( mFeat.id() );
773 
774  if ( !mLayer->isModified() )
775  return false;
776 
777  return true;
778 }
779 
780 Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
781 {
782  if ( !index.isValid() )
783  return Qt::ItemIsEnabled;
784 
785  if ( index.column() >= mFieldCount || !mLayer )
786  return Qt::NoItemFlags;
787 
788  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
789 
790  const int fieldIndex = mAttributes[index.column()];
791  const QgsFeatureId fid = rowToId( index.row() );
792 
793  if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
794  flags |= Qt::ItemIsEditable;
795 
796  return flags;
797 }
798 
799 void QgsAttributeTableModel::bulkEditCommandStarted()
800 {
801  mBulkEditCommandRunning = true;
802  mAttributeValueChanges.clear();
803 }
804 
805 void QgsAttributeTableModel::bulkEditCommandEnded()
806 {
807  mBulkEditCommandRunning = false;
808  // Full model update if the changed rows are more than half the total rows
809  // or if their count is > layer cache size
810  int changeCount( mAttributeValueChanges.count() );
811  bool fullModelUpdate = changeCount > mLayerCache->cacheSize() ||
812  changeCount > rowCount() * 0.5;
813 
814  QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended with %1 modified rows over (%4), cache size is %2, starting %3 update." )
815  .arg( changeCount )
816  .arg( mLayerCache->cacheSize() )
817  .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
818  .arg( rowCount() ),
819  3 );
820  // Invalidates the whole model
821  if ( fullModelUpdate )
822  {
823  // Invalidates the cache (there is no API for doing this directly)
824  emit mLayer->dataChanged();
825  emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
826  }
827  else
828  {
829  int minRow = rowCount();
830  int minCol = columnCount();
831  int maxRow = 0;
832  int maxCol = 0;
833  const auto keys = mAttributeValueChanges.keys();
834  for ( const auto &key : keys )
835  {
836  attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
837  int row( idToRow( key.first ) );
838  int col( fieldCol( key.second ) );
839  minRow = std::min<int>( row, minRow );
840  minCol = std::min<int>( col, minCol );
841  maxRow = std::max<int>( row, maxRow );
842  maxCol = std::max<int>( col, maxCol );
843  }
844  emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
845  }
846  mAttributeValueChanges.clear();
847 }
848 
849 void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
850 {
851  mFeat.setId( std::numeric_limits<int>::min() );
852  emit dataChanged( index1, index2 );
853 }
854 
855 
856 void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
857 {
858  QgsFeature f = feature( idx );
859  mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
860 }
861 
862 void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const
863 {
864  QgsFeature f = feature( idx );
865  action->triggerForFeature( mLayer, f );
866 }
867 
868 QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
869 {
870  QgsFeature f( mLayer->fields() );
871  f.initAttributes( mAttributes.size() );
872  f.setId( rowToId( idx.row() ) );
873  for ( int i = 0; i < mAttributes.size(); i++ )
874  {
875  f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
876  }
877 
878  return f;
879 }
880 
882 {
883  if ( column == -1 || column >= mAttributes.count() )
884  {
885  prefetchSortData( QString() );
886  }
887  else
888  {
889  prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
890  }
891 }
892 
893 void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
894 {
895  if ( cacheIndex >= mSortCaches.size() )
896  {
897  mSortCaches.resize( cacheIndex + 1 );
898  }
899  SortCache &cache = mSortCaches[cacheIndex];
900  cache.sortCache.clear();
901  cache.sortCacheAttributes.clear();
902  cache.sortFieldIndex = -1;
903  if ( !expressionString.isEmpty() )
904  cache.sortCacheExpression = QgsExpression( expressionString );
905  else
906  {
907  // no sorting
908  cache.sortCacheExpression = QgsExpression();
909  return;
910  }
911 
912  QgsFieldFormatter *fieldFormatter = nullptr;
913  QVariant widgetCache;
914  QVariantMap widgetConfig;
915 
916  if ( cache.sortCacheExpression.isField() )
917  {
918  QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
919  cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
920  }
921 
922  if ( cache.sortFieldIndex == -1 )
923  {
924  cache.sortCacheExpression.prepare( &mExpressionContext );
925 
926  const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
927 
928  for ( const QString &col : referencedColumns )
929  {
930  cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
931  }
932  }
933  else
934  {
935  cache.sortCacheAttributes.append( cache.sortFieldIndex );
936 
937  widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
938  widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
939  fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
940  }
941 
942  QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
944  .setSubsetOfAttributes( cache.sortCacheAttributes );
945  QgsFeatureIterator it = mLayerCache->getFeatures( request );
946 
947  QgsFeature f;
948  while ( it.nextFeature( f ) )
949  {
950  if ( cache.sortFieldIndex == -1 )
951  {
952  mExpressionContext.setFeature( f );
953  const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
954  cache.sortCache.insert( f.id(), cacheValue );
955  }
956  else
957  {
958  QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) );
959  cache.sortCache.insert( f.id(), sortValue );
960  }
961  }
962 }
963 
964 QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
965 {
966  QString expressionString;
967 
968  if ( cacheIndex >= mSortCaches.size() )
969  return expressionString;
970 
971  const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
972 
973  if ( expression.isValid() )
974  expressionString = expression.expression();
975  else
976  expressionString = QString();
977 
978  return expressionString;
979 }
980 
982 {
983  mFeatureRequest = request;
984  if ( mLayer && !mLayer->isSpatial() )
985  mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry );
986 }
987 
989 {
990  return mFeatureRequest;
991 }
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.
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 filter type which is currently set on this request.
@ FilterNone
No filter is applied.
const Flags & flags() const
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:237
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
Definition: qgsfeature.cpp:210
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:191
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:302
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:88
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:344
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:76
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.
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.
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...
virtual bool isModified() const
Returns true if the provider has been modified since the last commit.
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