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