QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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
19#include "qgsactionmanager.h"
21#include "qgsexpression.h"
22#include "qgsfeatureiterator.h"
23#include "qgsconditionalstyle.h"
24#include "qgsfields.h"
25#include "qgsfieldformatter.h"
26#include "qgslogger.h"
27#include "qgsmaplayeraction.h"
28#include "qgsvectorlayer.h"
31#include "qgsgui.h"
33#include "qgsfieldmodel.h"
35#include "qgsstringutils.h"
36#include "qgsvectorlayerutils.h"
37#include "qgsvectorlayercache.h"
38
39#include <QVariant>
40#include <QUuid>
41
42#include <limits>
43
45 : QAbstractTableModel( parent )
46 , mLayer( layerCache->layer() )
47 , mLayerCache( layerCache )
48{
50
51 if ( mLayer->geometryType() == Qgis::GeometryType::Null )
52 {
54 }
55
56 mFeat.setId( std::numeric_limits<int>::min() );
57
58 if ( !mLayer->isSpatial() )
60
61 loadAttributes();
62
63 connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted );
64 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted );
65 connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields );
66
67 connect( mLayer, &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted );
68 connect( mLayer, &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted );
69 connect( mLayer, &QgsVectorLayer::afterRollBack, this, [ = ]
70 {
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
192 if ( row < 0 || count < 1 )
193 return false;
194
195 if ( !mResettingModel )
196 {
197 beginRemoveRows( parent, row, row + count - 1 );
198 }
199
200#ifdef QGISDEBUG
201 if ( 3 <= QgsLogger::debugLevel() )
202 QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
203#endif
204
205 if ( mBulkEditCommandRunning && !mResettingModel )
206 {
207 for ( int i = row; i < row + count; i++ )
208 {
209 const QgsFeatureId fid { rowToId( row ) };
210 mInsertedRowsChanges.removeOne( fid );
211 }
212 }
213
214 // clean old references
215 for ( int i = row; i < row + count; i++ )
216 {
217 for ( SortCache &cache : mSortCaches )
218 cache.sortCache.remove( mRowIdMap[i] );
219 mIdRowMap.remove( mRowIdMap[i] );
220 mRowIdMap.remove( i );
221 }
222
223 // update maps
224 const int n = mRowIdMap.size() + count;
225 for ( int i = row + count; i < n; i++ )
226 {
227 const QgsFeatureId id = mRowIdMap[i];
228 mIdRowMap[id] -= count;
229 mRowIdMap[i - count] = id;
230 mRowIdMap.remove( i );
231 }
232
233#ifdef QGISDEBUG
234 if ( 4 <= QgsLogger::debugLevel() )
235 {
236 QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
237 QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 );
238 for ( QHash<QgsFeatureId, int>::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it )
239 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
240
241 QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 );
242 for ( QHash<int, QgsFeatureId>::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it )
243 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
244 }
245#endif
246
247 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
248
249 if ( !mResettingModel )
250 endRemoveRows();
251
252 return true;
253}
254
255void QgsAttributeTableModel::featureAdded( QgsFeatureId fid )
256{
257 QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( qgsEnumValueToKey( mFeatureRequest.filterType() ) ), 4 );
258 bool featOk = true;
259
260 if ( mFeat.id() != fid )
261 featOk = loadFeatureAtId( fid );
262
263 if ( featOk && mFeatureRequest.acceptFeature( mFeat ) )
264 {
265 for ( SortCache &cache : mSortCaches )
266 {
267 if ( cache.sortFieldIndex >= 0 )
268 {
269 const WidgetData &widgetData = getWidgetData( cache.sortFieldIndex );
270 const QVariant sortValue = widgetData.fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, mFeat.attribute( cache.sortFieldIndex ) );
271 cache.sortCache.insert( mFeat.id(), sortValue );
272 }
273 else if ( cache.sortCacheExpression.isValid() )
274 {
275 mExpressionContext.setFeature( mFeat );
276 cache.sortCache[mFeat.id()] = cache.sortCacheExpression.evaluate( &mExpressionContext );
277 }
278 }
279
280 // Skip if the fid is already in the map (do not add twice)!
281 if ( ! mIdRowMap.contains( fid ) )
282 {
283 const int n = mRowIdMap.size();
284 if ( !mResettingModel )
285 beginInsertRows( QModelIndex(), n, n );
286 mIdRowMap.insert( fid, n );
287 mRowIdMap.insert( n, fid );
288 if ( !mResettingModel )
289 endInsertRows();
290 reload( index( rowCount() - 1, 0 ), index( rowCount() - 1, columnCount() ) );
291 if ( mBulkEditCommandRunning && !mResettingModel )
292 {
293 mInsertedRowsChanges.append( fid );
294 }
295 }
296 }
297}
298
299void QgsAttributeTableModel::updatedFields()
300{
301 loadAttributes();
302 emit modelChanged();
303}
304
305void QgsAttributeTableModel::editCommandEnded()
306{
307 // do not do reload(...) due would trigger (dataChanged) row sort
308 // giving issue: https://github.com/qgis/QGIS/issues/23892
309 bulkEditCommandEnded( );
310}
311
312void QgsAttributeTableModel::attributeDeleted( int idx )
313{
314 int cacheIndex = 0;
315 for ( const SortCache &cache : mSortCaches )
316 {
317 if ( cache.sortCacheAttributes.contains( idx ) )
318 {
319 prefetchSortData( QString(), cacheIndex );
320 }
321 cacheIndex++;
322 }
323}
324
325void QgsAttributeTableModel::layerDeleted()
326{
327 mLayerCache = nullptr;
328 mLayer = nullptr;
329 removeRows( 0, rowCount() );
330
331 mAttributes.clear();
332 mWidgetDatas.clear();
333}
334
335void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter )
336{
337 for ( WidgetData &widgetData : mWidgetDatas )
338 {
339 if ( widgetData.fieldFormatter == fieldFormatter )
340 widgetData.fieldFormatter = QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter();
341 }
342}
343
344void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
345{
346 // Defer all updates if a bulk edit/rollback command is running
347 if ( mBulkEditCommandRunning )
348 {
349 mAttributeValueChanges.insert( QPair<QgsFeatureId, int>( fid, idx ), value );
350 return;
351 }
352 QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( qgsEnumValueToKey( mFeatureRequest.filterType() ) ), 2 );
353
354 for ( SortCache &cache : mSortCaches )
355 {
356 if ( cache.sortCacheAttributes.contains( idx ) )
357 {
358 if ( cache.sortFieldIndex == -1 )
359 {
360 if ( loadFeatureAtId( fid ) )
361 {
362 mExpressionContext.setFeature( mFeat );
363 cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext );
364 }
365 }
366 else
367 {
368 const WidgetData &widgetData = getWidgetData( cache.sortFieldIndex );
369 const QVariant sortValue = widgetData.fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, value );
370 cache.sortCache.insert( fid, sortValue );
371 }
372 }
373 }
374 // No filter request: skip all possibly heavy checks
375 if ( mFeatureRequest.filterType() == Qgis::FeatureRequestFilterType::NoFilter )
376 {
377 if ( loadFeatureAtId( fid ) )
378 {
379 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
380 setData( modelIndex, value, Qt::EditRole );
381 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
382 }
383 }
384 else
385 {
386 if ( loadFeatureAtId( fid ) )
387 {
388 if ( mFeatureRequest.acceptFeature( mFeat ) )
389 {
390 if ( !mIdRowMap.contains( fid ) )
391 {
392 // Feature changed in such a way, it will be shown now
393 featureAdded( fid );
394 }
395 else
396 {
397 // Update representation
398 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
399 setData( modelIndex, value, Qt::EditRole );
400 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
401 }
402 }
403 else
404 {
405 if ( mIdRowMap.contains( fid ) )
406 {
407 // Feature changed such, that it is no longer shown
408 featuresDeleted( QgsFeatureIds() << fid );
409 }
410 // else: we don't care
411 }
412 }
413 }
414}
415
416void QgsAttributeTableModel::loadAttributes()
417{
418 if ( !mLayer )
419 {
420 return;
421 }
422
423 const QgsFields fields = mLayer->fields();
424 if ( mFields == fields )
425 return;
426
427 mFields = fields;
428
429 bool ins = false, rm = false;
430
431 QgsAttributeList attributes;
432
433 mWidgetDatas.clear();
434
435 for ( int idx = 0; idx < fields.count(); ++idx )
436 {
437 attributes << idx;
438 }
439
440 if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
441 {
442 ins = true;
443 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
444 }
445 else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
446 {
447 rm = true;
448 beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
449 }
450
451 mFieldCount = attributes.size();
452 mAttributes = attributes;
453 mWidgetDatas.resize( mFieldCount );
454
455 for ( SortCache &cache : mSortCaches )
456 {
457 if ( cache.sortFieldIndex >= mAttributes.count() )
458 cache.sortFieldIndex = -1;
459 }
460
461 if ( ins )
462 {
463 endInsertColumns();
464 }
465 else if ( rm )
466 {
467 endRemoveColumns();
468 }
469}
470
472{
473 // make sure attributes are properly updated before caching the data
474 // (emit of progress() signal may enter event loop and thus attribute
475 // table view may be updated with inconsistent model which may assume
476 // wrong number of attributes)
477
478 loadAttributes();
479
480 mResettingModel = true;
481 beginResetModel();
482
483 if ( rowCount() != 0 )
484 {
485 removeRows( 0, rowCount() );
486 }
487
488 // Layer might have been deleted and cache set to nullptr!
489 if ( mLayerCache )
490 {
491 QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
492
493 int i = 0;
494
495 QElapsedTimer t;
496 t.start();
497
498 while ( features.nextFeature( mFeat ) )
499 {
500 ++i;
501
502 if ( t.elapsed() > 1000 )
503 {
504 bool cancel = false;
505 emit progress( i, cancel );
506 if ( cancel )
507 break;
508
509 t.restart();
510 }
511 featureAdded( mFeat.id() );
512 }
513
514 emit finished();
515 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
516 }
517
518 endResetModel();
519
520 mResettingModel = false;
521}
522
523
525{
526 if ( fieldName.isNull() )
527 {
528 mRowStylesMap.clear();
529 mConstraintStylesMap.clear();
530 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
531 return;
532 }
533
534 const int fieldIndex = mLayer->fields().lookupField( fieldName );
535 if ( fieldIndex == -1 )
536 return;
537
538 //whole column has changed
539 const int col = fieldCol( fieldIndex );
540 emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
541}
542
544{
545 if ( a == b )
546 return;
547
548 const int rowA = idToRow( a );
549 const int rowB = idToRow( b );
550
551 //emit layoutAboutToBeChanged();
552
553 mRowIdMap.remove( rowA );
554 mRowIdMap.remove( rowB );
555 mRowIdMap.insert( rowA, b );
556 mRowIdMap.insert( rowB, a );
557
558 mIdRowMap.remove( a );
559 mIdRowMap.remove( b );
560 mIdRowMap.insert( a, rowB );
561 mIdRowMap.insert( b, rowA );
562 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
563
564
565 //emit layoutChanged();
566}
567
569{
570 if ( !mIdRowMap.contains( id ) )
571 {
572 QgsDebugError( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
573 return -1;
574 }
575
576 return mIdRowMap[id];
577}
578
580{
581 return index( idToRow( id ), 0 );
582}
583
585{
586 QModelIndexList indexes;
587
588 const int row = idToRow( id );
589 const int columns = columnCount();
590 indexes.reserve( columns );
591 for ( int column = 0; column < columns; ++column )
592 {
593 indexes.append( index( row, column ) );
594 }
595
596 return indexes;
597}
598
600{
601 if ( !mRowIdMap.contains( row ) )
602 {
603 QgsDebugError( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
604 // return negative infinite (to avoid collision with newly added features)
605 return std::numeric_limits<int>::min();
606 }
607
608 return mRowIdMap[row];
609}
610
612{
613 return mAttributes[col];
614}
615
617{
618 return mAttributes.indexOf( idx );
619}
620
621int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
622{
623 Q_UNUSED( parent )
624 return mRowIdMap.size();
625}
626
627int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
628{
629 Q_UNUSED( parent )
630 return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
631}
632
633QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
634{
635 if ( !mLayer )
636 return QVariant();
637
638 if ( role == Qt::DisplayRole )
639 {
640 if ( orientation == Qt::Vertical ) //row
641 {
642 return QVariant( section );
643 }
644 else if ( section >= 0 && section < mFieldCount )
645 {
646 const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
647 return QVariant( attributeName );
648 }
649 else
650 {
651 return tr( "extra column" );
652 }
653 }
654 else if ( role == Qt::ToolTipRole )
655 {
656 if ( orientation == Qt::Vertical )
657 {
658 // TODO show DisplayExpression
659 return tr( "Feature ID: %1" ).arg( rowToId( section ) );
660 }
661 else
662 {
663 const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
664 return QgsFieldModel::fieldToolTipExtended( field, mLayer );
665 }
666 }
667 else
668 {
669 return QVariant();
670 }
671}
672
673QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
674{
675 if ( !index.isValid() || !mLayer ||
676 ( role != Qt::TextAlignmentRole
677 && role != Qt::DisplayRole
678 && role != Qt::ToolTipRole
679 && role != Qt::EditRole
680 && role != static_cast< int >( CustomRole::FeatureId )
681 && role != static_cast< int >( CustomRole::FieldIndex )
682 && role != Qt::BackgroundRole
683 && role != Qt::ForegroundRole
684 && role != Qt::DecorationRole
685 && role != Qt::FontRole
686 && role < static_cast< int >( CustomRole::Sort )
687 )
688 )
689 return QVariant();
690
691 const QgsFeatureId rowId = rowToId( index.row() );
692
693 if ( role == static_cast< int >( CustomRole::FeatureId ) )
694 return rowId;
695
696 if ( index.column() >= mFieldCount )
697 return QVariant();
698
699 const int fieldId = mAttributes.at( index.column() );
700
701 if ( role == static_cast< int >( CustomRole::FieldIndex ) )
702 return fieldId;
703
704 if ( role >= static_cast< int >( CustomRole::Sort ) )
705 {
706 const unsigned long cacheIndex = role - static_cast< int >( CustomRole::Sort );
707 if ( cacheIndex < mSortCaches.size() )
708 return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
709 else
710 return QVariant();
711 }
712
713 const QgsField field = mLayer->fields().at( fieldId );
714
715 if ( role == Qt::TextAlignmentRole )
716 {
717 const WidgetData &widgetData = getWidgetData( index.column() );
718 return static_cast<Qt::Alignment::Int>( widgetData.fieldFormatter->alignmentFlag( mLayer, fieldId, widgetData.config ) | Qt::AlignVCenter );
719 }
720
721 if ( mFeat.id() != rowId || !mFeat.isValid() || ! mLayerCache->cacheSubsetOfAttributes().contains( fieldId ) )
722 {
723 if ( !loadFeatureAtId( rowId, fieldId ) )
724 return QVariant( "ERROR" );
725
726 if ( mFeat.id() != rowId )
727 return QVariant( "ERROR" );
728 }
729
730 QVariant val = mFeat.attribute( fieldId );
731
732 switch ( role )
733 {
734 case Qt::DisplayRole:
735 {
736 const WidgetData &widgetData = getWidgetData( index.column() );
737 QString s = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
738 // In table view, too long strings kill performance. Just truncate them
739 constexpr int MAX_STRING_LENGTH = 10 * 1000;
740 if ( static_cast<size_t>( s.size() ) > static_cast<size_t>( MAX_STRING_LENGTH ) )
741 {
742 s.resize( MAX_STRING_LENGTH );
743 s.append( tr( "... truncated ..." ) );
744 }
745 return s;
746 }
747 case Qt::ToolTipRole:
748 {
749 const WidgetData &widgetData = getWidgetData( index.column() );
750 QString tooltip = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
751 if ( val.userType() == QMetaType::Type::QString && QgsStringUtils::isUrl( val.toString() ) )
752 {
753 tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
754 }
755 return tooltip;
756 }
757 case Qt::EditRole:
758 return val;
759
760 case Qt::BackgroundRole:
761 case Qt::ForegroundRole:
762 case Qt::DecorationRole:
763 case Qt::FontRole:
764 {
765 mExpressionContext.setFeature( mFeat );
766 QList<QgsConditionalStyle> styles;
767 if ( mRowStylesMap.contains( mFeat.id() ) )
768 {
769 styles = mRowStylesMap[mFeat.id()];
770 }
771 else
772 {
773 styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
774 mRowStylesMap.insert( mFeat.id(), styles );
775 }
777
778 QgsConditionalStyle constraintstyle;
779 if ( mShowValidityState && QgsVectorLayerUtils::attributeHasConstraints( mLayer, fieldId ) )
780 {
781 if ( mConstraintStylesMap.contains( mFeat.id() ) &&
782 mConstraintStylesMap[mFeat.id()].contains( fieldId ) )
783 {
784 constraintstyle = mConstraintStylesMap[mFeat.id()][fieldId];
785 }
786 else
787 {
788 QStringList errors;
790 {
792 }
793 else
794 {
796 {
798 }
799 }
800 mConstraintStylesMap[mFeat.id()].insert( fieldId, constraintstyle );
801 }
802 }
803
804 styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
805 styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
806 styles.insert( 0, rowstyle );
807 styles.insert( 0, constraintstyle );
809
810 if ( style.isValid() )
811 {
812 if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
813 return style.backgroundColor();
814 if ( role == Qt::ForegroundRole )
815 return style.textColor();
816 if ( role == Qt::DecorationRole )
817 return style.icon();
818 if ( role == Qt::FontRole )
819 return style.font();
820 }
821 else if ( val.userType() == QMetaType::Type::QString && QgsStringUtils::isUrl( val.toString() ) )
822 {
823 if ( role == Qt::ForegroundRole )
824 {
825 return QColor( Qt::blue );
826 }
827 else if ( role == Qt::FontRole )
828 {
829 QFont font;
830 font.setUnderline( true );
831 return font;
832 }
833 }
834
835 return QVariant();
836 }
837 }
838
839 return QVariant();
840}
841
842bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
843{
844 Q_UNUSED( value )
845
846 if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
847 return false;
848
849 mRowStylesMap.remove( mFeat.id() );
850 mConstraintStylesMap.remove( mFeat.id() );
851
852 if ( !mLayer->isModified() )
853 return false;
854
855 return true;
856}
857
858Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
859{
860 if ( !index.isValid() )
861 return Qt::ItemIsEnabled;
862
863 if ( index.column() >= mFieldCount || !mLayer )
864 return Qt::NoItemFlags;
865
866 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
867
868 const int fieldIndex = mAttributes[index.column()];
869 const QgsFeatureId fid = rowToId( index.row() );
870
871 if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
872 flags |= Qt::ItemIsEditable;
873
874 return flags;
875}
876
877void QgsAttributeTableModel::bulkEditCommandStarted()
878{
879 mBulkEditCommandRunning = true;
880 mAttributeValueChanges.clear();
881}
882
883void QgsAttributeTableModel::bulkEditCommandEnded()
884{
885 mBulkEditCommandRunning = false;
886 // Full model update if the changed rows are more than half the total rows
887 // or if their count is > layer cache size
888
889 const long long fullModelUpdateThreshold = std::min<long long >( mLayerCache->cacheSize(), std::ceil( rowCount() * 0.5 ) );
890 bool fullModelUpdate = false;
891
892 // try the cheaper check first
893 if ( mInsertedRowsChanges.size() > fullModelUpdateThreshold )
894 {
895 fullModelUpdate = true;
896 }
897 else
898 {
899 QSet< QgsFeatureId > changedRows;
900 changedRows.reserve( mAttributeValueChanges.size() );
901 // we need to count changed features, not the total of changed attributes (which may all apply to one feature)
902 for ( auto it = mAttributeValueChanges.constBegin(); it != mAttributeValueChanges.constEnd(); ++it )
903 {
904 changedRows.insert( it.key().first );
905 if ( changedRows.size() > fullModelUpdateThreshold )
906 {
907 fullModelUpdate = true;
908 break;
909 }
910 }
911 }
912
913 QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended modified rows over (%3), cache size is %1, starting %2 update." )
914 .arg( mLayerCache->cacheSize() )
915 .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
916 .arg( rowCount() ),
917 3 );
918
919 // Remove added rows on rollback
920 if ( mIsCleaningUpAfterRollback )
921 {
922 for ( const int fid : std::as_const( mInsertedRowsChanges ) )
923 {
924 const int row( idToRow( fid ) );
925 if ( row < 0 )
926 {
927 continue;
928 }
929 removeRow( row );
930 }
931 }
932
933 // Invalidates the whole model
934 if ( fullModelUpdate )
935 {
936 // Invalidates the cache (there is no API for doing this directly)
937 emit mLayer->dataChanged();
938 emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
939 }
940 else
941 {
942
943 int minRow = rowCount();
944 int minCol = columnCount();
945 int maxRow = 0;
946 int maxCol = 0;
947 const auto keys = mAttributeValueChanges.keys();
948 for ( const auto &key : keys )
949 {
950 attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
951 const int row( idToRow( key.first ) );
952 const int col( fieldCol( key.second ) );
953 minRow = std::min<int>( row, minRow );
954 minCol = std::min<int>( col, minCol );
955 maxRow = std::max<int>( row, maxRow );
956 maxCol = std::max<int>( col, maxCol );
957 }
958
959 emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
960 }
961 mAttributeValueChanges.clear();
962}
963
964void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
965{
966 mFeat.setId( std::numeric_limits<int>::min() );
967 emit dataChanged( index1, index2 );
968}
969
970
971void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
972{
973 const QgsFeature f = feature( idx );
974 mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
975}
976
977void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx, const QgsMapLayerActionContext &context ) const
978{
979 const QgsFeature f = feature( idx );
981 action->triggerForFeature( mLayer, f );
983 action->triggerForFeature( mLayer, f, context );
984}
985
986QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
987{
988 QgsFeature f( mLayer->fields() );
989 f.initAttributes( mAttributes.size() );
990 f.setId( rowToId( idx.row() ) );
991 for ( int i = 0; i < mAttributes.size(); i++ )
992 {
993 f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
994 }
995
996 return f;
997}
998
1000{
1001 if ( column == -1 || column >= mAttributes.count() )
1002 {
1003 prefetchSortData( QString() );
1004 }
1005 else
1006 {
1007 prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
1008 }
1009}
1010
1011void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
1012{
1013 if ( cacheIndex >= mSortCaches.size() )
1014 {
1015 mSortCaches.resize( cacheIndex + 1 );
1016 }
1017 SortCache &cache = mSortCaches[cacheIndex];
1018 cache.sortCache.clear();
1019 cache.sortCacheAttributes.clear();
1020 cache.sortFieldIndex = -1;
1021 if ( !expressionString.isEmpty() )
1022 cache.sortCacheExpression = QgsExpression( expressionString );
1023 else
1024 {
1025 // no sorting
1026 cache.sortCacheExpression = QgsExpression();
1027 return;
1028 }
1029
1030 WidgetData widgetData;
1031
1032 if ( cache.sortCacheExpression.isField() )
1033 {
1034 const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
1035 cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
1036 }
1037
1038 if ( cache.sortFieldIndex == -1 )
1039 {
1040 cache.sortCacheExpression.prepare( &mExpressionContext );
1041
1042 const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
1043
1044 for ( const QString &col : referencedColumns )
1045 {
1046 cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
1047 }
1048 }
1049 else
1050 {
1051 cache.sortCacheAttributes.append( cache.sortFieldIndex );
1052
1053 widgetData = getWidgetData( cache.sortFieldIndex );
1054 }
1055
1056 const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest ).setFlags( cache.sortCacheExpression.needsGeometry() ? Qgis::FeatureRequestFlag::NoFlags : Qgis::FeatureRequestFlag::NoGeometry ).setSubsetOfAttributes( cache.sortCacheAttributes );
1057
1058 QgsFeatureIterator it = mLayerCache->getFeatures( request );
1059
1060 QgsFeature f;
1061 while ( it.nextFeature( f ) )
1062 {
1063 if ( cache.sortFieldIndex == -1 )
1064 {
1065 mExpressionContext.setFeature( f );
1066 const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1067 cache.sortCache.insert( f.id(), cacheValue );
1068 }
1069 else
1070 {
1071 const QVariant sortValue = widgetData.fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, f.attribute( cache.sortFieldIndex ) );
1072 cache.sortCache.insert( f.id(), sortValue );
1073 }
1074 }
1075}
1076
1077QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1078{
1079 QString expressionString;
1080
1081 if ( cacheIndex >= mSortCaches.size() )
1082 return expressionString;
1083
1084 const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1085
1086 if ( expression.isValid() )
1087 expressionString = expression.expression();
1088 else
1089 expressionString = QString();
1090
1091 return expressionString;
1092}
1093
1095{
1096 if ( ! mFeatureRequest.compare( request ) )
1097 {
1098 mFeatureRequest = request;
1099 if ( mLayer && !mLayer->isSpatial() )
1100 mFeatureRequest.setFlags( mFeatureRequest.flags() | Qgis::FeatureRequestFlag::NoGeometry );
1101 // Prefetch data for sorting, resetting all caches
1102 for ( unsigned long i = 0; i < mSortCaches.size(); ++i )
1103 {
1105 }
1106 }
1107}
1108
1110{
1111 return mFeatureRequest;
1112}
1113
1114const QgsAttributeTableModel::WidgetData &QgsAttributeTableModel::getWidgetData( int column ) const
1115{
1116 Q_ASSERT( column >= 0 && column < mAttributes.size() );
1117
1118 WidgetData &widgetData = mWidgetDatas[ column ];
1119 if ( !widgetData.loaded )
1120 {
1121 const int idx = fieldIdx( column );
1122 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, mFields[ idx ].name() );
1123 widgetData.fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
1124 widgetData.config = setup.config();
1125 widgetData.cache = widgetData.fieldFormatter->createCache( mLayer, idx, setup.config() );
1126 widgetData.loaded = true;
1127 }
1128
1129 return widgetData;
1130}
@ 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:94
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:93
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:6494
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6108
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
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