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