diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b96c77c27..f371e92eb 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -52,6 +52,7 @@ set(MM_SRCS compass.cpp featurelayerpair.cpp featuresmodel.cpp + featuresproxymodel.cpp fieldsmodel.cpp guidelinecontroller.cpp identifykit.cpp @@ -135,6 +136,7 @@ set(MM_HDRS enumhelper.h featurelayerpair.h featuresmodel.h + featuresproxymodel.h fieldsmodel.h guidelinecontroller.h identifykit.h diff --git a/app/featuresmodel.cpp b/app/featuresmodel.cpp index 98cde7cbe..967874ca4 100644 --- a/app/featuresmodel.cpp +++ b/app/featuresmodel.cpp @@ -141,6 +141,7 @@ QVariant FeaturesModel::data( const QModelIndex &index, int role ) const case LayerName: return pair.layer() ? pair.layer()->name() : QString(); case LayerIcon: return pair.layer() ? InputUtils::loadIconFromLayer( pair.layer() ) : QString(); case Qt::DisplayRole: return featureTitle( pair ); + case SortValue: return sortValue( pair ); } return QVariant(); @@ -171,6 +172,15 @@ QVariant FeaturesModel::featureTitle( const FeatureLayerPair &featurePair ) cons return title; } +QVariant FeaturesModel::sortValue( const FeatureLayerPair &featurePair ) const +{ + QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( featurePair.layer() ) ); + context.setFeature( featurePair.feature() ); + QgsExpression expr( mSortExpression ); + QVariant result = expr.evaluate( &context ); + return result; +} + QString FeaturesModel::searchResultPair( const FeatureLayerPair &pair ) const { if ( mSearchExpression.isEmpty() ) @@ -274,6 +284,7 @@ QHash FeaturesModel::roleNames() const roleNames[SearchResult] = QStringLiteral( "SearchResult" ).toLatin1(); roleNames[LayerName] = QStringLiteral( "LayerName" ).toLatin1(); roleNames[LayerIcon] = QStringLiteral( "LayerIcon" ).toLatin1(); + roleNames[SortValue] = QStringLiteral( "SortValue" ).toLatin1(); return roleNames; } @@ -359,6 +370,7 @@ void FeaturesModel::setLayer( QgsVectorLayer *newLayer ) } emit layerFeaturesCountChanged( layerFeaturesCount() ); + setupSorting(); } } @@ -366,3 +378,19 @@ QgsVectorLayer *FeaturesModel::layer() const { return mLayer; } + +void FeaturesModel::setupSorting() +{ + mSortExpression = mLayer ? mLayer->attributeTableConfig().sortExpression() : QString(); + mSortOrder = mLayer ? mLayer->attributeTableConfig().sortOrder() : Qt::AscendingOrder; +} + +bool FeaturesModel::sortingEnabled() const +{ + return !mSortExpression.isEmpty(); +} + +Qt::SortOrder FeaturesModel::sortOrder() const +{ + return mSortOrder; +} diff --git a/app/featuresmodel.h b/app/featuresmodel.h index 1f1912cd0..779544b55 100644 --- a/app/featuresmodel.h +++ b/app/featuresmodel.h @@ -64,6 +64,7 @@ class FeaturesModel : public QAbstractListModel SearchResult, // pair of attribute and its value by which the feature was found, empty if search expression is empty LayerName, LayerIcon, + SortValue, }; Q_ENUM( ModelRoles ); @@ -117,6 +118,12 @@ class FeaturesModel : public QAbstractListModel int layerFeaturesCount() const; + //! Returns true if there is a sort expression set for the model + bool sortingEnabled() const; + + //! Returns the order in witch the model should be sorted + Qt::SortOrder sortOrder() const; + signals: void featuresLimitChanged( int featuresLimit ); @@ -139,6 +146,12 @@ class FeaturesModel : public QAbstractListModel virtual QVariant featureTitle( const FeatureLayerPair &featurePair ) const; + //! Populates the sort expression and sort order for the model + virtual void setupSorting(); + + QString mSortExpression; + Qt::SortOrder mSortOrder = Qt::AscendingOrder; + private slots: void onFutureFinished(); @@ -151,6 +164,9 @@ class FeaturesModel : public QAbstractListModel //! Returns found attribute and its value from search expression for feature QString searchResultPair( const FeatureLayerPair &feat ) const; + //! Evaluates the sort expression and returns the value used for this feature when sorting the model + QVariant sortValue( const FeatureLayerPair &featurePair ) const; + const int FEATURES_LIMIT = 10000; //!< Number of maximum features loaded from layer FeatureLayerPairs mFeatures; diff --git a/app/featuresproxymodel.cpp b/app/featuresproxymodel.cpp new file mode 100644 index 000000000..1694c6e58 --- /dev/null +++ b/app/featuresproxymodel.cpp @@ -0,0 +1,41 @@ +/*************************************************************************** + * * + * 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 "featuresproxymodel.h" + +FeaturesProxyModel::FeaturesProxyModel( QObject *parent ) : QSortFilterProxyModel( parent ) +{ +} + +void FeaturesProxyModel::initialize() +{ + setSourceModel( mModel ); + + // don't sort if there is no sort expression for the layer + if ( mModel->sortingEnabled() ) + { + setSortRole( FeaturesModel::SortValue ); + setSortCaseSensitivity( Qt::CaseInsensitive ); + sort( 0, mModel->sortOrder() ); + } +} + +FeaturesModel *FeaturesProxyModel::featuresSourceModel() const +{ + return mModel; +} + +void FeaturesProxyModel::setFeaturesSourceModel( FeaturesModel *sourceModel ) +{ + if ( mModel == sourceModel ) + return; + + mModel = sourceModel; + QObject::connect( mModel, &FeaturesModel::fetchingResultsChanged, this, [ = ]( bool pending ) { if ( !pending ) initialize(); } ); +} diff --git a/app/featuresproxymodel.h b/app/featuresproxymodel.h new file mode 100644 index 000000000..b20419316 --- /dev/null +++ b/app/featuresproxymodel.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef FEATURESPROXYMODEL_H +#define FEATURESPROXYMODEL_H + +#include +#include + +#include "inputconfig.h" +#include "featuresmodel.h" + +/** + * \brief The FeaturesProxyModel class used as a proxy sort model for the \see FeaturesModel class. + * + * FeaturesProxyModel is a QML type with required property of FeatureSourceModel. Without source model, this model does nothing (is not initialized). + * After setting source model, this model starts sorting and allows filtering (search) from view. + */ +class FeaturesProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY( FeaturesModel *featuresSourceModel READ featuresSourceModel WRITE setFeaturesSourceModel ) + + public: + explicit FeaturesProxyModel( QObject *parent = nullptr ); + ~FeaturesProxyModel() override {}; + + FeaturesModel *featuresSourceModel() const; + + public slots: + void setFeaturesSourceModel( FeaturesModel *sourceModel ); + + private: + void initialize(); + + FeaturesModel *mModel = nullptr; // not owned by this, needs to be set in order to proxy model to work + + Qt::SortOrder mSortOrder = Qt::SortOrder::AscendingOrder; + QString mSortExpression; + + friend class TestModels; +}; + +#endif // FEATURESPROXYMODEL_H diff --git a/app/main.cpp b/app/main.cpp index 3ddf8356a..37f8264c5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -91,6 +91,7 @@ #include "position/positionkit.h" #include "scalebarkit.h" #include "featuresmodel.h" +#include "featuresproxymodel.h" #include "relationfeaturesmodel.h" #include "relationreferencefeaturesmodel.h" #include "fieldvalidator.h" @@ -326,6 +327,7 @@ void initDeclarative() qmlRegisterType< MapThemesModel >( "mm", 1, 0, "MapThemesModel" ); qmlRegisterType< GuidelineController >( "mm", 1, 0, "GuidelineController" ); qmlRegisterType< FeaturesModel >( "mm", 1, 0, "FeaturesModel" ); + qmlRegisterType< FeaturesProxyModel >( "mm", 1, 0, "FeaturesProxyModel" ); qmlRegisterType< RelationFeaturesModel >( "mm", 1, 0, "RelationFeaturesModel" ); qmlRegisterType< ValueRelationFeaturesModel >( "mm", 1, 0, "ValueRelationFeaturesModel" ); qmlRegisterType< RelationReferenceFeaturesModel >( "mm", 1, 0, "RelationReferenceFeaturesModel" ); diff --git a/app/qml/form/editors/MMFormValueRelationEditor.qml b/app/qml/form/editors/MMFormValueRelationEditor.qml index e98643ed2..41c4b31a3 100644 --- a/app/qml/form/editors/MMFormValueRelationEditor.qml +++ b/app/qml/form/editors/MMFormValueRelationEditor.qml @@ -76,11 +76,15 @@ MMFormComboboxBaseEditor { valueRole: "FeatureId" textRole: "FeatureTitle" - list.model: MM.ValueRelationFeaturesModel { - id: vrDropdownModel + list.model: MM.FeaturesProxyModel { + id: vrDropdownProxyModel - config: root._fieldConfig - pair: root._fieldFeatureLayerPair + featuresSourceModel: MM.ValueRelationFeaturesModel { + id: vrDropdownModel + + config: root._fieldConfig + pair: root._fieldFeatureLayerPair + } } onSearchTextChanged: ( searchText ) => vrDropdownModel.searchExpression = searchText diff --git a/app/qml/layers/MMFeaturesListPage.qml b/app/qml/layers/MMFeaturesListPage.qml index 5b162c87d..41c5012c8 100644 --- a/app/qml/layers/MMFeaturesListPage.qml +++ b/app/qml/layers/MMFeaturesListPage.qml @@ -55,10 +55,14 @@ MMComponents.MMPage { topMargin: __style.spacing20 } - model: MM.FeaturesModel { - id: featuresModel + model: MM.FeaturesProxyModel { + id: featuresProxyModel - layer: root.selectedLayer + featuresSourceModel: MM.FeaturesModel { + id: featuresModel + + layer: root.selectedLayer + } } clip: true diff --git a/app/relationfeaturesmodel.cpp b/app/relationfeaturesmodel.cpp index f56fae43b..5422882e4 100644 --- a/app/relationfeaturesmodel.cpp +++ b/app/relationfeaturesmodel.cpp @@ -157,3 +157,8 @@ void RelationFeaturesModel::setHomePath( const QString &homePath ) emit homePathChanged(); } } + +void RelationFeaturesModel::setupSorting() +{ + mSortExpression.clear(); +} diff --git a/app/relationfeaturesmodel.h b/app/relationfeaturesmodel.h index c66b65726..3be6c7822 100644 --- a/app/relationfeaturesmodel.h +++ b/app/relationfeaturesmodel.h @@ -50,6 +50,7 @@ class RelationFeaturesModel : public FeaturesModel void setup() override; void setupFeatureRequest( QgsFeatureRequest &request ) override; + void setupSorting() override; void setParentFeatureLayerPair( FeatureLayerPair pair ); void setRelation( QgsRelation relation ); diff --git a/app/relationreferencefeaturesmodel.cpp b/app/relationreferencefeaturesmodel.cpp index 5404704ee..97ed06f07 100644 --- a/app/relationreferencefeaturesmodel.cpp +++ b/app/relationreferencefeaturesmodel.cpp @@ -121,3 +121,8 @@ void RelationReferenceFeaturesModel::setProject( QgsProject *project ) setup(); } } + +void RelationReferenceFeaturesModel::setupSorting() +{ + mSortExpression.clear(); +} diff --git a/app/relationreferencefeaturesmodel.h b/app/relationreferencefeaturesmodel.h index 2a91db09c..ebb734bcd 100644 --- a/app/relationreferencefeaturesmodel.h +++ b/app/relationreferencefeaturesmodel.h @@ -53,6 +53,8 @@ class RelationReferenceFeaturesModel : public FeaturesModel //! Reads config and with project instance queries all features from parent layer. Emits populated signal after loading features. void setup() override; + void setupSorting() override; + signals: void configChanged( QVariantMap config ); void projectChanged( QgsProject *project ); diff --git a/app/valuerelationfeaturesmodel.cpp b/app/valuerelationfeaturesmodel.cpp index 7c0ef3a9e..2ed0638de 100644 --- a/app/valuerelationfeaturesmodel.cpp +++ b/app/valuerelationfeaturesmodel.cpp @@ -207,3 +207,9 @@ void ValueRelationFeaturesModel::setConfig( const QVariantMap &newConfig ) setup(); } + +void ValueRelationFeaturesModel::setupSorting() +{ + const bool orderByValue = mConfig.value( QStringLiteral( "OrderByValue" ) ).toBool(); + mSortExpression = orderByValue ? mTitleField : QString(); +} diff --git a/app/valuerelationfeaturesmodel.h b/app/valuerelationfeaturesmodel.h index 7a1d178bd..2c660efeb 100644 --- a/app/valuerelationfeaturesmodel.h +++ b/app/valuerelationfeaturesmodel.h @@ -37,6 +37,7 @@ class ValueRelationFeaturesModel : public FeaturesModel void reset() override; void setupFeatureRequest( QgsFeatureRequest &request ) override; QVariant featureTitle( const FeatureLayerPair &pair ) const override; + void setupSorting() override; Q_INVOKABLE QVariant convertToKey( const QVariant &id ); Q_INVOKABLE QVariant convertToQgisType( const QVariantList &featureIds ); // feature id -> key