QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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->sortValue( 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  mFieldFormatters.clear();
395 
396  for ( int idx = 0; idx < fields.count(); ++idx )
397  {
398  const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fields[idx].name() );
399  QgsEditorWidgetFactory *widgetFactory = QgsGui::editorWidgetRegistry()->factory( setup.type() );
401 
402  mWidgetFactories.append( widgetFactory );
403  mWidgetConfigs.append( setup.config() );
404  mAttributeWidgetCaches.append( fieldFormatter->createCache( mLayer, idx, setup.config() ) );
405  mFieldFormatters.append( fieldFormatter );
406 
407  attributes << idx;
408  }
409 
410  if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
411  {
412  ins = true;
413  beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
414  }
415  else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
416  {
417  rm = true;
418  beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
419  }
420 
421  mFieldCount = attributes.size();
422  mAttributes = attributes;
423 
424  for ( SortCache &cache : mSortCaches )
425  {
426  if ( cache.sortFieldIndex >= mAttributes.count() )
427  cache.sortFieldIndex = -1;
428  }
429 
430  if ( ins )
431  {
432  endInsertColumns();
433  }
434  else if ( rm )
435  {
436  endRemoveColumns();
437  }
438 }
439 
441 {
442  // make sure attributes are properly updated before caching the data
443  // (emit of progress() signal may enter event loop and thus attribute
444  // table view may be updated with inconsistent model which may assume
445  // wrong number of attributes)
446 
447  loadAttributes();
448 
449  mResettingModel = true;
450  beginResetModel();
451 
452  if ( rowCount() != 0 )
453  {
454  removeRows( 0, rowCount() );
455  }
456 
457  // Layer might have been deleted and cache set to nullptr!
458  if ( mLayerCache )
459  {
460  QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
461 
462  int i = 0;
463 
464  QElapsedTimer t;
465  t.start();
466 
467  while ( features.nextFeature( mFeat ) )
468  {
469  ++i;
470 
471  if ( t.elapsed() > 1000 )
472  {
473  bool cancel = false;
474  emit progress( i, cancel );
475  if ( cancel )
476  break;
477 
478  t.restart();
479  }
480  featureAdded( mFeat.id() );
481  }
482 
483  emit finished();
484  connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
485  }
486 
487  endResetModel();
488 
489  mResettingModel = false;
490 }
491 
492 
494 {
495  if ( fieldName.isNull() )
496  {
497  mRowStylesMap.clear();
498  emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
499  return;
500  }
501 
502  const int fieldIndex = mLayer->fields().lookupField( fieldName );
503  if ( fieldIndex == -1 )
504  return;
505 
506  //whole column has changed
507  const int col = fieldCol( fieldIndex );
508  emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
509 }
510 
512 {
513  if ( a == b )
514  return;
515 
516  const int rowA = idToRow( a );
517  const int rowB = idToRow( b );
518 
519  //emit layoutAboutToBeChanged();
520 
521  mRowIdMap.remove( rowA );
522  mRowIdMap.remove( rowB );
523  mRowIdMap.insert( rowA, b );
524  mRowIdMap.insert( rowB, a );
525 
526  mIdRowMap.remove( a );
527  mIdRowMap.remove( b );
528  mIdRowMap.insert( a, rowB );
529  mIdRowMap.insert( b, rowA );
530  Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
531 
532 
533  //emit layoutChanged();
534 }
535 
537 {
538  if ( !mIdRowMap.contains( id ) )
539  {
540  QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
541  return -1;
542  }
543 
544  return mIdRowMap[id];
545 }
546 
548 {
549  return index( idToRow( id ), 0 );
550 }
551 
553 {
554  QModelIndexList indexes;
555 
556  const int row = idToRow( id );
557  const int columns = columnCount();
558  indexes.reserve( columns );
559  for ( int column = 0; column < columns; ++column )
560  {
561  indexes.append( index( row, column ) );
562  }
563 
564  return indexes;
565 }
566 
568 {
569  if ( !mRowIdMap.contains( row ) )
570  {
571  QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
572  // return negative infinite (to avoid collision with newly added features)
573  return std::numeric_limits<int>::min();
574  }
575 
576  return mRowIdMap[row];
577 }
578 
580 {
581  return mAttributes[col];
582 }
583 
585 {
586  return mAttributes.indexOf( idx );
587 }
588 
589 int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
590 {
591  Q_UNUSED( parent )
592  return mRowIdMap.size();
593 }
594 
595 int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
596 {
597  Q_UNUSED( parent )
598  return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
599 }
600 
601 QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
602 {
603  if ( !mLayer )
604  return QVariant();
605 
606  if ( role == Qt::DisplayRole )
607  {
608  if ( orientation == Qt::Vertical ) //row
609  {
610  return QVariant( section );
611  }
612  else if ( section >= 0 && section < mFieldCount )
613  {
614  const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
615  return QVariant( attributeName );
616  }
617  else
618  {
619  return tr( "extra column" );
620  }
621  }
622  else if ( role == Qt::ToolTipRole )
623  {
624  if ( orientation == Qt::Vertical )
625  {
626  // TODO show DisplayExpression
627  return tr( "Feature ID: %1" ).arg( rowToId( section ) );
628  }
629  else
630  {
631  const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
632  return QgsFieldModel::fieldToolTipExtended( field, mLayer );
633  }
634  }
635  else
636  {
637  return QVariant();
638  }
639 }
640 
641 QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
642 {
643  if ( !index.isValid() || !mLayer ||
644  ( role != Qt::TextAlignmentRole
645  && role != Qt::DisplayRole
646  && role != Qt::ToolTipRole
647  && role != Qt::EditRole
648  && role != FeatureIdRole
649  && role != FieldIndexRole
650 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
651  && role != Qt::BackgroundColorRole
652  && role != Qt::TextColorRole
653 #else
654  && role != Qt::BackgroundRole
655  && role != Qt::ForegroundRole
656 #endif
657  && role != Qt::DecorationRole
658  && role != Qt::FontRole
659  && role < SortRole
660  )
661  )
662  return QVariant();
663 
664  const QgsFeatureId rowId = rowToId( index.row() );
665 
666  if ( role == FeatureIdRole )
667  return rowId;
668 
669  if ( index.column() >= mFieldCount )
670  return QVariant();
671 
672  const int fieldId = mAttributes.at( index.column() );
673 
674  if ( role == FieldIndexRole )
675  return fieldId;
676 
677  if ( role >= SortRole )
678  {
679  const unsigned long cacheIndex = role - SortRole;
680  if ( cacheIndex < mSortCaches.size() )
681  return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
682  else
683  return QVariant();
684  }
685 
686  const QgsField field = mLayer->fields().at( fieldId );
687 
688  if ( role == Qt::TextAlignmentRole )
689  {
690  return static_cast<Qt::Alignment::Int>( mFieldFormatters.at( index.column() )->alignmentFlag( mLayer, fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter );
691  }
692 
693  if ( mFeat.id() != rowId || !mFeat.isValid() )
694  {
695  if ( !loadFeatureAtId( rowId ) )
696  return QVariant( "ERROR" );
697 
698  if ( mFeat.id() != rowId )
699  return QVariant( "ERROR" );
700  }
701 
702  QVariant val = mFeat.attribute( fieldId );
703 
704  switch ( role )
705  {
706  case Qt::DisplayRole:
707  return mFieldFormatters.at( index.column() )->representValue( mLayer,
708  fieldId,
709  mWidgetConfigs.at( index.column() ),
710  mAttributeWidgetCaches.at( index.column() ),
711  val );
712  case Qt::ToolTipRole:
713  {
714  QString tooltip = mFieldFormatters.at( index.column() )->representValue( mLayer,
715  fieldId,
716  mWidgetConfigs.at( index.column() ),
717  mAttributeWidgetCaches.at( index.column() ),
718  val );
719  if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
720  {
721  tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
722  }
723  return tooltip;
724  }
725  case Qt::EditRole:
726  return val;
727 
728  case Qt::BackgroundRole:
729 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
730  case Qt::TextColorRole:
731 #else
732  case Qt::ForegroundRole:
733 #endif
734  case Qt::DecorationRole:
735  case Qt::FontRole:
736  {
737  mExpressionContext.setFeature( mFeat );
738  QList<QgsConditionalStyle> styles;
739  if ( mRowStylesMap.contains( mFeat.id() ) )
740  {
741  styles = mRowStylesMap[mFeat.id()];
742  }
743  else
744  {
745  styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
746  mRowStylesMap.insert( mFeat.id(), styles );
747  }
748 
749  const QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
750  styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
751  styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
752  styles.insert( 0, rowstyle );
754 
755  if ( style.isValid() )
756  {
757  if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
758  return style.backgroundColor();
759 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
760  if ( role == Qt::TextColorRole && style.validTextColor() )
761 #else
762  if ( role == Qt::ForegroundRole )
763 #endif
764  return style.textColor();
765  if ( role == Qt::DecorationRole )
766  return style.icon();
767  if ( role == Qt::FontRole )
768  return style.font();
769  }
770  else if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
771  {
772  if ( role == Qt::ForegroundRole )
773  {
774  return QColor( Qt::blue );
775  }
776  else if ( role == Qt::FontRole )
777  {
778  QFont font;
779  font.setUnderline( true );
780  return font;
781  }
782  }
783 
784  return QVariant();
785  }
786  }
787 
788  return QVariant();
789 }
790 
791 bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
792 {
793  Q_UNUSED( value )
794 
795  if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
796  return false;
797 
798  mRowStylesMap.remove( mFeat.id() );
799 
800  if ( !mLayer->isModified() )
801  return false;
802 
803  return true;
804 }
805 
806 Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
807 {
808  if ( !index.isValid() )
809  return Qt::ItemIsEnabled;
810 
811  if ( index.column() >= mFieldCount || !mLayer )
812  return Qt::NoItemFlags;
813 
814  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
815 
816  const int fieldIndex = mAttributes[index.column()];
817  const QgsFeatureId fid = rowToId( index.row() );
818 
819  if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
820  flags |= Qt::ItemIsEditable;
821 
822  return flags;
823 }
824 
825 void QgsAttributeTableModel::bulkEditCommandStarted()
826 {
827  mBulkEditCommandRunning = true;
828  mAttributeValueChanges.clear();
829 }
830 
831 void QgsAttributeTableModel::bulkEditCommandEnded()
832 {
833  mBulkEditCommandRunning = false;
834  // Full model update if the changed rows are more than half the total rows
835  // or if their count is > layer cache size
836  const int changeCount( mAttributeValueChanges.count() );
837  const bool fullModelUpdate = changeCount > mLayerCache->cacheSize() ||
838  changeCount > rowCount() * 0.5;
839 
840  QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended with %1 modified rows over (%4), cache size is %2, starting %3 update." )
841  .arg( changeCount )
842  .arg( mLayerCache->cacheSize() )
843  .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
844  .arg( rowCount() ),
845  3 );
846  // Invalidates the whole model
847  if ( fullModelUpdate )
848  {
849  // Invalidates the cache (there is no API for doing this directly)
850  emit mLayer->dataChanged();
851  emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
852  }
853  else
854  {
855  int minRow = rowCount();
856  int minCol = columnCount();
857  int maxRow = 0;
858  int maxCol = 0;
859  const auto keys = mAttributeValueChanges.keys();
860  for ( const auto &key : keys )
861  {
862  attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
863  const int row( idToRow( key.first ) );
864  const int col( fieldCol( key.second ) );
865  minRow = std::min<int>( row, minRow );
866  minCol = std::min<int>( col, minCol );
867  maxRow = std::max<int>( row, maxRow );
868  maxCol = std::max<int>( col, maxCol );
869  }
870  emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
871  }
872  mAttributeValueChanges.clear();
873 }
874 
875 void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
876 {
877  mFeat.setId( std::numeric_limits<int>::min() );
878  emit dataChanged( index1, index2 );
879 }
880 
881 
882 void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
883 {
884  const QgsFeature f = feature( idx );
885  mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
886 }
887 
888 void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const
889 {
890  const QgsFeature f = feature( idx );
891  action->triggerForFeature( mLayer, f );
892 }
893 
894 QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
895 {
896  QgsFeature f( mLayer->fields() );
897  f.initAttributes( mAttributes.size() );
898  f.setId( rowToId( idx.row() ) );
899  for ( int i = 0; i < mAttributes.size(); i++ )
900  {
901  f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
902  }
903 
904  return f;
905 }
906 
908 {
909  if ( column == -1 || column >= mAttributes.count() )
910  {
911  prefetchSortData( QString() );
912  }
913  else
914  {
915  prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
916  }
917 }
918 
919 void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
920 {
921  if ( cacheIndex >= mSortCaches.size() )
922  {
923  mSortCaches.resize( cacheIndex + 1 );
924  }
925  SortCache &cache = mSortCaches[cacheIndex];
926  cache.sortCache.clear();
927  cache.sortCacheAttributes.clear();
928  cache.sortFieldIndex = -1;
929  if ( !expressionString.isEmpty() )
930  cache.sortCacheExpression = QgsExpression( expressionString );
931  else
932  {
933  // no sorting
934  cache.sortCacheExpression = QgsExpression();
935  return;
936  }
937 
938  QgsFieldFormatter *fieldFormatter = nullptr;
939  QVariant widgetCache;
940  QVariantMap widgetConfig;
941 
942  if ( cache.sortCacheExpression.isField() )
943  {
944  const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
945  cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
946  }
947 
948  if ( cache.sortFieldIndex == -1 )
949  {
950  cache.sortCacheExpression.prepare( &mExpressionContext );
951 
952  const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
953 
954  for ( const QString &col : referencedColumns )
955  {
956  cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
957  }
958  }
959  else
960  {
961  cache.sortCacheAttributes.append( cache.sortFieldIndex );
962 
963  widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
964  widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
965  fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
966  }
967 
968  const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
970  .setSubsetOfAttributes( cache.sortCacheAttributes );
971  QgsFeatureIterator it = mLayerCache->getFeatures( request );
972 
973  QgsFeature f;
974  while ( it.nextFeature( f ) )
975  {
976  if ( cache.sortFieldIndex == -1 )
977  {
978  mExpressionContext.setFeature( f );
979  const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
980  cache.sortCache.insert( f.id(), cacheValue );
981  }
982  else
983  {
984  const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) );
985  cache.sortCache.insert( f.id(), sortValue );
986  }
987  }
988 }
989 
990 QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
991 {
992  QString expressionString;
993 
994  if ( cacheIndex >= mSortCaches.size() )
995  return expressionString;
996 
997  const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
998 
999  if ( expression.isValid() )
1000  expressionString = expression.expression();
1001  else
1002  expressionString = QString();
1003 
1004  return expressionString;
1005 }
1006 
1008 {
1009  mFeatureRequest = request;
1010  if ( mLayer && !mLayer->isSpatial() )
1011  mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry );
1012 }
1013 
1015 {
1016  return mFeatureRequest;
1017 }
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:84
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