QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
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"
18#include "moc_qgsattributetablemodel.cpp"
19
20#include "qgsactionmanager.h"
22#include "qgsexpression.h"
23#include "qgsfeatureiterator.h"
24#include "qgsconditionalstyle.h"
25#include "qgsfields.h"
26#include "qgsfieldformatter.h"
27#include "qgslogger.h"
28#include "qgsmaplayeraction.h"
29#include "qgsvectorlayer.h"
32#include "qgsgui.h"
34#include "qgsfieldmodel.h"
36#include "qgsstringutils.h"
37#include "qgsvectorlayerutils.h"
38#include "qgsvectorlayercache.h"
39
40#include <QVariant>
41#include <QUuid>
42
43#include <limits>
44
46 : QAbstractTableModel( parent )
47 , mLayer( layerCache->layer() )
48 , mLayerCache( layerCache )
49{
51
52 if ( mLayer->geometryType() == Qgis::GeometryType::Null )
53 {
55 }
56
57 mFeat.setId( std::numeric_limits<int>::min() );
58
59 if ( !mLayer->isSpatial() )
61
62 loadAttributes();
63
64 connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted );
65 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted );
66 connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields );
67
68 connect( mLayer, &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted );
69 connect( mLayer, &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted );
70 connect( mLayer, &QgsVectorLayer::afterRollBack, this, [=] {
71 mIsCleaningUpAfterRollback = true;
72 bulkEditCommandEnded();
73 mIsCleaningUpAfterRollback = false;
74 } );
75
76 connect( mLayer, &QgsVectorLayer::editCommandEnded, this, &QgsAttributeTableModel::editCommandEnded );
77 connect( mLayerCache, &QgsVectorLayerCache::attributeValueChanged, this, &QgsAttributeTableModel::attributeValueChanged );
78 connect( mLayerCache, &QgsVectorLayerCache::featureAdded, this, [=]( QgsFeatureId id ) { featureAdded( id ); } );
79 connect( mLayerCache, &QgsVectorLayerCache::cachedLayerDeleted, this, &QgsAttributeTableModel::layerDeleted );
80}
81
82bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const
83{
84 QgsDebugMsgLevel( QStringLiteral( "loading feature %1" ).arg( fid ), 3 );
85
86 if ( fid == std::numeric_limits<int>::min() )
87 {
88 return false;
89 }
90
91 return mLayerCache->featureAtId( fid, mFeat );
92}
93
94bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid, int fieldIdx ) const
95{
96 QgsDebugMsgLevel( QStringLiteral( "loading feature %1 with field %2" ).arg( fid, fieldIdx ), 3 );
97
98 if ( mLayerCache->cacheSubsetOfAttributes().contains( fieldIdx ) )
99 {
100 return loadFeatureAtId( fid );
101 }
102
103 if ( fid == std::numeric_limits<int>::min() )
104 {
105 return false;
106 }
107 return mLayerCache->featureAtIdWithAllAttributes( fid, mFeat );
108}
109
111{
112 return mExtraColumns;
113}
114
116{
117 if ( extraColumns > mExtraColumns )
118 {
119 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, mFieldCount + extraColumns - 1 );
120 mExtraColumns = extraColumns;
121 endInsertColumns();
122 }
123 else if ( extraColumns < mExtraColumns )
124 {
125 beginRemoveColumns( QModelIndex(), mFieldCount + extraColumns, mFieldCount + mExtraColumns - 1 );
126 mExtraColumns = extraColumns;
127 endRemoveColumns();
128 }
129}
130
131void QgsAttributeTableModel::featuresDeleted( const QgsFeatureIds &fids )
132{
133 QList<int> rows;
134
135 const auto constFids = fids;
136 for ( const QgsFeatureId fid : constFids )
137 {
138 QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1, size: %3" ).arg( fid ).arg( qgsEnumValueToKey( mFeatureRequest.filterType() ) ).arg( mIdRowMap.size() ), 4 );
139
140 const int row = idToRow( fid );
141 if ( row != -1 )
142 rows << row;
143 }
144
145 std::sort( rows.begin(), rows.end() );
146
147 int lastRow = -1;
148 int beginRow = -1;
149 int currentRowCount = 0;
150 int removedRows = 0;
151 bool reset = false;
152
153 const auto constRows = rows;
154 for ( const int row : constRows )
155 {
156#if 0
157 qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows;
158#endif
159 if ( lastRow == -1 )
160 {
161 beginRow = row;
162 }
163
164 if ( row != lastRow + 1 && lastRow != -1 )
165 {
166 if ( rows.count() > 100 && currentRowCount < 10 )
167 {
168 reset = true;
169 break;
170 }
171 removeRows( beginRow - removedRows, currentRowCount );
172
173 beginRow = row;
174 removedRows += currentRowCount;
175 currentRowCount = 0;
176 }
177
178 currentRowCount++;
179
180 lastRow = row;
181 }
182
183 if ( !reset )
184 removeRows( beginRow - removedRows, currentRowCount );
185 else
186 resetModel();
187}
188
189bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent )
190{
191 if ( row < 0 || count < 1 )
192 return false;
193
194 if ( !mResettingModel )
195 {
196 beginRemoveRows( parent, row, row + count - 1 );
197 }
198
199#ifdef QGISDEBUG
200 if ( 3 <= QgsLogger::debugLevel() )
201 QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
202#endif
203
204 if ( mBulkEditCommandRunning && !mResettingModel )
205 {
206 for ( int i = row; i < row + count; i++ )
207 {
208 const QgsFeatureId fid { rowToId( row ) };
209 mInsertedRowsChanges.removeOne( fid );
210 }
211 }
212
213 // clean old references
214 for ( int i = row; i < row + count; i++ )
215 {
216 for ( SortCache &cache : mSortCaches )
217 cache.sortCache.remove( mRowIdMap[i] );
218 mIdRowMap.remove( mRowIdMap[i] );
219 mRowIdMap.remove( i );
220 }
221
222 // update maps
223 const int n = mRowIdMap.size() + count;
224 for ( int i = row + count; i < n; i++ )
225 {
226 const QgsFeatureId id = mRowIdMap[i];
227 mIdRowMap[id] -= count;
228 mRowIdMap[i - count] = id;
229 mRowIdMap.remove( i );
230 }
231
232#ifdef QGISDEBUG
233 if ( 4 <= QgsLogger::debugLevel() )
234 {
235 QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
236 QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 );
237 for ( QHash<QgsFeatureId, int>::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it )
238 {
239 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
240 }
241
242 QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 );
243 for ( QHash<int, QgsFeatureId>::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it )
244 {
245 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
246 }
247 }
248#endif
249
250 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
251
252 if ( !mResettingModel )
253 endRemoveRows();
254
255 return true;
256}
257
258void QgsAttributeTableModel::featureAdded( QgsFeatureId fid )
259{
260 QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( qgsEnumValueToKey( mFeatureRequest.filterType() ) ), 4 );
261 bool featOk = true;
262
263 if ( mFeat.id() != fid )
264 featOk = loadFeatureAtId( fid );
265
266 if ( featOk && mFeatureRequest.acceptFeature( mFeat ) )
267 {
268 for ( SortCache &cache : mSortCaches )
269 {
270 if ( cache.sortFieldIndex >= 0 )
271 {
272 const WidgetData &widgetData = getWidgetData( cache.sortFieldIndex );
273 const QVariant sortValue = widgetData.fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, mFeat.attribute( cache.sortFieldIndex ) );
274 cache.sortCache.insert( mFeat.id(), sortValue );
275 }
276 else if ( cache.sortCacheExpression.isValid() )
277 {
278 mExpressionContext.setFeature( mFeat );
279 cache.sortCache[mFeat.id()] = cache.sortCacheExpression.evaluate( &mExpressionContext );
280 }
281 }
282
283 // Skip if the fid is already in the map (do not add twice)!
284 if ( !mIdRowMap.contains( fid ) )
285 {
286 const int n = mRowIdMap.size();
287 if ( !mResettingModel )
288 beginInsertRows( QModelIndex(), n, n );
289 mIdRowMap.insert( fid, n );
290 mRowIdMap.insert( n, fid );
291 if ( !mResettingModel )
292 endInsertRows();
293 reload( index( rowCount() - 1, 0 ), index( rowCount() - 1, columnCount() ) );
294 if ( mBulkEditCommandRunning && !mResettingModel )
295 {
296 mInsertedRowsChanges.append( fid );
297 }
298 }
299 }
300}
301
302void QgsAttributeTableModel::updatedFields()
303{
304 loadAttributes();
305 emit modelChanged();
306}
307
308void QgsAttributeTableModel::editCommandEnded()
309{
310 // do not do reload(...) due would trigger (dataChanged) row sort
311 // giving issue: https://github.com/qgis/QGIS/issues/23892
312 bulkEditCommandEnded();
313}
314
315void QgsAttributeTableModel::attributeDeleted( int idx )
316{
317 int cacheIndex = 0;
318 for ( const SortCache &cache : mSortCaches )
319 {
320 if ( cache.sortCacheAttributes.contains( idx ) )
321 {
322 prefetchSortData( QString(), cacheIndex );
323 }
324 cacheIndex++;
325 }
326}
327
328void QgsAttributeTableModel::layerDeleted()
329{
330 mLayerCache = nullptr;
331 mLayer = nullptr;
332 removeRows( 0, rowCount() );
333
334 mAttributes.clear();
335 mWidgetDatas.clear();
336}
337
338void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter )
339{
340 for ( WidgetData &widgetData : mWidgetDatas )
341 {
342 if ( widgetData.fieldFormatter == fieldFormatter )
343 widgetData.fieldFormatter = QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter();
344 }
345}
346
347void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
348{
349 // Defer all updates if a bulk edit/rollback command is running
350 if ( mBulkEditCommandRunning )
351 {
352 mAttributeValueChanges.insert( QPair<QgsFeatureId, int>( fid, idx ), value );
353 return;
354 }
355 QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( qgsEnumValueToKey( mFeatureRequest.filterType() ) ), 2 );
356
357 for ( SortCache &cache : mSortCaches )
358 {
359 if ( cache.sortCacheAttributes.contains( idx ) )
360 {
361 if ( cache.sortFieldIndex == -1 )
362 {
363 if ( loadFeatureAtId( fid ) )
364 {
365 mExpressionContext.setFeature( mFeat );
366 cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext );
367 }
368 }
369 else
370 {
371 const WidgetData &widgetData = getWidgetData( cache.sortFieldIndex );
372 const QVariant sortValue = widgetData.fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, value );
373 cache.sortCache.insert( fid, sortValue );
374 }
375 }
376 }
377 // No filter request: skip all possibly heavy checks
378 if ( mFeatureRequest.filterType() == Qgis::FeatureRequestFilterType::NoFilter )
379 {
380 if ( loadFeatureAtId( fid ) )
381 {
382 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
383 setData( modelIndex, value, Qt::EditRole );
384 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
385 }
386 }
387 else
388 {
389 if ( loadFeatureAtId( fid ) )
390 {
391 if ( mFeatureRequest.acceptFeature( mFeat ) )
392 {
393 if ( !mIdRowMap.contains( fid ) )
394 {
395 // Feature changed in such a way, it will be shown now
396 featureAdded( fid );
397 }
398 else
399 {
400 // Update representation
401 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
402 setData( modelIndex, value, Qt::EditRole );
403 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
404 }
405 }
406 else
407 {
408 if ( mIdRowMap.contains( fid ) )
409 {
410 // Feature changed such, that it is no longer shown
411 featuresDeleted( QgsFeatureIds() << fid );
412 }
413 // else: we don't care
414 }
415 }
416 }
417}
418
419void QgsAttributeTableModel::loadAttributes()
420{
421 if ( !mLayer )
422 {
423 return;
424 }
425
426 const QgsFields fields = mLayer->fields();
427 if ( mFields == fields )
428 return;
429
430 mFields = fields;
431
432 bool ins = false, rm = false;
433
434 QgsAttributeList attributes;
435
436 mWidgetDatas.clear();
437
438 for ( int idx = 0; idx < fields.count(); ++idx )
439 {
440 attributes << idx;
441 }
442
443 if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
444 {
445 ins = true;
446 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
447 }
448 else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
449 {
450 rm = true;
451 beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
452 }
453
454 mFieldCount = attributes.size();
455 mAttributes = attributes;
456 mWidgetDatas.resize( mFieldCount );
457
458 for ( SortCache &cache : mSortCaches )
459 {
460 if ( cache.sortFieldIndex >= mAttributes.count() )
461 cache.sortFieldIndex = -1;
462 }
463
464 if ( ins )
465 {
466 endInsertColumns();
467 }
468 else if ( rm )
469 {
470 endRemoveColumns();
471 }
472}
473
475{
476 // make sure attributes are properly updated before caching the data
477 // (emit of progress() signal may enter event loop and thus attribute
478 // table view may be updated with inconsistent model which may assume
479 // wrong number of attributes)
480
481 loadAttributes();
482
483 mResettingModel = true;
484 beginResetModel();
485
486 if ( rowCount() != 0 )
487 {
488 removeRows( 0, rowCount() );
489 }
490
491 // Layer might have been deleted and cache set to nullptr!
492 if ( mLayerCache )
493 {
494 QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
495
496 int i = 0;
497
498 QElapsedTimer t;
499 t.start();
500
501 while ( features.nextFeature( mFeat ) )
502 {
503 ++i;
504
505 if ( t.elapsed() > 1000 )
506 {
507 bool cancel = false;
508 emit progress( i, cancel );
509 if ( cancel )
510 break;
511
512 t.restart();
513 }
514 featureAdded( mFeat.id() );
515 }
516
517 emit finished();
518 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
519 }
520
521 endResetModel();
522
523 mResettingModel = false;
524}
525
526
528{
529 if ( fieldName.isNull() )
530 {
531 mRowStylesMap.clear();
532 mConstraintStylesMap.clear();
533 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
534 return;
535 }
536
537 const int fieldIndex = mLayer->fields().lookupField( fieldName );
538 if ( fieldIndex == -1 )
539 return;
540
541 //whole column has changed
542 const int col = fieldCol( fieldIndex );
543 emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
544}
545
547{
548 if ( a == b )
549 return;
550
551 const int rowA = idToRow( a );
552 const int rowB = idToRow( b );
553
554 //emit layoutAboutToBeChanged();
555
556 mRowIdMap.remove( rowA );
557 mRowIdMap.remove( rowB );
558 mRowIdMap.insert( rowA, b );
559 mRowIdMap.insert( rowB, a );
560
561 mIdRowMap.remove( a );
562 mIdRowMap.remove( b );
563 mIdRowMap.insert( a, rowB );
564 mIdRowMap.insert( b, rowA );
565 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
566
567
568 //emit layoutChanged();
569}
570
572{
573 if ( !mIdRowMap.contains( id ) )
574 {
575 QgsDebugError( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
576 return -1;
577 }
578
579 return mIdRowMap[id];
580}
581
583{
584 return index( idToRow( id ), 0 );
585}
586
588{
589 QModelIndexList indexes;
590
591 const int row = idToRow( id );
592 const int columns = columnCount();
593 indexes.reserve( columns );
594 for ( int column = 0; column < columns; ++column )
595 {
596 indexes.append( index( row, column ) );
597 }
598
599 return indexes;
600}
601
603{
604 if ( !mRowIdMap.contains( row ) )
605 {
606 QgsDebugError( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
607 // return negative infinite (to avoid collision with newly added features)
608 return std::numeric_limits<int>::min();
609 }
610
611 return mRowIdMap[row];
612}
613
615{
616 return mAttributes[col];
617}
618
620{
621 return mAttributes.indexOf( idx );
622}
623
624int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
625{
626 Q_UNUSED( parent )
627 return mRowIdMap.size();
628}
629
630int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
631{
632 Q_UNUSED( parent )
633 return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
634}
635
636QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
637{
638 if ( !mLayer )
639 return QVariant();
640
641 if ( role == Qt::DisplayRole )
642 {
643 if ( orientation == Qt::Vertical ) //row
644 {
645 return QVariant( section );
646 }
647 else if ( section >= 0 && section < mFieldCount )
648 {
649 const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
650 return QVariant( attributeName );
651 }
652 else
653 {
654 return tr( "extra column" );
655 }
656 }
657 else if ( role == Qt::ToolTipRole )
658 {
659 if ( orientation == Qt::Vertical )
660 {
661 // TODO show DisplayExpression
662 return tr( "Feature ID: %1" ).arg( rowToId( section ) );
663 }
664 else
665 {
666 const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
667 return QgsFieldModel::fieldToolTipExtended( field, mLayer );
668 }
669 }
670 else
671 {
672 return QVariant();
673 }
674}
675
676QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
677{
678 if ( !index.isValid() || !mLayer || ( role != Qt::TextAlignmentRole && role != Qt::DisplayRole && role != Qt::ToolTipRole && role != Qt::EditRole && role != static_cast<int>( CustomRole::FeatureId ) && role != static_cast<int>( CustomRole::FieldIndex ) && role != Qt::BackgroundRole && role != Qt::ForegroundRole && role != Qt::DecorationRole && role != Qt::FontRole && role < static_cast<int>( CustomRole::Sort ) ) )
679 return QVariant();
680
681 const QgsFeatureId rowId = rowToId( index.row() );
682
683 if ( role == static_cast<int>( CustomRole::FeatureId ) )
684 return rowId;
685
686 if ( index.column() >= mFieldCount )
687 return QVariant();
688
689 const int fieldId = mAttributes.at( index.column() );
690
691 if ( role == static_cast<int>( CustomRole::FieldIndex ) )
692 return fieldId;
693
694 if ( role >= static_cast<int>( CustomRole::Sort ) )
695 {
696 const unsigned long cacheIndex = role - static_cast<int>( CustomRole::Sort );
697 if ( cacheIndex < mSortCaches.size() )
698 return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
699 else
700 return QVariant();
701 }
702
703 const QgsField field = mLayer->fields().at( fieldId );
704
705 if ( role == Qt::TextAlignmentRole )
706 {
707 const WidgetData &widgetData = getWidgetData( index.column() );
708 return static_cast<Qt::Alignment::Int>( widgetData.fieldFormatter->alignmentFlag( mLayer, fieldId, widgetData.config ) | Qt::AlignVCenter );
709 }
710
711 if ( mFeat.id() != rowId || !mFeat.isValid() || !mLayerCache->cacheSubsetOfAttributes().contains( fieldId ) )
712 {
713 if ( !loadFeatureAtId( rowId, fieldId ) )
714 return QVariant( "ERROR" );
715
716 if ( mFeat.id() != rowId )
717 return QVariant( "ERROR" );
718 }
719
720 QVariant val = mFeat.attribute( fieldId );
721
722 switch ( role )
723 {
724 case Qt::DisplayRole:
725 {
726 const WidgetData &widgetData = getWidgetData( index.column() );
727 QString s = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
728 // In table view, too long strings kill performance. Just truncate them
729 constexpr int MAX_STRING_LENGTH = 10 * 1000;
730 if ( static_cast<size_t>( s.size() ) > static_cast<size_t>( MAX_STRING_LENGTH ) )
731 {
732 s.resize( MAX_STRING_LENGTH );
733 s.append( tr( "... truncated ..." ) );
734 }
735 return s;
736 }
737 case Qt::ToolTipRole:
738 {
739 const WidgetData &widgetData = getWidgetData( index.column() );
740 QString tooltip = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
741 if ( val.userType() == QMetaType::Type::QString && QgsStringUtils::isUrl( val.toString() ) )
742 {
743 tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
744 }
745 return tooltip;
746 }
747 case Qt::EditRole:
748 return val;
749
750 case Qt::BackgroundRole:
751 case Qt::ForegroundRole:
752 case Qt::DecorationRole:
753 case Qt::FontRole:
754 {
755 mExpressionContext.setFeature( mFeat );
756 QList<QgsConditionalStyle> styles;
757 if ( mRowStylesMap.contains( mFeat.id() ) )
758 {
759 styles = mRowStylesMap[mFeat.id()];
760 }
761 else
762 {
763 styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
764 mRowStylesMap.insert( mFeat.id(), styles );
765 }
767
768 QgsConditionalStyle constraintstyle;
769 if ( mShowValidityState && QgsVectorLayerUtils::attributeHasConstraints( mLayer, fieldId ) )
770 {
771 if ( mConstraintStylesMap.contains( mFeat.id() ) && mConstraintStylesMap[mFeat.id()].contains( fieldId ) )
772 {
773 constraintstyle = mConstraintStylesMap[mFeat.id()][fieldId];
774 }
775 else
776 {
777 QStringList errors;
779 {
781 }
782 else
783 {
785 {
787 }
788 }
789 mConstraintStylesMap[mFeat.id()].insert( fieldId, constraintstyle );
790 }
791 }
792
793 styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
794 styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
795 styles.insert( 0, rowstyle );
796 styles.insert( 0, constraintstyle );
798
799 if ( style.isValid() )
800 {
801 if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
802 return style.backgroundColor();
803 if ( role == Qt::ForegroundRole )
804 return style.textColor();
805 if ( role == Qt::DecorationRole )
806 return style.icon();
807 if ( role == Qt::FontRole )
808 return style.font();
809 }
810 else if ( val.userType() == QMetaType::Type::QString && QgsStringUtils::isUrl( val.toString() ) )
811 {
812 if ( role == Qt::ForegroundRole )
813 {
814 return QColor( Qt::blue );
815 }
816 else if ( role == Qt::FontRole )
817 {
818 QFont font;
819 font.setUnderline( true );
820 return font;
821 }
822 }
823
824 return QVariant();
825 }
826 }
827
828 return QVariant();
829}
830
831bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
832{
833 Q_UNUSED( value )
834
835 if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
836 return false;
837
838 mRowStylesMap.remove( mFeat.id() );
839 mConstraintStylesMap.remove( mFeat.id() );
840
841 if ( !mLayer->isModified() )
842 return false;
843
844 return true;
845}
846
847Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
848{
849 if ( !index.isValid() )
850 return Qt::ItemIsEnabled;
851
852 if ( index.column() >= mFieldCount || !mLayer )
853 return Qt::NoItemFlags;
854
855 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
856
857 const int fieldIndex = mAttributes[index.column()];
858 const QgsFeatureId fid = rowToId( index.row() );
859
860 if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
861 flags |= Qt::ItemIsEditable;
862
863 return flags;
864}
865
866void QgsAttributeTableModel::bulkEditCommandStarted()
867{
868 mBulkEditCommandRunning = true;
869 mAttributeValueChanges.clear();
870}
871
872void QgsAttributeTableModel::bulkEditCommandEnded()
873{
874 mBulkEditCommandRunning = false;
875 // Full model update if the changed rows are more than half the total rows
876 // or if their count is > layer cache size
877
878 const long long fullModelUpdateThreshold = std::min<long long>( mLayerCache->cacheSize(), std::ceil( rowCount() * 0.5 ) );
879 bool fullModelUpdate = false;
880
881 // try the cheaper check first
882 if ( mInsertedRowsChanges.size() > fullModelUpdateThreshold )
883 {
884 fullModelUpdate = true;
885 }
886 else
887 {
888 QSet<QgsFeatureId> changedRows;
889 changedRows.reserve( mAttributeValueChanges.size() );
890 // we need to count changed features, not the total of changed attributes (which may all apply to one feature)
891 for ( auto it = mAttributeValueChanges.constBegin(); it != mAttributeValueChanges.constEnd(); ++it )
892 {
893 changedRows.insert( it.key().first );
894 if ( changedRows.size() > fullModelUpdateThreshold )
895 {
896 fullModelUpdate = true;
897 break;
898 }
899 }
900 }
901
902 QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended modified rows over (%3), cache size is %1, starting %2 update." ).arg( mLayerCache->cacheSize() ).arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) ).arg( rowCount() ), 3 );
903
904 // Remove added rows on rollback
905 if ( mIsCleaningUpAfterRollback )
906 {
907 for ( const int fid : std::as_const( mInsertedRowsChanges ) )
908 {
909 const int row( idToRow( fid ) );
910 if ( row < 0 )
911 {
912 continue;
913 }
914 removeRow( row );
915 }
916 }
917
918 // Invalidates the whole model
919 if ( fullModelUpdate )
920 {
921 // Invalidates the cache (there is no API for doing this directly)
922 emit mLayer->dataChanged();
923 emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
924 }
925 else
926 {
927 int minRow = rowCount();
928 int minCol = columnCount();
929 int maxRow = 0;
930 int maxCol = 0;
931 const auto keys = mAttributeValueChanges.keys();
932 for ( const auto &key : keys )
933 {
934 attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
935 const int row( idToRow( key.first ) );
936 const int col( fieldCol( key.second ) );
937 minRow = std::min<int>( row, minRow );
938 minCol = std::min<int>( col, minCol );
939 maxRow = std::max<int>( row, maxRow );
940 maxCol = std::max<int>( col, maxCol );
941 }
942
943 emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
944 }
945 mAttributeValueChanges.clear();
946}
947
948void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
949{
950 mFeat.setId( std::numeric_limits<int>::min() );
951 emit dataChanged( index1, index2 );
952}
953
954
955void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
956{
957 const QgsFeature f = feature( idx );
958 mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
959}
960
961void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx, const QgsMapLayerActionContext &context ) const
962{
963 const QgsFeature f = feature( idx );
965 action->triggerForFeature( mLayer, f );
967 action->triggerForFeature( mLayer, f, context );
968}
969
970QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
971{
972 QgsFeature f( mLayer->fields() );
973 f.initAttributes( mAttributes.size() );
974 f.setId( rowToId( idx.row() ) );
975 for ( int i = 0; i < mAttributes.size(); i++ )
976 {
977 f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
978 }
979
980 return f;
981}
982
984{
985 if ( column == -1 || column >= mAttributes.count() )
986 {
987 prefetchSortData( QString() );
988 }
989 else
990 {
991 prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
992 }
993}
994
995void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
996{
997 if ( cacheIndex >= mSortCaches.size() )
998 {
999 mSortCaches.resize( cacheIndex + 1 );
1000 }
1001 SortCache &cache = mSortCaches[cacheIndex];
1002 cache.sortCache.clear();
1003 cache.sortCacheAttributes.clear();
1004 cache.sortFieldIndex = -1;
1005 if ( !expressionString.isEmpty() )
1006 cache.sortCacheExpression = QgsExpression( expressionString );
1007 else
1008 {
1009 // no sorting
1010 cache.sortCacheExpression = QgsExpression();
1011 return;
1012 }
1013
1014 WidgetData widgetData;
1015
1016 if ( cache.sortCacheExpression.isField() )
1017 {
1018 const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
1019 cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
1020 }
1021
1022 if ( cache.sortFieldIndex == -1 )
1023 {
1024 cache.sortCacheExpression.prepare( &mExpressionContext );
1025
1026 const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
1027
1028 for ( const QString &col : referencedColumns )
1029 {
1030 cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
1031 }
1032 }
1033 else
1034 {
1035 cache.sortCacheAttributes.append( cache.sortFieldIndex );
1036
1037 widgetData = getWidgetData( cache.sortFieldIndex );
1038 }
1039
1040 const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest ).setFlags( cache.sortCacheExpression.needsGeometry() ? Qgis::FeatureRequestFlag::NoFlags : Qgis::FeatureRequestFlag::NoGeometry ).setSubsetOfAttributes( cache.sortCacheAttributes );
1041
1042 QgsFeatureIterator it = mLayerCache->getFeatures( request );
1043
1044 QgsFeature f;
1045 while ( it.nextFeature( f ) )
1046 {
1047 if ( cache.sortFieldIndex == -1 )
1048 {
1049 mExpressionContext.setFeature( f );
1050 const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1051 cache.sortCache.insert( f.id(), cacheValue );
1052 }
1053 else
1054 {
1055 const QVariant sortValue = widgetData.fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, f.attribute( cache.sortFieldIndex ) );
1056 cache.sortCache.insert( f.id(), sortValue );
1057 }
1058 }
1059}
1060
1061QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1062{
1063 QString expressionString;
1064
1065 if ( cacheIndex >= mSortCaches.size() )
1066 return expressionString;
1067
1068 const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1069
1070 if ( expression.isValid() )
1071 expressionString = expression.expression();
1072 else
1073 expressionString = QString();
1074
1075 return expressionString;
1076}
1077
1079{
1080 if ( !mFeatureRequest.compare( request ) )
1081 {
1082 mFeatureRequest = request;
1083 if ( mLayer && !mLayer->isSpatial() )
1084 mFeatureRequest.setFlags( mFeatureRequest.flags() | Qgis::FeatureRequestFlag::NoGeometry );
1085 // Prefetch data for sorting, resetting all caches
1086 for ( unsigned long i = 0; i < mSortCaches.size(); ++i )
1087 {
1089 }
1090 }
1091}
1092
1094{
1095 return mFeatureRequest;
1096}
1097
1098const QgsAttributeTableModel::WidgetData &QgsAttributeTableModel::getWidgetData( int column ) const
1099{
1100 Q_ASSERT( column >= 0 && column < mAttributes.size() );
1101
1102 WidgetData &widgetData = mWidgetDatas[column];
1103 if ( !widgetData.loaded )
1104 {
1105 const int idx = fieldIdx( column );
1106 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, mFields[idx].name() );
1107 widgetData.fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
1108 widgetData.config = setup.config();
1109 widgetData.cache = widgetData.fieldFormatter->createCache( mLayer, idx, setup.config() );
1110 widgetData.loaded = true;
1111 }
1112
1113 return widgetData;
1114}
@ NoFilter
No filter is applied.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFlags
No flags are set.
@ Null
No geometry.
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()
Emitted when the model has been changed.
void executeMapLayerAction(QgsMapLayerAction *action, const QModelIndex &idx, const QgsMapLayerActionContext &context=QgsMapLayerActionContext()) const
Execute a QgsMapLayerAction.
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
int extraColumns() const
Empty extra columns to announce from this model.
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
void finished()
Emitted when the model has completely loaded all features.
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...
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.
@ FeatureId
Get the feature id of the feature in this row.
@ Sort
Role used for sorting start here.
@ FieldIndex
Get the field index of this column.
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.
QgsConditionalStyle constraintFailureStyles(QgsFieldConstraints::ConstraintStrength strength)
Returns a style associated to a constraint failure.
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 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.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
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)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
Qgis::FeatureRequestFlags flags() const
Returns the flags which affect how features are fetched.
bool acceptFeature(const QgsFeature &feature)
Check if a feature is accepted by this requests filter.
bool compare(const QgsFeatureRequest &other) const
Compare two requests for equality, ignoring Expression Context, Transform Error Callback,...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
QgsFeatureId id
Definition qgsfeature.h:66
void setId(QgsFeatureId id)
Sets the feature id for this feature.
bool isValid() const
Returns the validity of this feature.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintStrengthSoft
User is warned if constraint is violated but feature can still be accepted.
@ ConstraintStrengthHard
Constraint must be honored before feature can be accepted.
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.
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:53
QString name
Definition qgsfield.h:62
QString displayName() const
Returns the name to use when displaying this field.
Definition qgsfield.cpp:95
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition qgsgui.cpp:95
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition qgslogger.h:108
Encapsulates the context in which a QgsMapLayerAction action is executed.
An action which can run on map layers The class can be used in two manners:
virtual Q_DECL_DEPRECATED 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.
bool featureAtIdWithAllAttributes(QgsFeatureId featureId, QgsFeature &feature, bool skipCache=false)
Gets the feature at the given feature id with all attributes, if the cached feature already contains ...
int cacheSize()
Returns the maximum number of features this cache will hold.
QgsAttributeList cacheSubsetOfAttributes() const
Returns the list (possibly a subset) of cached attributes.
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.
static bool attributeHasConstraints(const QgsVectorLayer *layer, int attributeIndex)
Returns true if a feature attribute has active constraints.
static bool validateAttribute(const QgsVectorLayer *layer, const QgsFeature &feature, int attributeIndex, QStringList &errors, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthNotSet, QgsFieldConstraints::ConstraintOrigin origin=QgsFieldConstraints::ConstraintOriginNotSet)
Tests a feature attribute value to check whether it passes all constraints which are present on the c...
bool isModified() const override
Returns true if the provider has been modified since the last commit.
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...
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.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
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.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6643
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6257
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6642
QSet< QgsFeatureId > QgsFeatureIds
#define FID_TO_STRING(fid)
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QList< int > QgsAttributeList
Definition qgsfield.h:27
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38