QGIS API Documentation 4.1.0-Master (31622b25bb0)
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 for ( int idx = 0; idx < fields.count(); ++idx )
438 {
439 attributes << idx;
440 }
441
442 if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
443 {
444 ins = true;
445 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
446 }
447 else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
448 {
449 rm = true;
450 beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
451 }
452
453 mFieldCount = attributes.size();
454 mAttributes = attributes;
455 mWidgetDatas.clear();
456 mWidgetDatas.resize( mFieldCount );
457
458 for ( SortCache &cache : mSortCaches )
459 {
460 if ( cache.sortFieldIndex >= mAttributes.count() )
461 cache.sortFieldIndex = -1;
462 }
463
464 if ( ins )
465 {
466 endInsertColumns();
467 }
468 else if ( rm )
469 {
470 endRemoveColumns();
471 }
472}
473
475{
476 // make sure attributes are properly updated before caching the data
477 // (emit of progress() signal may enter event loop and thus attribute
478 // table view may be updated with inconsistent model which may assume
479 // wrong number of attributes)
480
481 loadAttributes();
482
483 mResettingModel = true;
484 beginResetModel();
485
486 if ( rowCount() != 0 )
487 {
488 removeRows( 0, rowCount() );
489 }
490
491 // Layer might have been deleted and cache set to nullptr!
492 if ( mLayerCache )
493 {
494 QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
495
496 int i = 0;
497
498 QElapsedTimer t;
499 t.start();
500
501 while ( features.nextFeature( mFeat ) )
502 {
503 ++i;
504
505 if ( t.elapsed() > 1000 )
506 {
507 bool cancel = false;
508 emit progress( i, cancel );
509 if ( cancel )
510 break;
511
512 t.restart();
513 }
514 featureAdded( mFeat.id() );
515 }
516
517 emit finished();
518 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
519 }
520
521 endResetModel();
522
523 mResettingModel = false;
524}
525
526
528{
529 if ( fieldName.isNull() )
530 {
531 mRowStylesMap.clear();
532 mConstraintStylesMap.clear();
533 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
534 return;
535 }
536
537 const int fieldIndex = mLayer->fields().lookupField( fieldName );
538 if ( fieldIndex == -1 )
539 return;
540
541 //whole column has changed
542 const int col = fieldCol( fieldIndex );
543 emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
544}
545
547{
548 if ( a == b )
549 return;
550
551 const int rowA = idToRow( a );
552 const int rowB = idToRow( b );
553
554 //emit layoutAboutToBeChanged();
555
556 mRowIdMap.remove( rowA );
557 mRowIdMap.remove( rowB );
558 mRowIdMap.insert( rowA, b );
559 mRowIdMap.insert( rowB, a );
560
561 mIdRowMap.remove( a );
562 mIdRowMap.remove( b );
563 mIdRowMap.insert( a, rowB );
564 mIdRowMap.insert( b, rowA );
565 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
566
567
568 //emit layoutChanged();
569}
570
572{
573 if ( !mIdRowMap.contains( id ) )
574 {
575 QgsDebugError( u"idToRow: id %1 not in the map"_s.arg( id ) );
576 return -1;
577 }
578
579 return mIdRowMap[id];
580}
581
583{
584 return index( idToRow( id ), 0 );
585}
586
588{
589 QModelIndexList indexes;
590
591 const int row = idToRow( id );
592 const int columns = columnCount();
593 indexes.reserve( columns );
594 for ( int column = 0; column < columns; ++column )
595 {
596 indexes.append( index( row, column ) );
597 }
598
599 return indexes;
600}
601
603{
604 if ( !mRowIdMap.contains( row ) )
605 {
606 QgsDebugError( u"rowToId: row %1 not in the map"_s.arg( row ) );
607 // return negative infinite (to avoid collision with newly added features)
608 return std::numeric_limits<int>::min();
609 }
610
611 return mRowIdMap[row];
612}
613
615{
616 return mAttributes[col];
617}
618
620{
621 return mAttributes.indexOf( idx );
622}
623
624int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
625{
626 Q_UNUSED( parent )
627 return mRowIdMap.size();
628}
629
630int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
631{
632 Q_UNUSED( parent )
633 return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
634}
635
636QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
637{
638 if ( !mLayer )
639 return QVariant();
640
641 if ( role == Qt::DisplayRole )
642 {
643 if ( orientation == Qt::Vertical ) //row
644 {
645 return QVariant( section );
646 }
647 else if ( section >= 0 && section < mFieldCount )
648 {
649 const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
650 return QVariant( attributeName );
651 }
652 else
653 {
654 return tr( "extra column" );
655 }
656 }
657 else if ( role == Qt::ToolTipRole )
658 {
659 if ( orientation == Qt::Vertical )
660 {
661 // TODO show DisplayExpression
662 return tr( "Feature ID: %1" ).arg( rowToId( section ) );
663 }
664 else
665 {
666 const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
667 return QgsFieldModel::fieldToolTipExtended( field, mLayer );
668 }
669 }
670 else
671 {
672 return QVariant();
673 }
674}
675
676QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
677{
678 if ( !index.isValid()
679 || !mLayer
680 || ( 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 ) ) )
681 return QVariant();
682
683 const QgsFeatureId rowId = rowToId( index.row() );
684
685 if ( role == static_cast<int>( CustomRole::FeatureId ) )
686 return rowId;
687
688 if ( index.column() >= mFieldCount )
689 return QVariant();
690
691 const int fieldId = mAttributes.at( index.column() );
692
693 if ( role == static_cast<int>( CustomRole::FieldIndex ) )
694 return fieldId;
695
696 if ( role >= static_cast<int>( CustomRole::Sort ) )
697 {
698 const unsigned long cacheIndex = role - static_cast<int>( CustomRole::Sort );
699 if ( cacheIndex < mSortCaches.size() )
700 {
701 return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
702 }
703 else
704 {
705 return QVariant();
706 }
707 }
708
709 const QgsField field = mLayer->fields().at( fieldId );
710
711 if ( role == Qt::TextAlignmentRole )
712 {
713 const WidgetData &widgetData = getWidgetData( index.column() );
714 return static_cast<Qt::Alignment::Int>( widgetData.fieldFormatter->alignmentFlag( mLayer, fieldId, widgetData.config ) | Qt::AlignVCenter );
715 }
716
717 if ( mFeat.id() != rowId || !mFeat.isValid() || !mLayerCache->cacheSubsetOfAttributes().contains( fieldId ) )
718 {
719 if ( !loadFeatureAtId( rowId, fieldId ) )
720 return QVariant( "ERROR" );
721
722 if ( mFeat.id() != rowId )
723 return QVariant( "ERROR" );
724 }
725
726 QVariant val = mFeat.attribute( fieldId );
727
728 switch ( role )
729 {
730 case Qt::DisplayRole:
731 {
732 const WidgetData &widgetData = getWidgetData( index.column() );
733 QString s = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
734 // In table view, too long strings kill performance. Just truncate them
735 constexpr int MAX_STRING_LENGTH = 10 * 1000;
736 if ( static_cast<size_t>( s.size() ) > static_cast<size_t>( MAX_STRING_LENGTH ) )
737 {
738 s.resize( MAX_STRING_LENGTH );
739 s.append( tr( "... truncated ..." ) );
740 }
741 return s;
742 }
743 case Qt::ToolTipRole:
744 {
745 const WidgetData &widgetData = getWidgetData( index.column() );
746 QString tooltip = widgetData.fieldFormatter->representValue( mLayer, fieldId, widgetData.config, widgetData.cache, val );
747 if ( tooltip != val.toString() )
748 {
749 tooltip = tr( "%1 (%2)" ).arg( tooltip, val.toString() );
750 }
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() ) && mConstraintStylesMap[mFeat.id()].contains( fieldId ) )
782 {
783 constraintstyle = mConstraintStylesMap[mFeat.id()][fieldId];
784 }
785 else
786 {
787 QStringList errors;
789 {
790 constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthHard );
791 }
792 else
793 {
795 {
796 constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthSoft );
797 }
798 }
799 mConstraintStylesMap[mFeat.id()].insert( fieldId, constraintstyle );
800 }
801 }
802
803 styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
804 styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
805 styles.insert( 0, rowstyle );
806 styles.insert( 0, constraintstyle );
808
809 if ( style.isValid() )
810 {
811 if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
812 return style.backgroundColor();
813 if ( role == Qt::ForegroundRole && style.validTextColor() )
814 return style.textColor();
815 if ( role == Qt::DecorationRole )
816 return style.icon();
817 if ( role == Qt::FontRole )
818 return style.font();
819 }
820 else if ( val.userType() == QMetaType::Type::QString && QgsStringUtils::isUrl( val.toString() ) )
821 {
822 if ( role == Qt::ForegroundRole )
823 {
824 return QColor( Qt::blue );
825 }
826 else if ( role == Qt::FontRole )
827 {
828 QFont font;
829 font.setUnderline( true );
830 return font;
831 }
832 }
833
834 return QVariant();
835 }
836 }
837
838 return QVariant();
839}
840
841bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
842{
843 Q_UNUSED( value )
844
845 if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
846 return false;
847
848 mRowStylesMap.remove( mFeat.id() );
849 mConstraintStylesMap.remove( mFeat.id() );
850
851 if ( !mLayer->isModified() )
852 return false;
853
854 return true;
855}
856
857Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
858{
859 if ( !index.isValid() )
860 return Qt::ItemIsEnabled;
861
862 if ( index.column() >= mFieldCount || !mLayer )
863 return Qt::NoItemFlags;
864
865 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
866
867 const int fieldIndex = mAttributes[index.column()];
868 const QgsFeatureId fid = rowToId( index.row() );
869
870 if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
871 flags |= Qt::ItemIsEditable;
872
873 return flags;
874}
875
876void QgsAttributeTableModel::bulkEditCommandStarted()
877{
878 mBulkEditCommandRunning = true;
879 mAttributeValueChanges.clear();
880}
881
882void QgsAttributeTableModel::bulkEditCommandEnded()
883{
884 mBulkEditCommandRunning = false;
885 // Full model update if the changed rows are more than half the total rows
886 // or if their count is > layer cache size
887
888 const long long fullModelUpdateThreshold = std::min<long long>( mLayerCache->cacheSize(), std::ceil( rowCount() * 0.5 ) );
889 bool fullModelUpdate = false;
890
891 // try the cheaper check first
892 if ( mInsertedRowsChanges.size() > fullModelUpdateThreshold )
893 {
894 fullModelUpdate = true;
895 }
896 else
897 {
898 QSet<QgsFeatureId> changedRows;
899 changedRows.reserve( mAttributeValueChanges.size() );
900 // we need to count changed features, not the total of changed attributes (which may all apply to one feature)
901 for ( auto it = mAttributeValueChanges.constBegin(); it != mAttributeValueChanges.constEnd(); ++it )
902 {
903 changedRows.insert( it.key().first );
904 if ( changedRows.size() > fullModelUpdateThreshold )
905 {
906 fullModelUpdate = true;
907 break;
908 }
909 }
910 }
911
912 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 );
913
914 // Remove added rows on rollback
915 if ( mIsCleaningUpAfterRollback )
916 {
917 for ( const int fid : std::as_const( mInsertedRowsChanges ) )
918 {
919 const int row( idToRow( fid ) );
920 if ( row < 0 )
921 {
922 continue;
923 }
924 removeRow( row );
925 }
926 }
927
928 // Invalidates the whole model
929 if ( fullModelUpdate )
930 {
931 // Invalidates the cache (there is no API for doing this directly)
932 emit mLayer->dataChanged();
933 emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
934 }
935 else
936 {
937 int minRow = rowCount();
938 int minCol = columnCount();
939 int maxRow = 0;
940 int maxCol = 0;
941 const auto keys = mAttributeValueChanges.keys();
942 for ( const auto &key : keys )
943 {
944 attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
945 const int row( idToRow( key.first ) );
946 const int col( fieldCol( key.second ) );
947 minRow = std::min<int>( row, minRow );
948 minCol = std::min<int>( col, minCol );
949 maxRow = std::max<int>( row, maxRow );
950 maxCol = std::max<int>( col, maxCol );
951 }
952
953 emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
954 }
955 mAttributeValueChanges.clear();
956}
957
958void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
959{
960 mFeat.setId( std::numeric_limits<int>::min() );
961 emit dataChanged( index1, index2 );
962}
963
964
965void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
966{
967 const QgsFeature f = feature( idx );
968 mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
969}
970
971void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx, const QgsMapLayerActionContext &context ) const
972{
973 const QgsFeature f = feature( idx );
975 action->triggerForFeature( mLayer, f );
977 action->triggerForFeature( mLayer, f, context );
978}
979
980QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
981{
982 QgsFeature f( mLayer->fields() );
983 f.initAttributes( mAttributes.size() );
984 f.setId( rowToId( idx.row() ) );
985 for ( int i = 0; i < mAttributes.size(); i++ )
986 {
987 f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
988 }
989
990 return f;
991}
992
994{
995 if ( column == -1 || column >= mAttributes.count() )
996 {
997 prefetchSortData( QString() );
998 }
999 else
1000 {
1001 prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
1002 }
1003}
1004
1005void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
1006{
1007 if ( !mLayer )
1008 return;
1009
1010 if ( cacheIndex >= mSortCaches.size() )
1011 {
1012 mSortCaches.resize( cacheIndex + 1 );
1013 }
1014 SortCache &cache = mSortCaches[cacheIndex];
1015 cache.sortCache.clear();
1016 cache.sortCacheAttributes.clear();
1017 cache.sortFieldIndex = -1;
1018 if ( !expressionString.isEmpty() )
1019 cache.sortCacheExpression = QgsExpression( expressionString );
1020 else
1021 {
1022 // no sorting
1023 cache.sortCacheExpression = QgsExpression();
1024 return;
1025 }
1026
1027 WidgetData widgetData;
1028
1029 if ( cache.sortCacheExpression.isField() )
1030 {
1031 const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
1032 cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
1033 }
1034
1035 if ( cache.sortFieldIndex == -1 )
1036 {
1037 cache.sortCacheExpression.prepare( &mExpressionContext );
1038
1039 const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
1040
1041 for ( const QString &col : referencedColumns )
1042 {
1043 cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
1044 }
1045 }
1046 else
1047 {
1048 cache.sortCacheAttributes.append( cache.sortFieldIndex );
1049
1050 widgetData = getWidgetData( cache.sortFieldIndex );
1051 }
1052
1053 const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
1055 .setSubsetOfAttributes( cache.sortCacheAttributes );
1056
1057 QgsFeatureIterator it = mLayerCache->getFeatures( request );
1058
1059 QgsFeature f;
1060 while ( it.nextFeature( f ) )
1061 {
1062 if ( cache.sortFieldIndex == -1 )
1063 {
1064 mExpressionContext.setFeature( f );
1065 const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1066 cache.sortCache.insert( f.id(), cacheValue );
1067 }
1068 else
1069 {
1070 const QVariant sortValue = widgetData.fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetData.config, widgetData.cache, f.attribute( cache.sortFieldIndex ) );
1071 cache.sortCache.insert( f.id(), sortValue );
1072 }
1073 }
1074}
1075
1076QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1077{
1078 QString expressionString;
1079
1080 if ( cacheIndex >= mSortCaches.size() )
1081 return expressionString;
1082
1083 const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1084
1085 if ( expression.isValid() )
1086 expressionString = expression.expression();
1087 else
1088 expressionString = QString();
1089
1090 return expressionString;
1091}
1092
1094{
1095 if ( !mFeatureRequest.compare( request ) )
1096 {
1097 mFeatureRequest = request;
1098 if ( mLayer && !mLayer->isSpatial() )
1099 mFeatureRequest.setFlags( mFeatureRequest.flags() | Qgis::FeatureRequestFlag::NoGeometry );
1100 // Prefetch data for sorting, resetting all caches
1101 for ( unsigned long i = 0; i < mSortCaches.size(); ++i )
1102 {
1104 }
1105 }
1106}
1107
1109{
1110 return mFeatureRequest;
1111}
1112
1113const QgsAttributeTableModel::WidgetData &QgsAttributeTableModel::getWidgetData( int column ) const
1114{
1115 Q_ASSERT( column >= 0 && column < mAttributes.size() );
1116
1117 WidgetData &widgetData = mWidgetDatas[column];
1118 if ( !widgetData.loaded )
1119 {
1120 const int idx = fieldIdx( column );
1121 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, mFields[idx].name() );
1122 widgetData.fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
1123 widgetData.config = setup.config();
1124 widgetData.cache = widgetData.fieldFormatter->createCache( mLayer, idx, setup.config() );
1125 widgetData.loaded = true;
1126 }
1127
1128 return widgetData;
1129}
@ NoFilter
No filter is applied.
Definition qgis.h:2357
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2329
@ NoFlags
No flags are set.
Definition qgis.h:2328
@ Null
No geometry.
Definition qgis.h:384
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 validTextColor() const
Check if the text color is valid for render.
bool isValid() const
isValid Check if this rule is valid.
QPixmap icon() const
The icon set for style generated from the set symbol.
bool validBackgroundColor() const
Check if the background color is valid for render.
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:63
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, const QString &predefinedComment=QString())
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
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: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, 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:7766
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7404
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7765
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