QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgsattributetablemodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 QgsAttributeTableModel.cpp
3 --------------------------------------
4 Date : Feb 2009
5 Copyright : (C) 2009 Vita Cizek
6 Email : weetya (at) gmail.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsapplication.h"
19
20#include "qgsactionmanager.h"
23#include "qgsexpression.h"
24#include "qgsfeatureiterator.h"
25#include "qgsconditionalstyle.h"
26#include "qgsfields.h"
27#include "qgsfieldformatter.h"
28#include "qgslogger.h"
29#include "qgsmapcanvas.h"
31#include "qgsrenderer.h"
32#include "qgsvectorlayer.h"
34#include "qgssymbollayerutils.h"
36#include "qgsgui.h"
40#include "qgsfieldmodel.h"
43#include "qgsstringutils.h"
44#include "qgsvectorlayerutils.h"
45#include "qgsvectorlayercache.h"
46
47#include <QVariant>
48#include <QUuid>
49
50#include <limits>
51
53 : QAbstractTableModel( parent )
54 , mLayer( layerCache->layer() )
55 , mLayerCache( layerCache )
56{
58
59 if ( mLayer->geometryType() == QgsWkbTypes::NullGeometry )
60 {
61 mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
62 }
63
64 mFeat.setId( std::numeric_limits<int>::min() );
65
66 if ( !mLayer->isSpatial() )
67 mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry );
68
69 loadAttributes();
70
71 connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted );
72 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted );
73 connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields );
74
75 connect( mLayer, &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted );
76 connect( mLayer, &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted );
77 connect( mLayer, &QgsVectorLayer::afterRollBack, this, [ = ]
78 {
79 mIsCleaningUpAfterRollback = true;
80 bulkEditCommandEnded();
81 mIsCleaningUpAfterRollback = false;
82 } );
83
84 connect( mLayer, &QgsVectorLayer::editCommandEnded, this, &QgsAttributeTableModel::editCommandEnded );
85 connect( mLayerCache, &QgsVectorLayerCache::attributeValueChanged, this, &QgsAttributeTableModel::attributeValueChanged );
86 connect( mLayerCache, &QgsVectorLayerCache::featureAdded, this, [ = ]( QgsFeatureId id ) { featureAdded( id ); } );
87 connect( mLayerCache, &QgsVectorLayerCache::cachedLayerDeleted, this, &QgsAttributeTableModel::layerDeleted );
88}
89
90bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const
91{
92 QgsDebugMsgLevel( QStringLiteral( "loading feature %1" ).arg( fid ), 3 );
93
94 if ( fid == std::numeric_limits<int>::min() )
95 {
96 return false;
97 }
98
99 return mLayerCache->featureAtId( fid, mFeat );
100}
101
103{
104 return mExtraColumns;
105}
106
108{
109 mExtraColumns = extraColumns;
110 loadAttributes();
111}
112
113void QgsAttributeTableModel::featuresDeleted( const QgsFeatureIds &fids )
114{
115 QList<int> rows;
116
117 const auto constFids = fids;
118 for ( const QgsFeatureId fid : constFids )
119 {
120 QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1, size: %3" ).arg( fid ).arg( mFeatureRequest.filterType() ).arg( mIdRowMap.size() ), 4 );
121
122 const int row = idToRow( fid );
123 if ( row != -1 )
124 rows << row;
125 }
126
127 std::sort( rows.begin(), rows.end() );
128
129 int lastRow = -1;
130 int beginRow = -1;
131 int currentRowCount = 0;
132 int removedRows = 0;
133 bool reset = false;
134
135 const auto constRows = rows;
136 for ( const int row : constRows )
137 {
138#if 0
139 qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows;
140#endif
141 if ( lastRow == -1 )
142 {
143 beginRow = row;
144 }
145
146 if ( row != lastRow + 1 && lastRow != -1 )
147 {
148 if ( rows.count() > 100 && currentRowCount < 10 )
149 {
150 reset = true;
151 break;
152 }
153 removeRows( beginRow - removedRows, currentRowCount );
154
155 beginRow = row;
156 removedRows += currentRowCount;
157 currentRowCount = 0;
158 }
159
160 currentRowCount++;
161
162 lastRow = row;
163 }
164
165 if ( !reset )
166 removeRows( beginRow - removedRows, currentRowCount );
167 else
168 resetModel();
169}
170
171bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent )
172{
173
174 if ( row < 0 || count < 1 )
175 return false;
176
177 if ( !mResettingModel )
178 {
179 beginRemoveRows( parent, row, row + count - 1 );
180 }
181
182#ifdef QGISDEBUG
183 if ( 3 <= QgsLogger::debugLevel() )
184 QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 );
185#endif
186
187 if ( mBulkEditCommandRunning && !mResettingModel )
188 {
189 for ( int i = row; i < row + count; i++ )
190 {
191 const QgsFeatureId fid { rowToId( row ) };
192 mInsertedRowsChanges.removeOne( fid );
193 }
194 }
195
196 // clean old references
197 for ( int i = row; i < row + count; i++ )
198 {
199 for ( SortCache &cache : mSortCaches )
200 cache.sortCache.remove( mRowIdMap[i] );
201 mIdRowMap.remove( mRowIdMap[i] );
202 mRowIdMap.remove( i );
203 }
204
205 // update maps
206 const int n = mRowIdMap.size() + count;
207 for ( int i = row + count; i < n; i++ )
208 {
209 const QgsFeatureId id = mRowIdMap[i];
210 mIdRowMap[id] -= count;
211 mRowIdMap[i - count] = id;
212 mRowIdMap.remove( i );
213 }
214
215#ifdef QGISDEBUG
216 if ( 4 <= QgsLogger::debugLevel() )
217 {
218 QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 );
219 QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 );
220 for ( QHash<QgsFeatureId, int>::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it )
221 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 );
222
223 QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 );
224 for ( QHash<int, QgsFeatureId>::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it )
225 QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 );
226 }
227#endif
228
229 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
230
231 if ( !mResettingModel )
232 endRemoveRows();
233
234 return true;
235}
236
237void QgsAttributeTableModel::featureAdded( QgsFeatureId fid )
238{
239 QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 );
240 bool featOk = true;
241
242 if ( mFeat.id() != fid )
243 featOk = loadFeatureAtId( fid );
244
245 if ( featOk && mFeatureRequest.acceptFeature( mFeat ) )
246 {
247 for ( SortCache &cache : mSortCaches )
248 {
249 if ( cache.sortFieldIndex >= 0 )
250 {
251 QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
252 const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
253 const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
254 const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, 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 mAttributeWidgetCaches.clear();
316 mAttributes.clear();
317 mWidgetFactories.clear();
318 mWidgetConfigs.clear();
319 mFieldFormatters.clear();
320}
321
322void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter )
323{
324 for ( int i = 0; i < mFieldFormatters.size(); ++i )
325 {
326 if ( mFieldFormatters.at( i ) == fieldFormatter )
328 }
329}
330
331void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
332{
333 // Defer all updates if a bulk edit/rollback command is running
334 if ( mBulkEditCommandRunning )
335 {
336 mAttributeValueChanges.insert( QPair<QgsFeatureId, int>( fid, idx ), value );
337 return;
338 }
339 QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( mFeatureRequest.filterType() ), 2 );
340
341 for ( SortCache &cache : mSortCaches )
342 {
343 if ( cache.sortCacheAttributes.contains( idx ) )
344 {
345 if ( cache.sortFieldIndex == -1 )
346 {
347 if ( loadFeatureAtId( fid ) )
348 {
349 mExpressionContext.setFeature( mFeat );
350 cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext );
351 }
352 }
353 else
354 {
355 QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
356 const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
357 const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
358 const QVariant sortValue = fieldFormatter->representValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, value );
359 cache.sortCache.insert( fid, sortValue );
360 }
361 }
362 }
363 // No filter request: skip all possibly heavy checks
364 if ( mFeatureRequest.filterType() == QgsFeatureRequest::FilterNone )
365 {
366 if ( loadFeatureAtId( fid ) )
367 {
368 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
369 setData( modelIndex, value, Qt::EditRole );
370 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
371 }
372 }
373 else
374 {
375 if ( loadFeatureAtId( fid ) )
376 {
377 if ( mFeatureRequest.acceptFeature( mFeat ) )
378 {
379 if ( !mIdRowMap.contains( fid ) )
380 {
381 // Feature changed in such a way, it will be shown now
382 featureAdded( fid );
383 }
384 else
385 {
386 // Update representation
387 const QModelIndex modelIndex = index( idToRow( fid ), fieldCol( idx ) );
388 setData( modelIndex, value, Qt::EditRole );
389 emit dataChanged( modelIndex, modelIndex, QVector<int>() << Qt::DisplayRole );
390 }
391 }
392 else
393 {
394 if ( mIdRowMap.contains( fid ) )
395 {
396 // Feature changed such, that it is no longer shown
397 featuresDeleted( QgsFeatureIds() << fid );
398 }
399 // else: we don't care
400 }
401 }
402 }
403}
404
405void QgsAttributeTableModel::loadAttributes()
406{
407 if ( !mLayer )
408 {
409 return;
410 }
411
412 bool ins = false, rm = false;
413
414 QgsAttributeList attributes;
415 const QgsFields &fields = mLayer->fields();
416
417 mWidgetFactories.clear();
418 mAttributeWidgetCaches.clear();
419 mWidgetConfigs.clear();
420 mFieldFormatters.clear();
421
422 for ( int idx = 0; idx < fields.count(); ++idx )
423 {
424 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fields[idx].name() );
427
428 mWidgetFactories.append( widgetFactory );
429 mWidgetConfigs.append( setup.config() );
430 mAttributeWidgetCaches.append( fieldFormatter->createCache( mLayer, idx, setup.config() ) );
431 mFieldFormatters.append( fieldFormatter );
432
433 attributes << idx;
434 }
435
436 if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
437 {
438 ins = true;
439 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
440 }
441 else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
442 {
443 rm = true;
444 beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
445 }
446
447 mFieldCount = attributes.size();
448 mAttributes = attributes;
449
450 for ( SortCache &cache : mSortCaches )
451 {
452 if ( cache.sortFieldIndex >= mAttributes.count() )
453 cache.sortFieldIndex = -1;
454 }
455
456 if ( ins )
457 {
458 endInsertColumns();
459 }
460 else if ( rm )
461 {
462 endRemoveColumns();
463 }
464}
465
467{
468 // make sure attributes are properly updated before caching the data
469 // (emit of progress() signal may enter event loop and thus attribute
470 // table view may be updated with inconsistent model which may assume
471 // wrong number of attributes)
472
473 loadAttributes();
474
475 mResettingModel = true;
476 beginResetModel();
477
478 if ( rowCount() != 0 )
479 {
480 removeRows( 0, rowCount() );
481 }
482
483 // Layer might have been deleted and cache set to nullptr!
484 if ( mLayerCache )
485 {
486 QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
487
488 int i = 0;
489
490 QElapsedTimer t;
491 t.start();
492
493 while ( features.nextFeature( mFeat ) )
494 {
495 ++i;
496
497 if ( t.elapsed() > 1000 )
498 {
499 bool cancel = false;
500 emit progress( i, cancel );
501 if ( cancel )
502 break;
503
504 t.restart();
505 }
506 featureAdded( mFeat.id() );
507 }
508
509 emit finished();
510 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
511 }
512
513 endResetModel();
514
515 mResettingModel = false;
516}
517
518
520{
521 if ( fieldName.isNull() )
522 {
523 mRowStylesMap.clear();
524 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
525 return;
526 }
527
528 const int fieldIndex = mLayer->fields().lookupField( fieldName );
529 if ( fieldIndex == -1 )
530 return;
531
532 //whole column has changed
533 const int col = fieldCol( fieldIndex );
534 emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
535}
536
538{
539 if ( a == b )
540 return;
541
542 const int rowA = idToRow( a );
543 const int rowB = idToRow( b );
544
545 //emit layoutAboutToBeChanged();
546
547 mRowIdMap.remove( rowA );
548 mRowIdMap.remove( rowB );
549 mRowIdMap.insert( rowA, b );
550 mRowIdMap.insert( rowB, a );
551
552 mIdRowMap.remove( a );
553 mIdRowMap.remove( b );
554 mIdRowMap.insert( a, rowB );
555 mIdRowMap.insert( b, rowA );
556 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
557
558
559 //emit layoutChanged();
560}
561
563{
564 if ( !mIdRowMap.contains( id ) )
565 {
566 QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
567 return -1;
568 }
569
570 return mIdRowMap[id];
571}
572
574{
575 return index( idToRow( id ), 0 );
576}
577
579{
580 QModelIndexList indexes;
581
582 const int row = idToRow( id );
583 const int columns = columnCount();
584 indexes.reserve( columns );
585 for ( int column = 0; column < columns; ++column )
586 {
587 indexes.append( index( row, column ) );
588 }
589
590 return indexes;
591}
592
594{
595 if ( !mRowIdMap.contains( row ) )
596 {
597 QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
598 // return negative infinite (to avoid collision with newly added features)
599 return std::numeric_limits<int>::min();
600 }
601
602 return mRowIdMap[row];
603}
604
606{
607 return mAttributes[col];
608}
609
611{
612 return mAttributes.indexOf( idx );
613}
614
615int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
616{
617 Q_UNUSED( parent )
618 return mRowIdMap.size();
619}
620
621int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
622{
623 Q_UNUSED( parent )
624 return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
625}
626
627QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
628{
629 if ( !mLayer )
630 return QVariant();
631
632 if ( role == Qt::DisplayRole )
633 {
634 if ( orientation == Qt::Vertical ) //row
635 {
636 return QVariant( section );
637 }
638 else if ( section >= 0 && section < mFieldCount )
639 {
640 const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
641 return QVariant( attributeName );
642 }
643 else
644 {
645 return tr( "extra column" );
646 }
647 }
648 else if ( role == Qt::ToolTipRole )
649 {
650 if ( orientation == Qt::Vertical )
651 {
652 // TODO show DisplayExpression
653 return tr( "Feature ID: %1" ).arg( rowToId( section ) );
654 }
655 else
656 {
657 const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
659 }
660 }
661 else
662 {
663 return QVariant();
664 }
665}
666
667QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
668{
669 if ( !index.isValid() || !mLayer ||
670 ( role != Qt::TextAlignmentRole
671 && role != Qt::DisplayRole
672 && role != Qt::ToolTipRole
673 && role != Qt::EditRole
674 && role != FeatureIdRole
675 && role != FieldIndexRole
676 && role != Qt::BackgroundRole
677 && role != Qt::ForegroundRole
678 && role != Qt::DecorationRole
679 && role != Qt::FontRole
680 && role < SortRole
681 )
682 )
683 return QVariant();
684
685 const QgsFeatureId rowId = rowToId( index.row() );
686
687 if ( role == FeatureIdRole )
688 return rowId;
689
690 if ( index.column() >= mFieldCount )
691 return QVariant();
692
693 const int fieldId = mAttributes.at( index.column() );
694
695 if ( role == FieldIndexRole )
696 return fieldId;
697
698 if ( role >= SortRole )
699 {
700 const unsigned long cacheIndex = role - SortRole;
701 if ( cacheIndex < mSortCaches.size() )
702 return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
703 else
704 return QVariant();
705 }
706
707 const QgsField field = mLayer->fields().at( fieldId );
708
709 if ( role == Qt::TextAlignmentRole )
710 {
711 return static_cast<Qt::Alignment::Int>( mFieldFormatters.at( index.column() )->alignmentFlag( mLayer, fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter );
712 }
713
714 if ( mFeat.id() != rowId || !mFeat.isValid() )
715 {
716 if ( !loadFeatureAtId( rowId ) )
717 return QVariant( "ERROR" );
718
719 if ( mFeat.id() != rowId )
720 return QVariant( "ERROR" );
721 }
722
723 QVariant val = mFeat.attribute( fieldId );
724
725 switch ( role )
726 {
727 case Qt::DisplayRole:
728 return mFieldFormatters.at( index.column() )->representValue( mLayer,
729 fieldId,
730 mWidgetConfigs.at( index.column() ),
731 mAttributeWidgetCaches.at( index.column() ),
732 val );
733 case Qt::ToolTipRole:
734 {
735 QString tooltip = mFieldFormatters.at( index.column() )->representValue( mLayer,
736 fieldId,
737 mWidgetConfigs.at( index.column() ),
738 mAttributeWidgetCaches.at( index.column() ),
739 val );
740 if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
741 {
742 tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
743 }
744 return tooltip;
745 }
746 case Qt::EditRole:
747 return val;
748
749 case Qt::BackgroundRole:
750 case Qt::ForegroundRole:
751 case Qt::DecorationRole:
752 case Qt::FontRole:
753 {
754 mExpressionContext.setFeature( mFeat );
755 QList<QgsConditionalStyle> styles;
756 if ( mRowStylesMap.contains( mFeat.id() ) )
757 {
758 styles = mRowStylesMap[mFeat.id()];
759 }
760 else
761 {
762 styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
763 mRowStylesMap.insert( mFeat.id(), styles );
764 }
765
767 styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
768 styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
769 styles.insert( 0, rowstyle );
771
772 if ( style.isValid() )
773 {
774 if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
775 return style.backgroundColor();
776 if ( role == Qt::ForegroundRole )
777 return style.textColor();
778 if ( role == Qt::DecorationRole )
779 return style.icon();
780 if ( role == Qt::FontRole )
781 return style.font();
782 }
783 else if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
784 {
785 if ( role == Qt::ForegroundRole )
786 {
787 return QColor( Qt::blue );
788 }
789 else if ( role == Qt::FontRole )
790 {
791 QFont font;
792 font.setUnderline( true );
793 return font;
794 }
795 }
796
797 return QVariant();
798 }
799 }
800
801 return QVariant();
802}
803
804bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
805{
806 Q_UNUSED( value )
807
808 if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
809 return false;
810
811 mRowStylesMap.remove( mFeat.id() );
812
813 if ( !mLayer->isModified() )
814 return false;
815
816 return true;
817}
818
819Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
820{
821 if ( !index.isValid() )
822 return Qt::ItemIsEnabled;
823
824 if ( index.column() >= mFieldCount || !mLayer )
825 return Qt::NoItemFlags;
826
827 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
828
829 const int fieldIndex = mAttributes[index.column()];
830 const QgsFeatureId fid = rowToId( index.row() );
831
832 if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
833 flags |= Qt::ItemIsEditable;
834
835 return flags;
836}
837
838void QgsAttributeTableModel::bulkEditCommandStarted()
839{
840 mBulkEditCommandRunning = true;
841 mAttributeValueChanges.clear();
842}
843
844void QgsAttributeTableModel::bulkEditCommandEnded()
845{
846 mBulkEditCommandRunning = false;
847 // Full model update if the changed rows are more than half the total rows
848 // or if their count is > layer cache size
849
850 const long long fullModelUpdateThreshold = std::min<long long >( mLayerCache->cacheSize(), std::ceil( rowCount() * 0.5 ) );
851 bool fullModelUpdate = false;
852
853 // try the cheaper check first
854 if ( mInsertedRowsChanges.size() > fullModelUpdateThreshold )
855 {
856 fullModelUpdate = true;
857 }
858 else
859 {
860 QSet< QgsFeatureId > changedRows;
861 changedRows.reserve( mAttributeValueChanges.size() );
862 // we need to count changed features, not the total of changed attributes (which may all apply to one feature)
863 for ( auto it = mAttributeValueChanges.constBegin(); it != mAttributeValueChanges.constEnd(); ++it )
864 {
865 changedRows.insert( it.key().first );
866 if ( changedRows.size() > fullModelUpdateThreshold )
867 {
868 fullModelUpdate = true;
869 break;
870 }
871 }
872 }
873
874 QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended modified rows over (%3), cache size is %1, starting %2 update." )
875 .arg( mLayerCache->cacheSize() )
876 .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
877 .arg( rowCount() ),
878 3 );
879
880 // Remove added rows on rollback
881 if ( mIsCleaningUpAfterRollback )
882 {
883 for ( const int fid : std::as_const( mInsertedRowsChanges ) )
884 {
885 const int row( idToRow( fid ) );
886 if ( row < 0 )
887 {
888 continue;
889 }
890 removeRow( row );
891 }
892 }
893
894 // Invalidates the whole model
895 if ( fullModelUpdate )
896 {
897 // Invalidates the cache (there is no API for doing this directly)
898 emit mLayer->dataChanged();
899 emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
900 }
901 else
902 {
903
904 int minRow = rowCount();
905 int minCol = columnCount();
906 int maxRow = 0;
907 int maxCol = 0;
908 const auto keys = mAttributeValueChanges.keys();
909 for ( const auto &key : keys )
910 {
911 attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
912 const int row( idToRow( key.first ) );
913 const int col( fieldCol( key.second ) );
914 minRow = std::min<int>( row, minRow );
915 minCol = std::min<int>( col, minCol );
916 maxRow = std::max<int>( row, maxRow );
917 maxCol = std::max<int>( col, maxCol );
918 }
919
920 emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
921 }
922 mAttributeValueChanges.clear();
923}
924
925void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
926{
927 mFeat.setId( std::numeric_limits<int>::min() );
928 emit dataChanged( index1, index2 );
929}
930
931
932void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
933{
934 const QgsFeature f = feature( idx );
935 mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
936}
937
938void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const
939{
940 const QgsFeature f = feature( idx );
941 action->triggerForFeature( mLayer, f );
942}
943
944QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
945{
946 QgsFeature f( mLayer->fields() );
947 f.initAttributes( mAttributes.size() );
948 f.setId( rowToId( idx.row() ) );
949 for ( int i = 0; i < mAttributes.size(); i++ )
950 {
951 f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
952 }
953
954 return f;
955}
956
958{
959 if ( column == -1 || column >= mAttributes.count() )
960 {
961 prefetchSortData( QString() );
962 }
963 else
964 {
965 prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
966 }
967}
968
969void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
970{
971 if ( cacheIndex >= mSortCaches.size() )
972 {
973 mSortCaches.resize( cacheIndex + 1 );
974 }
975 SortCache &cache = mSortCaches[cacheIndex];
976 cache.sortCache.clear();
977 cache.sortCacheAttributes.clear();
978 cache.sortFieldIndex = -1;
979 if ( !expressionString.isEmpty() )
980 cache.sortCacheExpression = QgsExpression( expressionString );
981 else
982 {
983 // no sorting
984 cache.sortCacheExpression = QgsExpression();
985 return;
986 }
987
988 QgsFieldFormatter *fieldFormatter = nullptr;
989 QVariant widgetCache;
990 QVariantMap widgetConfig;
991
992 if ( cache.sortCacheExpression.isField() )
993 {
994 const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
995 cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
996 }
997
998 if ( cache.sortFieldIndex == -1 )
999 {
1000 cache.sortCacheExpression.prepare( &mExpressionContext );
1001
1002 const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
1003
1004 for ( const QString &col : referencedColumns )
1005 {
1006 cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
1007 }
1008 }
1009 else
1010 {
1011 cache.sortCacheAttributes.append( cache.sortFieldIndex );
1012
1013 widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
1014 widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
1015 fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
1016 }
1017
1018 const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
1020 .setSubsetOfAttributes( cache.sortCacheAttributes );
1021 QgsFeatureIterator it = mLayerCache->getFeatures( request );
1022
1023 QgsFeature f;
1024 while ( it.nextFeature( f ) )
1025 {
1026 if ( cache.sortFieldIndex == -1 )
1027 {
1028 mExpressionContext.setFeature( f );
1029 const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1030 cache.sortCache.insert( f.id(), cacheValue );
1031 }
1032 else
1033 {
1034 const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) );
1035 cache.sortCache.insert( f.id(), sortValue );
1036 }
1037 }
1038}
1039
1040QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1041{
1042 QString expressionString;
1043
1044 if ( cacheIndex >= mSortCaches.size() )
1045 return expressionString;
1046
1047 const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1048
1049 if ( expression.isValid() )
1050 expressionString = expression.expression();
1051 else
1052 expressionString = QString();
1053
1054 return expressionString;
1055}
1056
1058{
1059 mFeatureRequest = request;
1060 if ( mLayer && !mLayer->isSpatial() )
1061 mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry );
1062}
1063
1065{
1066 return mFeatureRequest;
1067}
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 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.
void executeMapLayerAction(QgsMapLayerAction *action, const QModelIndex &idx) const
Execute a QgsMapLayerAction.
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.
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.
Every attribute editor widget needs a factory, which inherits this class.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
QgsEditorWidgetFactory * factory(const QString &widgetId)
Gets a factory for the given widget type id.
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.
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
QgsFeatureId id
Definition qgsfeature.h:64
void setId(QgsFeatureId id)
Sets the feature id for this feature.
bool isValid() const
Returns the validity of this feature.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
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 QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const
Create a cache for a given 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:51
QString name
Definition qgsfield.h:60
QString displayName() const
Returns the name to use when displaying this field.
Definition qgsfield.cpp:90
Container of fields for a vector layer.
Definition qgsfields.h:45
int count() const
Returns number of items.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition qgsgui.cpp:83
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition qgslogger.h:108
An action which can run on map layers The class can be used in two manners:
virtual 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.
bool isModified() const override
Returns true if the provider has been modified since the last commit.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
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.
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.
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:26
const QgsField & field
Definition qgsfield.h:476
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugMsg(str)
Definition qgslogger.h:38