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