Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1a83d21
commit fa8f2d0
Showing
3 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
#include "qgsquickfeatureslistmodel.h" | ||
#include "qgsexpressioncontextutils.h" | ||
#include "qgslogger.h" | ||
#include "qvariant.h" | ||
|
||
QgsQuickFeaturesListModel::QgsQuickFeaturesListModel( QObject *parent ) | ||
: QAbstractListModel( parent ), | ||
mCurrentLayer( nullptr ), | ||
mModelType( modelTypes::FeatureListing ) | ||
{ | ||
} | ||
|
||
QgsQuickFeatureLayerPair QgsQuickFeaturesListModel::featureLayerPair( const int &featureId ) | ||
{ | ||
for ( const QgsQuickFeatureLayerPair &i : mFeatures ) | ||
{ | ||
if ( i.feature().id() == featureId ) | ||
return i; | ||
} | ||
return QgsQuickFeatureLayerPair(); | ||
} | ||
|
||
int QgsQuickFeaturesListModel::rowCount( const QModelIndex &parent ) const | ||
{ | ||
// For list models only the root node (an invalid parent) should return the list's size. For all | ||
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model. | ||
if ( parent.isValid() ) | ||
return 0; | ||
|
||
return mFeatures.count(); | ||
} | ||
|
||
QVariant QgsQuickFeaturesListModel::featureTitle( const QgsQuickFeatureLayerPair &featurePair ) const | ||
{ | ||
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( featurePair.layer() ) ); | ||
context.setFeature( featurePair.feature() ); | ||
QgsExpression expr( featurePair.layer()->displayExpression() ); | ||
const QString title = expr.evaluate( &context ).toString(); | ||
|
||
if ( title.isEmpty() ) | ||
return QVariant( featurePair.feature().id() ); | ||
|
||
return QVariant( title ); | ||
} | ||
|
||
QVariant QgsQuickFeaturesListModel::data( const QModelIndex &index, int role ) const | ||
{ | ||
int row = index.row(); | ||
if ( row < 0 || row >= mFeatures.count() ) | ||
return QVariant(); | ||
|
||
if ( !index.isValid() ) | ||
return QVariant(); | ||
|
||
const QgsQuickFeatureLayerPair pair = mFeatures.at( index.row() ); | ||
|
||
switch ( role ) | ||
{ | ||
case FeatureTitle: return featureTitle( pair ); | ||
case FeatureId: return QVariant( pair.feature().id() ); | ||
case Feature: return QVariant::fromValue<QgsFeature>( pair.feature() ); | ||
case Description: return QVariant( QString( "Feature ID %1" ).arg( pair.feature().id() ) ); | ||
case EmitableIndex: { | ||
if ( mModelType == modelTypes::ValueRelation ) | ||
return pair.feature().attribute( mKeyFieldName ); | ||
return pair.feature().id(); | ||
} | ||
case FoundPair: return foundPair( pair ); | ||
case Qt::DisplayRole: { | ||
if ( row >= 0 && row < mCache.count() ) | ||
{ | ||
int r = rowIndexFromKey( pair.feature().attribute( mKeyFieldName ) ); | ||
return mCache[r].value; | ||
} | ||
} | ||
} | ||
|
||
return QVariant(); | ||
} | ||
|
||
QString QgsQuickFeaturesListModel::foundPair( const QgsQuickFeatureLayerPair &pair ) const | ||
{ | ||
if ( mFilterExpression.isEmpty() ) | ||
return QString(); | ||
|
||
QgsFields fields = pair.feature().fields(); | ||
|
||
for ( const QgsField &field : fields ) | ||
{ | ||
QString attrValue = pair.feature().attribute( field.name() ).toString(); | ||
|
||
if ( attrValue.toLower().indexOf( mFilterExpression.toLower() ) != -1 ) | ||
return field.name() + ": " + attrValue; | ||
} | ||
return QString(); | ||
} | ||
|
||
QString QgsQuickFeaturesListModel::buildFilterExpression() | ||
{ | ||
if ( mFilterExpression.isEmpty() || !mCurrentLayer ) | ||
return QString(); | ||
|
||
const QgsFields fields = mCurrentLayer->fields(); | ||
QStringList expressionParts; | ||
|
||
bool filterExpressionIsNumeric; | ||
mFilterExpression.toInt( &filterExpressionIsNumeric ); | ||
|
||
for ( const QgsField &field : fields ) | ||
{ | ||
if ( field.isNumeric() && filterExpressionIsNumeric ) | ||
expressionParts << QStringLiteral( "%1 ~ '%2.*'" ).arg( QgsExpression::quotedColumnRef( field.name() ), QString::number( mFilterExpression.toInt() ) ); | ||
else if ( field.type() == QVariant::String ) | ||
expressionParts << QStringLiteral( "%1 ILIKE '%%2%'" ).arg( QgsExpression::quotedColumnRef( field.name() ), mFilterExpression ); | ||
} | ||
|
||
QString expression = QStringLiteral( "(%1)" ).arg( expressionParts.join( QStringLiteral( " ) OR ( " ) ) ); | ||
|
||
return expression; | ||
} | ||
|
||
void QgsQuickFeaturesListModel::loadFeaturesFromLayer( QgsVectorLayer *layer ) | ||
{ | ||
if ( layer && layer->isValid() ) | ||
mCurrentLayer = layer; | ||
|
||
if ( mCurrentLayer ) | ||
{ | ||
beginResetModel(); | ||
|
||
mFeatures.clear(); | ||
QgsFeatureRequest req; | ||
if ( !mFilterExpression.isEmpty() ) | ||
req.setFilterExpression( buildFilterExpression() ); | ||
req.setLimit( FEATURES_LIMIT ); | ||
|
||
QgsFeatureIterator it = mCurrentLayer->getFeatures( req ); | ||
QgsFeature f; | ||
|
||
while ( it.nextFeature( f ) ) | ||
{ | ||
mFeatures << QgsQuickFeatureLayerPair( f, mCurrentLayer ); | ||
} | ||
emit featuresCountChanged( featuresCount() ); | ||
endResetModel(); | ||
} | ||
} | ||
|
||
void QgsQuickFeaturesListModel::populate( const QVariantMap &config ) | ||
{ | ||
beginResetModel(); | ||
emptyData(); | ||
|
||
mCache = QgsValueRelationFieldFormatter::createCache( config ); | ||
QgsVectorLayer *layer = QgsValueRelationFieldFormatter::resolveLayer( config, QgsProject::instance() ); | ||
|
||
if ( layer ) | ||
{ | ||
// save key field | ||
QgsFields fields = layer->fields(); | ||
mKeyFieldName = fields.field( config.value( QStringLiteral( "Key" ) ).toString() ).name(); | ||
|
||
loadFeaturesFromLayer( layer ); | ||
} | ||
|
||
endResetModel(); | ||
} | ||
|
||
void QgsQuickFeaturesListModel::populateFromLayer( QgsVectorLayer *layer ) | ||
{ | ||
beginResetModel(); | ||
emptyData(); | ||
|
||
loadFeaturesFromLayer( layer ); | ||
endResetModel(); | ||
} | ||
|
||
void QgsQuickFeaturesListModel::emptyData() | ||
{ | ||
mFeatures.clear(); | ||
mCurrentLayer = nullptr; | ||
mCache.clear(); | ||
mKeyFieldName.clear(); | ||
mFilterExpression.clear(); | ||
} | ||
|
||
QHash<int, QByteArray> QgsQuickFeaturesListModel::roleNames() const | ||
{ | ||
QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames(); | ||
roleNames[FeatureTitle] = QStringLiteral( "FeatureTitle" ).toLatin1(); | ||
roleNames[FeatureId] = QStringLiteral( "FeatureId" ).toLatin1(); | ||
roleNames[Feature] = QStringLiteral( "Feature" ).toLatin1(); | ||
roleNames[Description] = QStringLiteral( "Description" ).toLatin1(); | ||
roleNames[FoundPair] = QStringLiteral( "FoundPair" ).toLatin1(); | ||
roleNames[EmitableIndex] = QStringLiteral( "EmitableIndex" ).toLatin1(); | ||
return roleNames; | ||
} | ||
|
||
int QgsQuickFeaturesListModel::featuresCount() const | ||
{ | ||
if ( mCurrentLayer ) | ||
return mCurrentLayer->featureCount(); | ||
return 0; | ||
} | ||
|
||
QString QgsQuickFeaturesListModel::filterExpression() const | ||
{ | ||
return mFilterExpression; | ||
} | ||
|
||
void QgsQuickFeaturesListModel::setFilterExpression( const QString &filterExpression ) | ||
{ | ||
mFilterExpression = filterExpression; | ||
emit filterExpressionChanged( mFilterExpression ); | ||
|
||
loadFeaturesFromLayer(); | ||
} | ||
|
||
int QgsQuickFeaturesListModel::featuresLimit() const | ||
{ | ||
return FEATURES_LIMIT; | ||
} | ||
|
||
QgsQuickFeaturesListModel::modelTypes QgsQuickFeaturesListModel::modelType() const | ||
{ | ||
return mModelType; | ||
} | ||
|
||
void QgsQuickFeaturesListModel::setModelType( modelTypes modelType ) | ||
{ | ||
mModelType = modelType; | ||
} | ||
|
||
int QgsQuickFeaturesListModel::rowIndexFromKey( const QVariant &key ) const | ||
{ | ||
for ( int i = 0; i < mCache.count(); ++i ) | ||
{ | ||
if ( mCache[i].key == key ) | ||
return i; | ||
} | ||
QgsDebugMsg( "rowForKey: key not found: " + key.toString() ); | ||
return -1; | ||
} | ||
|
||
int QgsQuickFeaturesListModel::rowIndexFromKeyModel( const QVariant &key ) const | ||
{ | ||
for ( int i = 0; i< mFeatures.count(); ++i ) | ||
{ | ||
if ( mFeatures[i].feature().attribute( mKeyFieldName ) == key ) | ||
return i; | ||
} | ||
QgsDebugMsg( "Could not find index in features model, index: " + key.toString() ); | ||
return -1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
#ifndef QGSQUICKFEATURESMODEL_H | ||
#define QGSQUICKFEATURESMODEL_H | ||
|
||
#include <QAbstractListModel> | ||
|
||
#include "qgsvectorlayer.h" | ||
#include "qgsquickfeaturelayerpair.h" | ||
#include "qgsvaluerelationfieldformatter.h" | ||
|
||
class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel | ||
{ | ||
Q_OBJECT | ||
|
||
Q_PROPERTY( int featuresCount READ featuresCount NOTIFY featuresCountChanged ) | ||
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged ) | ||
Q_PROPERTY( int featuresLimit READ featuresLimit NOTIFY featuresLimitChanged ) | ||
Q_PROPERTY( modelTypes modelType READ modelType WRITE setModelType ) | ||
|
||
enum roleNames | ||
{ | ||
FeatureTitle = Qt::UserRole + 1, | ||
FeatureId, | ||
Feature, | ||
Description, // secondary text in list view | ||
EmitableIndex, // key in value relation | ||
FoundPair // pair of attribute and its value by which the feature was found, empty if mFilterExpression is empty | ||
}; | ||
|
||
public: | ||
|
||
enum modelTypes | ||
{ | ||
FeatureListing, | ||
ValueRelation | ||
}; | ||
Q_ENUM( modelTypes ); | ||
|
||
explicit QgsQuickFeaturesListModel( QObject *parent = nullptr ); | ||
~QgsQuickFeaturesListModel() override {}; | ||
|
||
//! Function to get QgsQuickFeatureLayerPair by feature id | ||
Q_INVOKABLE QgsQuickFeatureLayerPair featureLayerPair( const int &featureId ); | ||
|
||
int rowCount( const QModelIndex &parent = QModelIndex() ) const override; | ||
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; | ||
QHash<int, QByteArray> roleNames() const override; | ||
|
||
//! Features count represents real number of features in layer being browsed | ||
int featuresCount() const; | ||
|
||
QString filterExpression() const; | ||
void setFilterExpression( const QString &filterExpression ); | ||
|
||
int featuresLimit() const; | ||
|
||
Q_INVOKABLE void populate( const QVariantMap &config ); | ||
|
||
Q_INVOKABLE void populateFromLayer( QgsVectorLayer *layer ); | ||
|
||
Q_INVOKABLE void loadFeaturesFromLayer( QgsVectorLayer *layer = nullptr ); | ||
|
||
//! Returns row number | ||
Q_INVOKABLE int rowIndexFromKey( const QVariant &key ) const; | ||
|
||
Q_INVOKABLE int rowIndexFromKeyModel( const QVariant &key ) const; | ||
|
||
modelTypes modelType() const; | ||
|
||
public slots: | ||
void setModelType( modelTypes modelType ); | ||
|
||
signals: | ||
void featuresCountChanged( int featuresCount ); | ||
void featuresLimitChanged( int featuresLimit ); | ||
void filterExpressionChanged( QString filterExpression ); | ||
|
||
private: | ||
//! Empty data when changing map theme or project | ||
void emptyData(); | ||
|
||
//! Builds feature title in list | ||
QVariant featureTitle( const QgsQuickFeatureLayerPair &featurePair ) const; | ||
|
||
//! Builds filter qgis expression from mFilterExpression | ||
QString buildFilterExpression(); | ||
|
||
//! Returns found attribute and its value from mFilterExpression | ||
QString foundPair( const QgsQuickFeatureLayerPair &feat ) const; | ||
|
||
//! QList of loaded features from layer | ||
//! Hold maximum of FEATURES_LIMIT features | ||
//! \note mFeatures.size() is not always the same as mFeaturesCount | ||
QList<QgsQuickFeatureLayerPair> mFeatures; | ||
|
||
//! Number of maximum features loaded from layer | ||
const int FEATURES_LIMIT = 10000; | ||
|
||
//! Search string, change of string results in reloading features from layer with this text | ||
QString mFilterExpression; | ||
|
||
//! Pointer to layer that is currently parsed | ||
QgsVectorLayer *mCurrentLayer = nullptr; | ||
|
||
//! Data from config for value relations | ||
QgsValueRelationFieldFormatter::ValueRelationCache mCache; | ||
|
||
//! Type of a model - Listing (browsing) features or use in Value relation widget | ||
modelTypes mModelType; | ||
|
||
QString mKeyFieldName; | ||
}; | ||
|
||
#endif // QGSQUICKFEATURESMODEL_H |