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