/*************************************************************************** QgsAttributeTableModel.cpp -------------------------------------- Date : Feb 2009 Copyright : (C) 2009 Vita Cizek Email : weetya (at) gmail.com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsapplication.h" #include "qgsattributetablemodel.h" #include "qgsattributetablefiltermodel.h" #include "qgsactionmanager.h" #include "qgseditorwidgetregistry.h" #include "qgseditorwidgetfactory.h" #include "qgsexpression.h" #include "qgsfeatureiterator.h" #include "qgsconditionalstyle.h" #include "qgsfields.h" #include "qgsfieldformatter.h" #include "qgslogger.h" #include "qgsmapcanvas.h" #include "qgsmaplayeractionregistry.h" #include "qgsrenderer.h" #include "qgsvectorlayer.h" #include "qgsvectordataprovider.h" #include "qgssymbollayerutils.h" #include "qgsfieldformatterregistry.h" #include "qgsgui.h" #include "qgsexpressionnodeimpl.h" #include "qgsvectorlayerjoininfo.h" #include "qgsvectorlayerjoinbuffer.h" #include "qgsfieldmodel.h" #include "qgstexteditwidgetfactory.h" #include "qgsexpressioncontextutils.h" #include "qgsvectorlayerutils.h" #include #include QgsAttributeTableModel::QgsAttributeTableModel( QgsVectorLayerCache *layerCache, QObject *parent ) : QAbstractTableModel( parent ) , mLayerCache( layerCache ) { mExpressionContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layerCache->layer() ) ); if ( layerCache->layer()->geometryType() == QgsWkbTypes::NullGeometry ) { mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry ); } mFeat.setId( std::numeric_limits::min() ); if ( !layer()->isSpatial() ) mFeatureRequest.setFlags( QgsFeatureRequest::NoGeometry ); loadAttributes(); connect( layer(), &QgsVectorLayer::featuresDeleted, this, &QgsAttributeTableModel::featuresDeleted ); connect( layer(), &QgsVectorLayer::attributeDeleted, this, &QgsAttributeTableModel::attributeDeleted ); connect( layer(), &QgsVectorLayer::updatedFields, this, &QgsAttributeTableModel::updatedFields ); connect( layer(), &QgsVectorLayer::editCommandStarted, this, &QgsAttributeTableModel::bulkEditCommandStarted ); connect( layer(), &QgsVectorLayer::beforeRollBack, this, &QgsAttributeTableModel::bulkEditCommandStarted ); connect( layer(), &QgsVectorLayer::afterRollBack, this, &QgsAttributeTableModel::bulkEditCommandEnded ); connect( layer(), &QgsVectorLayer::editCommandEnded, this, &QgsAttributeTableModel::editCommandEnded ); connect( mLayerCache, &QgsVectorLayerCache::attributeValueChanged, this, &QgsAttributeTableModel::attributeValueChanged ); connect( mLayerCache, &QgsVectorLayerCache::featureAdded, this, [ = ]( QgsFeatureId id ) { featureAdded( id ); } ); connect( mLayerCache, &QgsVectorLayerCache::cachedLayerDeleted, this, &QgsAttributeTableModel::layerDeleted ); } bool QgsAttributeTableModel::loadFeatureAtId( QgsFeatureId fid ) const { QgsDebugMsgLevel( QStringLiteral( "loading feature %1" ).arg( fid ), 3 ); if ( fid == std::numeric_limits::min() ) { return false; } return mLayerCache->featureAtId( fid, mFeat ); } int QgsAttributeTableModel::extraColumns() const { return mExtraColumns; } void QgsAttributeTableModel::setExtraColumns( int extraColumns ) { mExtraColumns = extraColumns; loadAttributes(); } void QgsAttributeTableModel::featuresDeleted( const QgsFeatureIds &fids ) { QList rows; const auto constFids = fids; for ( QgsFeatureId fid : constFids ) { QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1, size: %3" ).arg( fid ).arg( mFeatureRequest.filterType() ).arg( mIdRowMap.size() ), 4 ); int row = idToRow( fid ); if ( row != -1 ) rows << row; } std::sort( rows.begin(), rows.end() ); int lastRow = -1; int beginRow = -1; int currentRowCount = 0; int removedRows = 0; bool reset = false; const auto constRows = rows; for ( int row : constRows ) { #if 0 qDebug() << "Row: " << row << ", begin " << beginRow << ", last " << lastRow << ", current " << currentRowCount << ", removed " << removedRows; #endif if ( lastRow == -1 ) { beginRow = row; } if ( row != lastRow + 1 && lastRow != -1 ) { if ( rows.count() > 100 && currentRowCount < 10 ) { reset = true; break; } removeRows( beginRow - removedRows, currentRowCount ); beginRow = row; removedRows += currentRowCount; currentRowCount = 0; } currentRowCount++; lastRow = row; } if ( !reset ) removeRows( beginRow - removedRows, currentRowCount ); else resetModel(); } bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex &parent ) { if ( row < 0 || count < 1 ) return false; beginRemoveRows( parent, row, row + count - 1 ); #ifdef QGISDEBUG if ( 3 <= QgsLogger::debugLevel() ) QgsDebugMsgLevel( QStringLiteral( "remove %2 rows at %1 (rows %3, ids %4)" ).arg( row ).arg( count ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 ); #endif // clean old references for ( int i = row; i < row + count; i++ ) { for ( SortCache &cache : mSortCaches ) cache.sortCache.remove( mRowIdMap[i] ); mIdRowMap.remove( mRowIdMap[i] ); mRowIdMap.remove( i ); } // update maps int n = mRowIdMap.size() + count; for ( int i = row + count; i < n; i++ ) { QgsFeatureId id = mRowIdMap[i]; mIdRowMap[id] -= count; mRowIdMap[i - count] = id; mRowIdMap.remove( i ); } #ifdef QGISDEBUG if ( 4 <= QgsLogger::debugLevel() ) { QgsDebugMsgLevel( QStringLiteral( "after removal rows %1, ids %2" ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 ); QgsDebugMsgLevel( QStringLiteral( "id->row" ), 4 ); for ( QHash::const_iterator it = mIdRowMap.constBegin(); it != mIdRowMap.constEnd(); ++it ) QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( FID_TO_STRING( it.key() ) ).arg( *it ), 4 ); QgsDebugMsgLevel( QStringLiteral( "row->id" ), 4 ); for ( QHash::const_iterator it = mRowIdMap.constBegin(); it != mRowIdMap.constEnd(); ++it ) QgsDebugMsgLevel( QStringLiteral( "%1->%2" ).arg( it.key() ).arg( FID_TO_STRING( *it ) ), 4 ); } #endif Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() ); endRemoveRows(); return true; } void QgsAttributeTableModel::featureAdded( QgsFeatureId fid, bool resettingModel ) { QgsDebugMsgLevel( QStringLiteral( "(%2) fid: %1" ).arg( fid ).arg( mFeatureRequest.filterType() ), 4 ); bool featOk = true; if ( mFeat.id() != fid ) featOk = loadFeatureAtId( fid ); if ( featOk && mFeatureRequest.acceptFeature( mFeat ) ) { for ( SortCache &cache : mSortCaches ) { if ( cache.sortFieldIndex >= 0 ) { QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex ); const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex ); const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex ); QVariant sortValue = fieldFormatter->representValue( layer(), cache.sortFieldIndex, widgetConfig, widgetCache, mFeat.attribute( cache.sortFieldIndex ) ); cache.sortCache.insert( mFeat.id(), sortValue ); } else if ( cache.sortCacheExpression.isValid() ) { mExpressionContext.setFeature( mFeat ); cache.sortCache[mFeat.id()] = cache.sortCacheExpression.evaluate( &mExpressionContext ); } } // Skip if the fid is already in the map (do not add twice)! if ( ! mIdRowMap.contains( fid ) ) { int n = mRowIdMap.size(); if ( !resettingModel ) beginInsertRows( QModelIndex(), n, n ); mIdRowMap.insert( fid, n ); mRowIdMap.insert( n, fid ); if ( !resettingModel ) endInsertRows(); reload( index( rowCount() - 1, 0 ), index( rowCount() - 1, columnCount() ) ); } } } void QgsAttributeTableModel::updatedFields() { loadAttributes(); emit modelChanged(); } void QgsAttributeTableModel::editCommandEnded() { // do not do reload(...) due would trigger (dataChanged) row sort // giving issue: https://github.com/qgis/QGIS/issues/23892 bulkEditCommandEnded( ); } void QgsAttributeTableModel::attributeDeleted( int idx ) { int cacheIndex = 0; for ( const SortCache &cache : mSortCaches ) { if ( cache.sortCacheAttributes.contains( idx ) ) { prefetchSortData( QString(), cacheIndex ); } cacheIndex++; } } void QgsAttributeTableModel::layerDeleted() { mLayerCache = nullptr; removeRows( 0, rowCount() ); mAttributeWidgetCaches.clear(); mAttributes.clear(); mWidgetFactories.clear(); mWidgetConfigs.clear(); mFieldFormatters.clear(); } void QgsAttributeTableModel::fieldFormatterRemoved( QgsFieldFormatter *fieldFormatter ) { for ( int i = 0; i < mFieldFormatters.size(); ++i ) { if ( mFieldFormatters.at( i ) == fieldFormatter ) mFieldFormatters[i] = QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter(); } } void QgsAttributeTableModel::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value ) { // Defer all updates if a bulk edit/rollback command is running if ( mBulkEditCommandRunning ) { mAttributeValueChanges.insert( QPair( fid, idx ), value ); return; } QgsDebugMsgLevel( QStringLiteral( "(%4) fid: %1, idx: %2, value: %3" ).arg( fid ).arg( idx ).arg( value.toString() ).arg( mFeatureRequest.filterType() ), 2 ); for ( SortCache &cache : mSortCaches ) { if ( cache.sortCacheAttributes.contains( idx ) ) { if ( cache.sortFieldIndex == -1 ) { if ( loadFeatureAtId( fid ) ) { mExpressionContext.setFeature( mFeat ); cache.sortCache[fid] = cache.sortCacheExpression.evaluate( &mExpressionContext ); } } else { QgsFieldFormatter *fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex ); const QVariant &widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex ); const QVariantMap &widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex ); QVariant sortValue = fieldFormatter->representValue( layer(), cache.sortFieldIndex, widgetConfig, widgetCache, value ); cache.sortCache.insert( fid, sortValue ); } } } // No filter request: skip all possibly heavy checks if ( mFeatureRequest.filterType() == QgsFeatureRequest::FilterNone ) { if ( loadFeatureAtId( fid ) ) setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole ); } else { if ( loadFeatureAtId( fid ) ) { if ( mFeatureRequest.acceptFeature( mFeat ) ) { if ( !mIdRowMap.contains( fid ) ) { // Feature changed in such a way, it will be shown now featureAdded( fid ); } else { // Update representation setData( index( idToRow( fid ), fieldCol( idx ) ), value, Qt::EditRole ); } } else { if ( mIdRowMap.contains( fid ) ) { // Feature changed such, that it is no longer shown featuresDeleted( QgsFeatureIds() << fid ); } // else: we don't care } } } } void QgsAttributeTableModel::loadAttributes() { if ( !layer() ) { return; } bool ins = false, rm = false; QgsAttributeList attributes; const QgsFields &fields = layer()->fields(); mWidgetFactories.clear(); mAttributeWidgetCaches.clear(); mWidgetConfigs.clear(); for ( int idx = 0; idx < fields.count(); ++idx ) { const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( layer(), fields[idx].name() ); QgsEditorWidgetFactory *widgetFactory = QgsGui::editorWidgetRegistry()->factory( setup.type() ); QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() ); mWidgetFactories.append( widgetFactory ); mWidgetConfigs.append( setup.config() ); mAttributeWidgetCaches.append( fieldFormatter->createCache( layer(), idx, setup.config() ) ); mFieldFormatters.append( fieldFormatter ); attributes << idx; } if ( mFieldCount + mExtraColumns < attributes.size() + mExtraColumns ) { ins = true; beginInsertColumns( QModelIndex(), mFieldCount + mExtraColumns, attributes.size() - 1 ); } else if ( attributes.size() + mExtraColumns < mFieldCount + mExtraColumns ) { rm = true; beginRemoveColumns( QModelIndex(), attributes.size(), mFieldCount + mExtraColumns - 1 ); } mFieldCount = attributes.size(); mAttributes = attributes; for ( SortCache &cache : mSortCaches ) { if ( cache.sortFieldIndex >= mAttributes.count() ) cache.sortFieldIndex = -1; } if ( ins ) { endInsertColumns(); } else if ( rm ) { endRemoveColumns(); } } void QgsAttributeTableModel::loadLayer() { // make sure attributes are properly updated before caching the data // (emit of progress() signal may enter event loop and thus attribute // table view may be updated with inconsistent model which may assume // wrong number of attributes) loadAttributes(); beginResetModel(); if ( rowCount() != 0 ) { removeRows( 0, rowCount() ); } // Layer might have been deleted and cache set to nullptr! if ( mLayerCache ) { QgsFeatureIterator features = mLayerCache->getFeatures( mFeatureRequest ); int i = 0; QTime t; t.start(); while ( features.nextFeature( mFeat ) ) { ++i; if ( t.elapsed() > 1000 ) { bool cancel = false; emit progress( i, cancel ); if ( cancel ) break; t.restart(); } featureAdded( mFeat.id(), true ); } emit finished(); connect( mLayerCache, &QgsVectorLayerCache::invalidated, this, &QgsAttributeTableModel::loadLayer, Qt::UniqueConnection ); } endResetModel(); } void QgsAttributeTableModel::fieldConditionalStyleChanged( const QString &fieldName ) { if ( fieldName.isNull() ) { mRowStylesMap.clear(); emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) ); return; } int fieldIndex = mLayerCache->layer()->fields().lookupField( fieldName ); if ( fieldIndex == -1 ) return; //whole column has changed int col = fieldCol( fieldIndex ); emit dataChanged( index( 0, col ), index( rowCount() - 1, col ) ); } void QgsAttributeTableModel::swapRows( QgsFeatureId a, QgsFeatureId b ) { if ( a == b ) return; int rowA = idToRow( a ); int rowB = idToRow( b ); //emit layoutAboutToBeChanged(); mRowIdMap.remove( rowA ); mRowIdMap.remove( rowB ); mRowIdMap.insert( rowA, b ); mRowIdMap.insert( rowB, a ); mIdRowMap.remove( a ); mIdRowMap.remove( b ); mIdRowMap.insert( a, rowB ); mIdRowMap.insert( b, rowA ); Q_ASSERT( mRowIdMap.size() == mIdRowMap.size() ); //emit layoutChanged(); } int QgsAttributeTableModel::idToRow( QgsFeatureId id ) const { if ( !mIdRowMap.contains( id ) ) { QgsDebugMsg( QStringLiteral( "idToRow: id %1 not in the map" ).arg( id ) ); return -1; } return mIdRowMap[id]; } QModelIndex QgsAttributeTableModel::idToIndex( QgsFeatureId id ) const { return index( idToRow( id ), 0 ); } QModelIndexList QgsAttributeTableModel::idToIndexList( QgsFeatureId id ) const { QModelIndexList indexes; int row = idToRow( id ); int columns = columnCount(); indexes.reserve( columns ); for ( int column = 0; column < columns; ++column ) { indexes.append( index( row, column ) ); } return indexes; } QgsFeatureId QgsAttributeTableModel::rowToId( const int row ) const { if ( !mRowIdMap.contains( row ) ) { QgsDebugMsg( QStringLiteral( "rowToId: row %1 not in the map" ).arg( row ) ); // return negative infinite (to avoid collision with newly added features) return std::numeric_limits::min(); } return mRowIdMap[row]; } int QgsAttributeTableModel::fieldIdx( int col ) const { return mAttributes[col]; } int QgsAttributeTableModel::fieldCol( int idx ) const { return mAttributes.indexOf( idx ); } int QgsAttributeTableModel::rowCount( const QModelIndex &parent ) const { Q_UNUSED( parent ) return mRowIdMap.size(); } int QgsAttributeTableModel::columnCount( const QModelIndex &parent ) const { Q_UNUSED( parent ) return std::max( 1, mFieldCount + mExtraColumns ); // if there are zero columns all model indices will be considered invalid } QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( !layer() ) return QVariant(); if ( role == Qt::DisplayRole ) { if ( orientation == Qt::Vertical ) //row { return QVariant( section ); } else if ( section >= 0 && section < mFieldCount ) { QString attributeName = layer()->fields().at( mAttributes.at( section ) ).displayName(); return QVariant( attributeName ); } else { return tr( "extra column" ); } } else if ( role == Qt::ToolTipRole ) { if ( orientation == Qt::Vertical ) { // TODO show DisplayExpression return tr( "Feature ID: %1" ).arg( rowToId( section ) ); } else { const QgsField field = layer()->fields().at( mAttributes.at( section ) ); return QgsFieldModel::fieldToolTip( field ); } } else { return QVariant(); } } QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const { if ( !index.isValid() || !layer() || ( role != Qt::TextAlignmentRole && role != Qt::DisplayRole && role != Qt::ToolTipRole && role != Qt::EditRole && role != FeatureIdRole && role != FieldIndexRole && role != Qt::BackgroundColorRole && role != Qt::TextColorRole && role != Qt::DecorationRole && role != Qt::FontRole && role < SortRole ) ) return QVariant(); QgsFeatureId rowId = rowToId( index.row() ); if ( role == FeatureIdRole ) return rowId; if ( index.column() >= mFieldCount ) return QVariant(); int fieldId = mAttributes.at( index.column() ); if ( role == FieldIndexRole ) return fieldId; if ( role >= SortRole ) { unsigned long cacheIndex = role - SortRole; if ( cacheIndex < mSortCaches.size() ) return mSortCaches.at( cacheIndex ).sortCache.value( rowId ); else return QVariant(); } QgsField field = layer()->fields().at( fieldId ); if ( role == Qt::TextAlignmentRole ) { return QVariant( mFieldFormatters.at( index.column() )->alignmentFlag( layer(), fieldId, mWidgetConfigs.at( index.column() ) ) | Qt::AlignVCenter ); } if ( mFeat.id() != rowId || !mFeat.isValid() ) { if ( !loadFeatureAtId( rowId ) ) return QVariant( "ERROR" ); if ( mFeat.id() != rowId ) return QVariant( "ERROR" ); } QVariant val = mFeat.attribute( fieldId ); switch ( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: return mFieldFormatters.at( index.column() )->representValue( layer(), fieldId, mWidgetConfigs.at( index.column() ), mAttributeWidgetCaches.at( index.column() ), val ); case Qt::EditRole: return val; case Qt::BackgroundRole: case Qt::TextColorRole: case Qt::DecorationRole: case Qt::FontRole: { mExpressionContext.setFeature( mFeat ); QList styles; if ( mRowStylesMap.contains( index.row() ) ) { styles = mRowStylesMap[index.row()]; } else { styles = QgsConditionalStyle::matchingConditionalStyles( layer()->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext ); mRowStylesMap.insert( index.row(), styles ); } QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles ); styles = layer()->conditionalStyles()->fieldStyles( field.name() ); styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext ); styles.insert( 0, rowstyle ); QgsConditionalStyle style = QgsConditionalStyle::compressStyles( styles ); if ( style.isValid() ) { if ( role == Qt::BackgroundRole && style.validBackgroundColor() ) return style.backgroundColor(); if ( role == Qt::TextColorRole && style.validTextColor() ) return style.textColor(); if ( role == Qt::DecorationRole ) return style.icon(); if ( role == Qt::FontRole ) return style.font(); } return QVariant(); } } return QVariant(); } bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_UNUSED( value ) if ( !index.isValid() || index.column() >= mFieldCount || role != Qt::EditRole || !layer()->isEditable() ) return false; if ( !layer()->isModified() ) return false; mRowStylesMap.remove( index.row() ); return true; } Qt::ItemFlags QgsAttributeTableModel::flags( const QModelIndex &index ) const { if ( !index.isValid() ) return Qt::ItemIsEnabled; if ( index.column() >= mFieldCount || !layer() ) return Qt::NoItemFlags; Qt::ItemFlags flags = QAbstractTableModel::flags( index ); const int fieldIndex = mAttributes[index.column()]; const QgsFeatureId fid = rowToId( index.row() ); if ( QgsVectorLayerUtils::fieldIsEditable( layer(), fieldIndex, fid ) ) flags |= Qt::ItemIsEditable; return flags; } void QgsAttributeTableModel::bulkEditCommandStarted() { mBulkEditCommandRunning = true; mAttributeValueChanges.clear(); } void QgsAttributeTableModel::bulkEditCommandEnded() { mBulkEditCommandRunning = false; // Full model update if the changed rows are more than half the total rows // or if their count is > layer cache size int changeCount( mAttributeValueChanges.count() ); bool fullModelUpdate = changeCount > mLayerCache->cacheSize() || changeCount > rowCount() * 0.5; QgsDebugMsgLevel( QStringLiteral( "Bulk edit command ended with %1 modified rows over (%4), cache size is %2, starting %3 update." ) .arg( changeCount ) .arg( mLayerCache->cacheSize() ) .arg( fullModelUpdate ? QStringLiteral( "full" ) : QStringLiteral( "incremental" ) ) .arg( rowCount() ), 3 ); // Invalidates the whole model if ( fullModelUpdate ) { // Invalidates the cache (there is no API for doing this directly) emit mLayerCache->layer()->dataChanged(); emit dataChanged( createIndex( 0, 0 ), createIndex( rowCount() - 1, columnCount() - 1 ) ); } else { int minRow = rowCount(); int minCol = columnCount(); int maxRow = 0; int maxCol = 0; const auto keys = mAttributeValueChanges.keys(); for ( const auto &key : keys ) { attributeValueChanged( key.first, key.second, mAttributeValueChanges.value( key ) ); int row( idToRow( key.first ) ); int col( fieldCol( key.second ) ); minRow = std::min( row, minRow ); minCol = std::min( col, minCol ); maxRow = std::max( row, maxRow ); maxCol = std::max( col, maxCol ); } emit dataChanged( createIndex( minRow, minCol ), createIndex( maxRow, maxCol ) ); } mAttributeValueChanges.clear(); } void QgsAttributeTableModel::reload( const QModelIndex &index1, const QModelIndex &index2 ) { mFeat.setId( std::numeric_limits::min() ); emit dataChanged( index1, index2 ); } void QgsAttributeTableModel::executeAction( QUuid action, const QModelIndex &idx ) const { QgsFeature f = feature( idx ); layer()->actions()->doAction( action, f, fieldIdx( idx.column() ) ); } void QgsAttributeTableModel::executeMapLayerAction( QgsMapLayerAction *action, const QModelIndex &idx ) const { QgsFeature f = feature( idx ); action->triggerForFeature( layer(), &f ); } QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const { QgsFeature f; f.initAttributes( mAttributes.size() ); f.setId( rowToId( idx.row() ) ); for ( int i = 0; i < mAttributes.size(); i++ ) { f.setAttribute( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) ); } return f; } void QgsAttributeTableModel::prefetchColumnData( int column ) { if ( column == -1 || column >= mAttributes.count() ) { prefetchSortData( QString() ); } else { prefetchSortData( QgsExpression::quotedColumnRef( mLayerCache->layer()->fields().at( mAttributes.at( column ) ).name() ) ); } } void QgsAttributeTableModel::prefetchSortData( const QString &expressionString, unsigned long cacheIndex ) { if ( cacheIndex >= mSortCaches.size() ) { mSortCaches.resize( cacheIndex + 1 ); } SortCache &cache = mSortCaches[cacheIndex]; cache.sortCache.clear(); cache.sortCacheAttributes.clear(); cache.sortFieldIndex = -1; if ( !expressionString.isEmpty() ) cache.sortCacheExpression = QgsExpression( expressionString ); else { // no sorting cache.sortCacheExpression = QgsExpression(); return; } QgsFieldFormatter *fieldFormatter = nullptr; QVariant widgetCache; QVariantMap widgetConfig; if ( cache.sortCacheExpression.isField() ) { QString fieldName = static_cast( cache.sortCacheExpression.rootNode() )->name(); cache.sortFieldIndex = mLayerCache->layer()->fields().lookupField( fieldName ); } if ( cache.sortFieldIndex == -1 ) { cache.sortCacheExpression.prepare( &mExpressionContext ); const QSet &referencedColumns = cache.sortCacheExpression.referencedColumns(); for ( const QString &col : referencedColumns ) { cache.sortCacheAttributes.append( mLayerCache->layer()->fields().lookupField( col ) ); } } else { cache.sortCacheAttributes.append( cache.sortFieldIndex ); widgetCache = mAttributeWidgetCaches.at( cache.sortFieldIndex ); widgetConfig = mWidgetConfigs.at( cache.sortFieldIndex ); fieldFormatter = mFieldFormatters.at( cache.sortFieldIndex ); } QgsFeatureRequest request = QgsFeatureRequest( mFeatureRequest ) .setFlags( QgsFeatureRequest::NoGeometry ) .setSubsetOfAttributes( cache.sortCacheAttributes ); QgsFeatureIterator it = mLayerCache->getFeatures( request ); QgsFeature f; while ( it.nextFeature( f ) ) { if ( cache.sortFieldIndex == -1 ) { mExpressionContext.setFeature( f ); const QVariant cacheValue = cache.sortCacheExpression.evaluate( &mExpressionContext ); cache.sortCache.insert( f.id(), cacheValue ); } else { QVariant sortValue = fieldFormatter->sortValue( layer(), cache.sortFieldIndex, widgetConfig, widgetCache, f.attribute( cache.sortFieldIndex ) ); cache.sortCache.insert( f.id(), sortValue ); } } } QString QgsAttributeTableModel::sortCacheExpression( unsigned long cacheIndex ) const { QString expressionString; if ( cacheIndex >= mSortCaches.size() ) return expressionString; const QgsExpression &expression = mSortCaches[cacheIndex].sortCacheExpression; if ( expression.isValid() ) expressionString = expression.expression(); else expressionString = QString(); return expressionString; } void QgsAttributeTableModel::setRequest( const QgsFeatureRequest &request ) { mFeatureRequest = request; if ( layer() && !layer()->isSpatial() ) mFeatureRequest.setFlags( mFeatureRequest.flags() | QgsFeatureRequest::NoGeometry ); } const QgsFeatureRequest &QgsAttributeTableModel::request() const { return mFeatureRequest; }