QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
368 }
369 else
370 {
371 if ( loadFeatureAtId( fid ) )
372 {
373 if ( mFeatureRequest.acceptFeature( mFeat ) )
374 {
375 if ( !mIdRowMap.contains( fid ) )
376 {
377 // Feature changed in such a way, it will be shown now
378 featureAdded( fid );
379 }
380 else
381 {
382 // Update representation
383 setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole );
384 }
385 }
386 else
387 {
388 if ( mIdRowMap.contains( fid ) )
389 {
390 // Feature changed such, that it is no longer shown
391 featuresDeleted( QgsFeatureIds() << fid );
392 }
393 // else: we don't care
394 }
395 }
396 }
397}
398
399void QgsAttributeTableModel::loadAttributes()
400{
401 if ( !mLayer )
402 {
403 return;
404 }
405
406 bool ins = false, rm = false;
407
408 QgsAttributeList attributes;
409 const QgsFields &fields = mLayer->fields();
410
411 mWidgetFactories.clear();
412 mAttributeWidgetCaches.clear();
413 mWidgetConfigs.clear();
414 mFieldFormatters.clear();
415
416 for ( int idx = 0; idx < fields.count(); ++idx )
417 {
418 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fields[idx].name() );
421
422 mWidgetFactories.append( widgetFactory );
423 mWidgetConfigs.append( setup.config() );
424 mAttributeWidgetCaches.append( fieldFormatter->createCache( mLayer, idx, setup.config() ) );
425 mFieldFormatters.append( fieldFormatter );
426
427 attributes << idx;
428 }
429
430 if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns )
431 {
432 ins = true;
433 beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 );
434 }
435 else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns )
436 {
437 rm = true;
438 beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 );
439 }
440
441 mFieldCount = attributes.size();
442 mAttributes = attributes;
443
444 for ( SortCache &cache : mSortCaches )
445 {
446 if ( cache.sortFieldIndex >= mAttributes.count() )
447 cache.sortFieldIndex = -1;
448 }
449
450 if ( ins )
451 {
452 endInsertColumns();
453 }
454 else if ( rm )
455 {
456 endRemoveColumns();
457 }
458}
459
461{
462 // make sure attributes are properly updated before caching the data
463 // (emit of progress() signal may enter event loop and thus attribute
464 // table view may be updated with inconsistent model which may assume
465 // wrong number of attributes)
466
467 loadAttributes();
468
469 mResettingModel = true;
470 beginResetModel();
471
472 if ( rowCount() != 0 )
473 {
474 removeRows( 0, rowCount() );
475 }
476
477 // Layer might have been deleted and cache set to nullptr!
478 if ( mLayerCache )
479 {
480 QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest );
481
482 int i = 0;
483
484 QElapsedTimer t;
485 t.start();
486
487 while ( features.nextFeature( mFeat ) )
488 {
489 ++i;
490
491 if ( t.elapsed() > 1000 )
492 {
493 bool cancel = false;
494 emit progress( i, cancel );
495 if ( cancel )
496 break;
497
498 t.restart();
499 }
500 featureAdded( mFeat.id() );
501 }
502
503 emit finished();
504 connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection );
505 }
506
507 endResetModel();
508
509 mResettingModel = false;
510}
511
512
514{
515 if ( fieldName.isNull() )
516 {
517 mRowStylesMap.clear();
518 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
519 return;
520 }
521
522 const int fieldIndex = mLayer->fields().lookupField( fieldName );
523 if ( fieldIndex == -1 )
524 return;
525
526 //whole column has changed
527 const int col = fieldCol( fieldIndex );
528 emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) );
529}
530
532{
533 if ( a == b )
534 return;
535
536 const int rowA = idToRow( a );
537 const int rowB = idToRow( b );
538
539 //emit layoutAboutToBeChanged();
540
541 mRowIdMap.remove( rowA );
542 mRowIdMap.remove( rowB );
543 mRowIdMap.insert( rowA, b );
544 mRowIdMap.insert( rowB, a );
545
546 mIdRowMap.remove( a );
547 mIdRowMap.remove( b );
548 mIdRowMap.insert( a, rowB );
549 mIdRowMap.insert( b, rowA );
550 Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() );
551
552
553 //emit layoutChanged();
554}
555
557{
558 if ( !mIdRowMap.contains( id ) )
559 {
560 QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) );
561 return -1;
562 }
563
564 return mIdRowMap[id];
565}
566
568{
569 return index( idToRow( id ), 0 );
570}
571
573{
574 QModelIndexList indexes;
575
576 const int row = idToRow( id );
577 const int columns = columnCount();
578 indexes.reserve( columns );
579 for ( int column = 0; column < columns; ++column )
580 {
581 indexes.append( index( row, column ) );
582 }
583
584 return indexes;
585}
586
588{
589 if ( !mRowIdMap.contains( row ) )
590 {
591 QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) );
592 // return negative infinite (to avoid collision with newly added features)
593 return std::numeric_limits<int>::min();
594 }
595
596 return mRowIdMap[row];
597}
598
600{
601 return mAttributes[col];
602}
603
605{
606 return mAttributes.indexOf( idx );
607}
608
609int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const
610{
611 Q_UNUSED( parent )
612 return mRowIdMap.size();
613}
614
615int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const
616{
617 Q_UNUSED( parent )
618 return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid
619}
620
621QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
622{
623 if ( !mLayer )
624 return QVariant();
625
626 if ( role == Qt::DisplayRole )
627 {
628 if ( orientation == Qt::Vertical ) //row
629 {
630 return QVariant( section );
631 }
632 else if ( section >= 0 && section < mFieldCount )
633 {
634 const QString attributeName = mLayer->fields().at( mAttributes.at( section ) ).displayName();
635 return QVariant( attributeName );
636 }
637 else
638 {
639 return tr( "extra column" );
640 }
641 }
642 else if ( role == Qt::ToolTipRole )
643 {
644 if ( orientation == Qt::Vertical )
645 {
646 // TODO show DisplayExpression
647 return tr( "Feature ID: %1" ).arg( rowToId( section ) );
648 }
649 else
650 {
651 const QgsField field = mLayer->fields().at( mAttributes.at( section ) );
653 }
654 }
655 else
656 {
657 return QVariant();
658 }
659}
660
661QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
662{
663 if ( !index.isValid() || !mLayer ||
664 ( role != Qt::TextAlignmentRole
665 && role != Qt::DisplayRole
666 && role != Qt::ToolTipRole
667 && role != Qt::EditRole
668 && role != FeatureIdRole
669 && role != FieldIndexRole
670 && role != Qt::BackgroundRole
671 && role != Qt::ForegroundRole
672 && role != Qt::DecorationRole
673 && role != Qt::FontRole
674 && role < SortRole
675 )
676 )
677 return QVariant();
678
679 const QgsFeatureId rowId = rowToId( index.row() );
680
681 if ( role == FeatureIdRole )
682 return rowId;
683
684 if ( index.column() >= mFieldCount )
685 return QVariant();
686
687 const int fieldId = mAttributes.at( index.column() );
688
689 if ( role == FieldIndexRole )
690 return fieldId;
691
692 if ( role >= SortRole )
693 {
694 const unsigned long cacheIndex = role - SortRole;
695 if ( cacheIndex < mSortCaches.size() )
696 return mSortCaches.at( cacheIndex ).sortCache.value( rowId );
697 else
698 return QVariant();
699 }
700
701 const QgsField field = mLayer->fields().at( fieldId );
702
703 if ( role == Qt::TextAlignmentRole )
704 {
705 return static_cast<Qt::Alignment::Int>( mFieldFormatters.at( index.column() )->alignmentFlag( mLayer, fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter );
706 }
707
708 if ( mFeat.id() != rowId || !mFeat.isValid() )
709 {
710 if ( !loadFeatureAtId( rowId ) )
711 return QVariant( "ERROR" );
712
713 if ( mFeat.id() != rowId )
714 return QVariant( "ERROR" );
715 }
716
717 QVariant val = mFeat.attribute( fieldId );
718
719 switch ( role )
720 {
721 case Qt::DisplayRole:
722 return mFieldFormatters.at( index.column() )->representValue( mLayer,
723 fieldId,
724 mWidgetConfigs.at( index.column() ),
725 mAttributeWidgetCaches.at( index.column() ),
726 val );
727 case Qt::ToolTipRole:
728 {
729 QString tooltip = mFieldFormatters.at( index.column() )->representValue( mLayer,
730 fieldId,
731 mWidgetConfigs.at( index.column() ),
732 mAttributeWidgetCaches.at( index.column() ),
733 val );
734 if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
735 {
736 tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip );
737 }
738 return tooltip;
739 }
740 case Qt::EditRole:
741 return val;
742
743 case Qt::BackgroundRole:
744 case Qt::ForegroundRole:
745 case Qt::DecorationRole:
746 case Qt::FontRole:
747 {
748 mExpressionContext.setFeature( mFeat );
749 QList<QgsConditionalStyle> styles;
750 if ( mRowStylesMap.contains( mFeat.id() ) )
751 {
752 styles = mRowStylesMap[mFeat.id()];
753 }
754 else
755 {
756 styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
757 mRowStylesMap.insert( mFeat.id(), styles );
758 }
759
761 styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
762 styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
763 styles.insert( 0, rowstyle );
765
766 if ( style.isValid() )
767 {
768 if ( role == Qt::BackgroundRole && style.validBackgroundColor() )
769 return style.backgroundColor();
770 if ( role == Qt::ForegroundRole )
771 return style.textColor();
772 if ( role == Qt::DecorationRole )
773 return style.icon();
774 if ( role == Qt::FontRole )
775 return style.font();
776 }
777 else if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) )
778 {
779 if ( role == Qt::ForegroundRole )
780 {
781 return QColor( Qt::blue );
782 }
783 else if ( role == Qt::FontRole )
784 {
785 QFont font;
786 font.setUnderline( true );
787 return font;
788 }
789 }
790
791 return QVariant();
792 }
793 }
794
795 return QVariant();
796}
797
798bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
799{
800 Q_UNUSED( value )
801
802 if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !mLayer->isEditable() )
803 return false;
804
805 mRowStylesMap.remove( mFeat.id() );
806
807 if ( !mLayer->isModified() )
808 return false;
809
810 return true;
811}
812
813Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const
814{
815 if ( !index.isValid() )
816 return Qt::ItemIsEnabled;
817
818 if ( index.column() >= mFieldCount || !mLayer )
819 return Qt::NoItemFlags;
820
821 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
822
823 const int fieldIndex = mAttributes[index.column()];
824 const QgsFeatureId fid = rowToId( index.row() );
825
826 if ( QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, fid ) )
827 flags |= Qt::ItemIsEditable;
828
829 return flags;
830}
831
832void QgsAttributeTableModel::bulkEditCommandStarted()
833{
834 mBulkEditCommandRunning = true;
835 mAttributeValueChanges.clear();
836}
837
838void QgsAttributeTableModel::bulkEditCommandEnded()
839{
840 mBulkEditCommandRunning = false;
841 // Full model update if the changed rows are more than half the total rows
842 // or if their count is > layer cache size
843 const int changeCount( std::max( mAttributeValueChanges.count(), mInsertedRowsChanges.count() ) );
844 const bool fullModelUpdate = changeCount > mLayerCache->cacheSize() ||
845 changeCount > rowCount() * 0.5;
846
847 QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended with %1 modified rows over (%4), cache size is %2, starting %3 update." )
848 .arg( changeCount )
849 .arg( mLayerCache->cacheSize() )
850 .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) )
851 .arg( rowCount() ),
852 3 );
853
854 // Remove added rows on rollback
855 if ( mIsCleaningUpAfterRollback )
856 {
857 for ( const int fid : std::as_const( mInsertedRowsChanges ) )
858 {
859 const int row( idToRow( fid ) );
860 if ( row < 0 )
861 {
862 continue;
863 }
864 removeRow( row );
865 }
866 }
867
868 // Invalidates the whole model
869 if ( fullModelUpdate )
870 {
871 // Invalidates the cache (there is no API for doing this directly)
872 emit mLayer->dataChanged();
873 emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) );
874 }
875 else
876 {
877
878 int minRow = rowCount();
879 int minCol = columnCount();
880 int maxRow = 0;
881 int maxCol = 0;
882 const auto keys = mAttributeValueChanges.keys();
883 for ( const auto &key : keys )
884 {
885 attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) );
886 const int row( idToRow( key.first ) );
887 const int col( fieldCol( key.second ) );
888 minRow = std::min<int>( row, minRow );
889 minCol = std::min<int>( col, minCol );
890 maxRow = std::max<int>( row, maxRow );
891 maxCol = std::max<int>( col, maxCol );
892 }
893
894 emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) );
895 }
896 mAttributeValueChanges.clear();
897}
898
899void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 )
900{
901 mFeat.setId( std::numeric_limits<int>::min() );
902 emit dataChanged( index1, index2 );
903}
904
905
906void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const
907{
908 const QgsFeature f = feature( idx );
909 mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
910}
911
912void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const
913{
914 const QgsFeature f = feature( idx );
915 action->triggerForFeature( mLayer, f );
916}
917
918QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
919{
920 QgsFeature f( mLayer->fields() );
921 f.initAttributes( mAttributes.size() );
922 f.setId( rowToId( idx.row() ) );
923 for ( int i = 0; i < mAttributes.size(); i++ )
924 {
925 f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
926 }
927
928 return f;
929}
930
932{
933 if ( column == -1 || column >= mAttributes.count() )
934 {
935 prefetchSortData( QString() );
936 }
937 else
938 {
939 prefetchSortData( QgsExpression::quotedColumnRef( mLayer->fields().at( mAttributes.at( column ) ).name() ) );
940 }
941}
942
943void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex )
944{
945 if ( cacheIndex >= mSortCaches.size() )
946 {
947 mSortCaches.resize( cacheIndex + 1 );
948 }
949 SortCache &cache = mSortCaches[cacheIndex];
950 cache.sortCache.clear();
951 cache.sortCacheAttributes.clear();
952 cache.sortFieldIndex = -1;
953 if ( !expressionString.isEmpty() )
954 cache.sortCacheExpression = QgsExpression( expressionString );
955 else
956 {
957 // no sorting
958 cache.sortCacheExpression = QgsExpression();
959 return;
960 }
961
962 QgsFieldFormatter *fieldFormatter = nullptr;
963 QVariant widgetCache;
964 QVariantMap widgetConfig;
965
966 if ( cache.sortCacheExpression.isField() )
967 {
968 const QString fieldName = static_cast<const QgsExpressionNodeColumnRef *>( cache.sortCacheExpression.rootNode() )->name();
969 cache.sortFieldIndex = mLayer->fields().lookupField( fieldName );
970 }
971
972 if ( cache.sortFieldIndex == -1 )
973 {
974 cache.sortCacheExpression.prepare( &mExpressionContext );
975
976 const QSet<QString> &referencedColumns = cache.sortCacheExpression.referencedColumns();
977
978 for ( const QString &col : referencedColumns )
979 {
980 cache.sortCacheAttributes.append( mLayer->fields().lookupField( col ) );
981 }
982 }
983 else
984 {
985 cache.sortCacheAttributes.append( cache.sortFieldIndex );
986
987 widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex );
988 widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex );
989 fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex );
990 }
991
992 const QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest )
994 .setSubsetOfAttributes( cache.sortCacheAttributes );
995 QgsFeatureIterator it = mLayerCache->getFeatures( request );
996
997 QgsFeature f;
998 while ( it.nextFeature( f ) )
999 {
1000 if ( cache.sortFieldIndex == -1 )
1001 {
1002 mExpressionContext.setFeature( f );
1003 const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext );
1004 cache.sortCache.insert( f.id(), cacheValue );
1005 }
1006 else
1007 {
1008 const QVariant sortValue = fieldFormatter->sortValue( mLayer, cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) );
1009 cache.sortCache.insert( f.id(), sortValue );
1010 }
1011 }
1012}
1013
1014QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const
1015{
1016 QString expressionString;
1017
1018 if ( cacheIndex >= mSortCaches.size() )
1019 return expressionString;
1020
1021 const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression;
1022
1023 if ( expression.isValid() )
1024 expressionString = expression.expression();
1025 else
1026 expressionString = QString();
1027
1028 return expressionString;
1029}
1030
1032{
1033 mFeatureRequest = request;
1034 if ( mLayer && !mLayer->isSpatial() )
1035 mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry );
1036}
1037
1039{
1040 return mFeatureRequest;
1041}
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.
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
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.
Definition: qgsfields.cpp:133
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:349
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
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:463
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38