Skip to content

Commit

Permalink
Introduce a FeaturesProxyModel to conditionally sort features list an…
Browse files Browse the repository at this point in the history
…d value relations (#3543)

* Introduce a FeaturesProxyModel to conditionally sort features list and value relations

MMFeaturesListPage is sorted according to the layer's QgsAttributeTableConfig
MMFormValueRelationEditor is sorted by value when the widget has the OrderByValue config set

* Address review comments

* Invalidate proxy model when disabling sorting
Remove unused private variables
Don't try to disconnect the first time model is set

* Add tests

* do initialization bits once

* call setupSorting() on the model only once
  • Loading branch information
uclaros authored Aug 9, 2024
1 parent 795bf57 commit a60b443
Show file tree
Hide file tree
Showing 13 changed files with 355 additions and 7 deletions.
2 changes: 2 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ set(MM_SRCS
compass.cpp
featurelayerpair.cpp
featuresmodel.cpp
featuresproxymodel.cpp
fieldsmodel.cpp
guidelinecontroller.cpp
identifykit.cpp
Expand Down Expand Up @@ -135,6 +136,7 @@ set(MM_HDRS
enumhelper.h
featurelayerpair.h
featuresmodel.h
featuresproxymodel.h
fieldsmodel.h
guidelinecontroller.h
identifykit.h
Expand Down
27 changes: 27 additions & 0 deletions app/featuresmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -177,6 +178,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() )
Expand Down Expand Up @@ -280,6 +290,7 @@ QHash<int, QByteArray> 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;
}

Expand Down Expand Up @@ -372,3 +383,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;
}
16 changes: 16 additions & 0 deletions app/featuresmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand Down Expand Up @@ -117,6 +118,15 @@ class FeaturesModel : public QAbstractListModel

int layerFeaturesCount() const;

//! Populates the sort expression and sort order for the model
virtual void setupSorting();

//! 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 );
Expand All @@ -139,6 +149,9 @@ class FeaturesModel : public QAbstractListModel

virtual QVariant featureTitle( const FeatureLayerPair &featurePair ) const;

QString mSortExpression;
Qt::SortOrder mSortOrder = Qt::AscendingOrder;

private slots:
void onFutureFinished();

Expand All @@ -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;
Expand Down
48 changes: 48 additions & 0 deletions app/featuresproxymodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/***************************************************************************
* *
* 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 )
{
setSortRole( FeaturesModel::SortValue );
setSortCaseSensitivity( Qt::CaseInsensitive );
}

void FeaturesProxyModel::updateSorting()
{
// don't sort if there is no sort expression for the layer
if ( mModel->sortingEnabled() )
{
sort( 0, mModel->sortOrder() );
}
else
{
invalidate();
}
}

FeaturesModel *FeaturesProxyModel::featuresSourceModel() const
{
return mModel;
}

void FeaturesProxyModel::setFeaturesSourceModel( FeaturesModel *sourceModel )
{
if ( mModel == sourceModel )
return;

if ( mModel )
disconnect( mModel, nullptr, this, nullptr );

mModel = sourceModel;
setSourceModel( mModel );
mModel->setupSorting();
connect( mModel, &FeaturesModel::fetchingResultsChanged, this, [ = ]( bool pending ) { if ( !pending ) updateSorting(); } );
}
47 changes: 47 additions & 0 deletions app/featuresproxymodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/***************************************************************************
* *
* 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 <QObject>
#include <QSortFilterProxyModel>

#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 if sortingEnabled() returns true for the source model.
*/
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 updateSorting();

FeaturesModel *mModel = nullptr; // not owned by this, needs to be set in order to proxy model to work

friend class TestModels;
};

#endif // FEATURESPROXYMODEL_H
2 changes: 2 additions & 0 deletions app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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" );
Expand Down
12 changes: 8 additions & 4 deletions app/qml/form/editors/MMFormValueRelationEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions app/qml/layers/MMFeaturesListPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit a60b443

Please sign in to comment.