Skip to content

Commit

Permalink
Add features model - cpp
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasMizera authored and wonder-sk committed Sep 11, 2020
1 parent 1a83d21 commit fa8f2d0
Show file tree
Hide file tree
Showing 3 changed files with 369 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/quickgui/CMakeLists.txt
Expand Up @@ -19,6 +19,7 @@ SET(QGIS_QUICK_GUI_MOC_HDRS
qgsquicksimulatedpositionsource.h
qgsquickutils.h
qgsquickvaluerelationlistmodel.h
qgsquickfeatureslistmodel.h
)

SET(QGIS_QUICK_GUI_HDRS
Expand All @@ -45,6 +46,7 @@ SET(QGIS_QUICK_GUI_SRC
qgsquicksimulatedpositionsource.cpp
qgsquickutils.cpp
qgsquickvaluerelationlistmodel.cpp
qgsquickfeatureslistmodel.cpp
)

INCLUDE_DIRECTORIES(
Expand Down
254 changes: 254 additions & 0 deletions src/quickgui/qgsquickfeatureslistmodel.cpp
@@ -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;
}
113 changes: 113 additions & 0 deletions src/quickgui/qgsquickfeatureslistmodel.h
@@ -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

0 comments on commit fa8f2d0

Please sign in to comment.