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