diff --git a/.zenodo.json b/.zenodo.json
index 679504442..8e70b4f5c 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -2,7 +2,7 @@
"description": "
Mergin Maps mobile app is a QGIS powered app for Android and iOS devices.
",
"license": "GPLv3",
"title": "Mergin Maps mobile app",
- "version": "2024.5.0",
+ "version": "2024.4.1",
"upload_type": "software",
"publication_date": "2022-02-24",
"creators": [
@@ -39,7 +39,7 @@
"related_identifiers": [
{
"scheme": "url",
- "identifier": "https://github.com/MerginMaps/mobile/tree/2024.5.0",
+ "identifier": "https://github.com/MerginMaps/mobile/tree/2024.4.1",
"relation": "isSupplementTo"
},
{
diff --git a/CITATION.cff b/CITATION.cff
index 8b1d6266b..b53b22977 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,4 +1,4 @@
-cff-version: 2024.5.0
+cff-version: 2024.4.1
message: "If you use this software, please cite it as below."
authors:
- family-names: "Martin"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a883047c..a02d666a6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,8 +6,8 @@ cmake_minimum_required(VERSION 3.22)
# TODO automatically change with the scripts/update version script
set(MM_VERSION_MAJOR "2024")
-set(MM_VERSION_MINOR "5")
-set(MM_VERSION_PATCH "0")
+set(MM_VERSION_MINOR "4")
+set(MM_VERSION_PATCH "1")
set(QT_VERSION_DEFAULT "6.6.3")
# Note: we cannot set this for non-android build, since CMake will start looking for
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 7d8400e9d..cf9201375 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -53,7 +53,6 @@ set(MM_SRCS
compass.cpp
featurelayerpair.cpp
featuresmodel.cpp
- featuresproxymodel.cpp
fieldsmodel.cpp
guidelinecontroller.cpp
identifykit.cpp
@@ -138,7 +137,6 @@ set(MM_HDRS
enumhelper.h
featurelayerpair.h
featuresmodel.h
- featuresproxymodel.h
fieldsmodel.h
guidelinecontroller.h
identifykit.h
diff --git a/app/android/src/uk/co/lutraconsulting/PositionTrackingBroadcastMiddleware.java b/app/android/src/uk/co/lutraconsulting/PositionTrackingBroadcastMiddleware.java
index 1040bb519..5bf89a93a 100644
--- a/app/android/src/uk/co/lutraconsulting/PositionTrackingBroadcastMiddleware.java
+++ b/app/android/src/uk/co/lutraconsulting/PositionTrackingBroadcastMiddleware.java
@@ -10,6 +10,7 @@
package uk.co.lutraconsulting;
import android.util.Log;
+import android.os.Build;
import android.content.Intent;
import android.content.Context;
@@ -46,7 +47,12 @@ public void registerBroadcastReceiver( Context context ) {
intentFilter.addAction( TRACKING_ALIVE_STATUS_ACTION );
// Set this as the receiver of POSITION_UPDATE and STATUS_MESSAGE intent actions
- context.registerReceiver( serviceMessageReceiver, intentFilter );
+ if ( Build.VERSION.SDK_INT >= 34 ) { // Android 14 ( Upside Down Cake )
+ context.registerReceiver( serviceMessageReceiver, intentFilter, Context.RECEIVER_EXPORTED );
+ }
+ else {
+ context.registerReceiver( serviceMessageReceiver, intentFilter );
+ }
}
public void unregisterBroadcastReceiver( Context context ) {
diff --git a/app/android/src/uk/co/lutraconsulting/PositionTrackingService.java b/app/android/src/uk/co/lutraconsulting/PositionTrackingService.java
index 3d384e0a4..dc38a3cb8 100644
--- a/app/android/src/uk/co/lutraconsulting/PositionTrackingService.java
+++ b/app/android/src/uk/co/lutraconsulting/PositionTrackingService.java
@@ -16,6 +16,7 @@
import android.content.Intent;
import android.app.PendingIntent;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -65,7 +66,7 @@ public void onCreate() {
File file = new File( getFilesDir(), "tracking_updates.txt" );
sendStatusUpdateMessage( "Tracking file path:" + file.getAbsolutePath() );
-
+
try {
// Open the FileOutputStream in append mode
positionUpdatesStream = new FileOutputStream(file, true);
@@ -166,7 +167,12 @@ public int onStartCommand( Intent intent, int flags, int startId ) {
Notification notification = notificationBuilder.build();
- startForeground( SERVICE_ID, notification );
+ if ( Build.VERSION.SDK_INT >= 34) { // Android 14 ( Upside Down Cake )
+ startForeground( SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION );
+ }
+ else {
+ startForeground( SERVICE_ID, notification );
+ }
sendStatusUpdateMessage( "Position tracking: Started the foreground service!" );
diff --git a/app/featuresmodel.cpp b/app/featuresmodel.cpp
index 07a70552b..7cc386424 100644
--- a/app/featuresmodel.cpp
+++ b/app/featuresmodel.cpp
@@ -141,7 +141,6 @@ 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();
@@ -178,15 +177,6 @@ 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() )
@@ -261,6 +251,20 @@ void FeaturesModel::setupFeatureRequest( QgsFeatureRequest &request )
request.setFilterExpression( buildSearchExpression() );
}
+ if ( mUseAttributeTableSortOrder && mLayer && !mLayer->attributeTableConfig().sortExpression().isEmpty() )
+ {
+ // get a context with global, project and layer scopes
+ // QGIS docs are not very clear, but this context is also used for evaluation of the request's 'order by' expressions too
+ QgsExpressionContext context = mLayer->createExpressionContext();
+ request.setExpressionContext( context );
+ request.setOrderBy( QgsFeatureRequest::OrderBy(
+ {
+ QgsFeatureRequest::OrderByClause(
+ mLayer->attributeTableConfig().sortExpression(),
+ mLayer->attributeTableConfig().sortOrder() == Qt::AscendingOrder )
+ } ) );
+ }
+
request.setLimit( FEATURES_LIMIT );
}
@@ -290,7 +294,6 @@ 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;
}
@@ -363,7 +366,6 @@ void FeaturesModel::setLayer( QgsVectorLayer *newLayer )
}
mLayer = newLayer;
- setupSorting();
emit layerChanged( mLayer );
if ( mLayer )
@@ -384,19 +386,3 @@ 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 32af6cb56..1a7ddaad8 100644
--- a/app/featuresmodel.h
+++ b/app/featuresmodel.h
@@ -52,6 +52,9 @@ class FeaturesModel : public QAbstractListModel
// Name of the property is intentionally `count` so that it matches ListModel's count property
Q_PROPERTY( int count READ count NOTIFY countChanged )
+ // Returns if the model should be sorted according to the layer's attribute table configuration sort order
+ Q_PROPERTY( bool useAttributeTableSortOrder MEMBER mUseAttributeTableSortOrder )
+
public:
enum ModelRoles
@@ -64,7 +67,6 @@ 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 );
@@ -118,15 +120,6 @@ 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 );
@@ -149,9 +142,6 @@ class FeaturesModel : public QAbstractListModel
virtual QVariant featureTitle( const FeatureLayerPair &featurePair ) const;
- QString mSortExpression;
- Qt::SortOrder mSortOrder = Qt::AscendingOrder;
-
private slots:
void onFutureFinished();
@@ -164,9 +154,6 @@ 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;
@@ -176,6 +163,9 @@ class FeaturesModel : public QAbstractListModel
QAtomicInt mNextSearchId = 0;
QFutureWatcher mSearchResultWatcher;
bool mFetchingResults = false;
+ bool mUseAttributeTableSortOrder = false;
+
+ friend class TestModels;
};
#endif // FEATURESMODEL_H
diff --git a/app/featuresproxymodel.cpp b/app/featuresproxymodel.cpp
deleted file mode 100644
index a91664e7a..000000000
--- a/app/featuresproxymodel.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-/***************************************************************************
- * *
- * 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(); } );
-}
diff --git a/app/featuresproxymodel.h b/app/featuresproxymodel.h
deleted file mode 100644
index 3f3d537ef..000000000
--- a/app/featuresproxymodel.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/***************************************************************************
- * *
- * 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 "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
diff --git a/app/i18n/input_en.ts b/app/i18n/input_en.ts
index d4fb5e6cb..4513cea6d 100644
--- a/app/i18n/input_en.ts
+++ b/app/i18n/input_en.ts
@@ -156,7 +156,7 @@
FeaturesModel
-
+
@@ -735,7 +735,7 @@ Won't be added to the project.
MMFeaturesListPage
-
+
@@ -2791,12 +2791,12 @@ only allows up to %1 downloaded projects.
-
+
-
+
diff --git a/app/i18n/input_hu.qm b/app/i18n/input_hu.qm
index bad31a884..e28299bfa 100644
Binary files a/app/i18n/input_hu.qm and b/app/i18n/input_hu.qm differ
diff --git a/app/i18n/input_sl.qm b/app/i18n/input_sl.qm
index eadfb766c..91f9f5a6a 100644
Binary files a/app/i18n/input_sl.qm and b/app/i18n/input_sl.qm differ
diff --git a/app/i18n/input_sl.ts b/app/i18n/input_sl.ts
index 980815380..6a7813f45 100644
--- a/app/i18n/input_sl.ts
+++ b/app/i18n/input_sl.ts
@@ -353,8 +353,8 @@ V projekt ne bo dodano.
MMAboutPage
-
- O Mergin Maps
+
+ O programu
@@ -389,8 +389,8 @@ V projekt ne bo dodano.
-
- Bolje je delati skupaj, pridruži se delovnemu prostoru in začni raziskovati Mergin Maps!
+
+ Pridruži se delovnemu prostoru in raziščimo skupaj!
@@ -422,8 +422,8 @@ V projekt ne bo dodano.
-
- Sprejemam Mergin Maps %1Pogoje uporabe%3 in %2Politiko zasebnosti%3
+
+ Sprejemam %1pravila in pogoje%3 ter %2politiko zasebnosti%3
@@ -583,8 +583,8 @@ V projekt ne bo dodano.
-
- To dejanje bo izbrisalo tvoj Mergin Maps račun . Če si lastnik delovnega prostora, moraš lastništvo prenesti na nekoga drugega ali zapreti delovni prostor.
+
+ To dejanje bo izbrisalo tvoj račun . Če si lastnik delovnega prostora, moraš lastništvo prenesti na nekoga drugega ali zapreti delovni prostor.
@@ -659,8 +659,8 @@ V projekt ne bo dodano.
-
- Nasvet od Mergin Maps
+
+ Nasvet od nas
@@ -1200,7 +1200,7 @@ V projekt ne bo dodano.
- (no-entries)
+ (ni-zapisov)
@@ -1286,7 +1286,7 @@ V projekt ne bo dodano.
- streaming
+ sledenje
@@ -1427,8 +1427,8 @@ V projekt ne bo dodano.
-
- Za sinhronizacijo projekta moraš biti prijavljen v svoj Mergin Maps račun.
+
+ Za sinhronizacijo projekta moraš biti prijavljen v svoj račun.
@@ -1524,8 +1524,8 @@ V projekt ne bo dodano.
-
- Ta funkcija ni na voljo za iOS. Vaš prodajalec strojne opreme lahko zagotovi posebno aplikacijo, ki se poveže s sprejemnikom in nastavi lokacijo. Mergin Maps bo še vedno upošteval, da je to notranji GPS vašega telefona/tablice.
+
+ Ta funkcija ni na voljo za iOS. Vaš prodajalec strojne opreme lahko zagotovi posebno aplikacijo, ki se poveže s sprejemnikom in nastavi lokacijo. Aplikacija bo še vedno upoštevala, da je to notranji GPS vašega telefona/tablice.
@@ -1547,13 +1547,13 @@ V projekt ne bo dodano.
-
- Mergin Maps lahko v tem projektu sledi tvojemu položaju.
+
+ Sledi tvojim potem tudi ob izklopljenem zaslonu. Poti so shranjene v ločenem sloju. Končane poti se sinhronizirajo enako kot drugi podatki.
-
- Sledi tvojim potem tudi ob izklopljenem zaslonu. Poti so shranjene v ločenem sloju. Končane poti se sinhronizirajo enako kot drugi podatki.
+
+ Aplikacija lahko v tem projektu sledi tvojemu položaju.
@@ -1695,8 +1695,8 @@ V projekt ne bo dodano.
-
- Klikni tukaj za Mergin Maps nadzorno ploščo
+
+ Klikni tukaj za dostop do nadzorne plošče
@@ -1769,8 +1769,8 @@ V projekt ne bo dodano.
-
- Začni delo z Mergin Maps
+
+ Začnimo!
@@ -2327,12 +2327,12 @@ V projekt ne bo dodano.
- Stop streaming mode
+ Ustavi sledenje
- Start streaming mode
+ Začni sledenje
@@ -2398,8 +2398,8 @@ V projekt ne bo dodano.
-
- We've been busy making Mergin Maps even better! This update brings a fresh look and improved navigation, making it faster to find what you need. Take a look around!
+
+ Zelo smo se potrudili, da bi aplikacijo še izboljšali! Ta posodobitev prinaša svež videz in izboljšano navigacijo, zaradi česar boš hitreje našeš, kar potrebuješ. Razišči naokrog!
diff --git a/app/i18n/input_sl_SI.qm b/app/i18n/input_sl_SI.qm
index 208f13724..b375e978f 100644
Binary files a/app/i18n/input_sl_SI.qm and b/app/i18n/input_sl_SI.qm differ
diff --git a/app/i18n/input_sl_SI.ts b/app/i18n/input_sl_SI.ts
index 855d2c0b2..835b58300 100644
--- a/app/i18n/input_sl_SI.ts
+++ b/app/i18n/input_sl_SI.ts
@@ -353,8 +353,8 @@ V projekt ne bo dodano.
MMAboutPage
-
- O Mergin Maps
+
+ O programu
@@ -389,8 +389,8 @@ V projekt ne bo dodano.
-
- Bolje je delati skupaj, pridruži se delovnemu prostoru in začni raziskovati Mergin Maps!
+
+ Pridruži se delovnemu prostoru in raziščimo skupaj!
@@ -422,8 +422,8 @@ V projekt ne bo dodano.
-
- Sprejemam Mergin Maps %1Pogoje uporabe%3 in %2Politiko zasebnosti%3
+
+ Sprejemam %1pravila in pogoje%3 ter %2politiko zasebnosti%3
@@ -583,8 +583,8 @@ V projekt ne bo dodano.
-
- To dejanje bo izbrisalo tvoj Mergin Maps račun . Če si lastnik delovnega prostora, moraš lastništvo prenesti na nekoga drugega ali zapreti delovni prostor.
+
+ To dejanje bo izbrisalo tvoj račun . Če si lastnik delovnega prostora, moraš lastništvo prenesti na nekoga drugega ali zapreti delovni prostor.
@@ -659,8 +659,8 @@ V projekt ne bo dodano.
-
- Nasvet od Mergin Maps
+
+ Nasvet od nas
@@ -1427,8 +1427,8 @@ V projekt ne bo dodano.
-
- Za sinhronizacijo projekta moraš biti prijavljen v svoj Mergin Maps račun.
+
+ Za sinhronizacijo projekta moraš biti prijavljen v svoj račun.
@@ -1524,8 +1524,8 @@ V projekt ne bo dodano.
-
- Ta funkcija ni na voljo za iOS. Vaš prodajalec strojne opreme lahko zagotovi posebno aplikacijo, ki se poveže s sprejemnikom in nastavi lokacijo. Mergin Maps bo še vedno upošteval, da je to notranji GPS vašega telefona/tablice.
+
+ Ta funkcija ni na voljo za iOS. Vaš prodajalec strojne opreme lahko zagotovi posebno aplikacijo, ki se poveže s sprejemnikom in nastavi lokacijo. Aplikacija bo še vedno upoštevala, da je to notranji GPS vašega telefona/tablice.
@@ -1547,13 +1547,13 @@ V projekt ne bo dodano.
-
- Mergin Maps lahko v tem projektu sledi tvojemu položaju.
+
+ Snema tvoje poti tudi ob izklopljenem zaslonu. Posnete poti so shranjene v ločenem sloju. Končane poti se sinhronizirajo enako kot drugi podatki.
-
- Snema tvoje poti tudi ob izklopljenem zaslonu. Posnete poti so shranjene v ločenem sloju. Končane poti se sinhronizirajo enako kot drugi podatki.
+
+ Aplikacija lahko v tem projektu sledi tvojemu položaju.
@@ -1695,8 +1695,8 @@ V projekt ne bo dodano.
-
- Klikni tukaj za Mergin Maps nadzorno ploščo
+
+ Klikni tukaj za dostop do nadzorne plošče
@@ -1769,8 +1769,8 @@ V projekt ne bo dodano.
-
- Začni delo z Mergin Maps
+
+ Začnimo!
@@ -2398,8 +2398,8 @@ V projekt ne bo dodano.
-
- We've been busy making Mergin Maps even better! This update brings a fresh look and improved navigation, making it faster to find what you need. Take a look around!
+
+ Zelo smo se potrudili, da bi aplikacijo še izboljšali! Ta posodobitev prinaša svež videz in izboljšano navigacijo, zaradi česar boš hitreje našeš, kar potrebuješ. Razišči naokrog!
diff --git a/app/main.cpp b/app/main.cpp
index 148e00c6c..2ff044e83 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -91,7 +91,6 @@
#include "position/positionkit.h"
#include "scalebarkit.h"
#include "featuresmodel.h"
-#include "featuresproxymodel.h"
#include "relationfeaturesmodel.h"
#include "relationreferencefeaturesmodel.h"
#include "fieldvalidator.h"
@@ -328,7 +327,6 @@ 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 41c4b31a3..e98643ed2 100644
--- a/app/qml/form/editors/MMFormValueRelationEditor.qml
+++ b/app/qml/form/editors/MMFormValueRelationEditor.qml
@@ -76,15 +76,11 @@ MMFormComboboxBaseEditor {
valueRole: "FeatureId"
textRole: "FeatureTitle"
- list.model: MM.FeaturesProxyModel {
- id: vrDropdownProxyModel
+ list.model: MM.ValueRelationFeaturesModel {
+ id: vrDropdownModel
- featuresSourceModel: MM.ValueRelationFeaturesModel {
- id: vrDropdownModel
-
- config: root._fieldConfig
- pair: root._fieldFeatureLayerPair
- }
+ 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 41c5012c8..04b8bb256 100644
--- a/app/qml/layers/MMFeaturesListPage.qml
+++ b/app/qml/layers/MMFeaturesListPage.qml
@@ -21,6 +21,7 @@ MMComponents.MMPage {
property var selectedLayer: null
property bool hasToolbar: false
+ property bool layerIsReadOnly: selectedLayer?.readOnly ?? false
signal featureClicked( var featurePair )
signal addFeatureClicked( var toLayer )
@@ -55,14 +56,11 @@ MMComponents.MMPage {
topMargin: __style.spacing20
}
- model: MM.FeaturesProxyModel {
- id: featuresProxyModel
+ model: MM.FeaturesModel {
+ id: featuresModel
- featuresSourceModel: MM.FeaturesModel {
- id: featuresModel
-
- layer: root.selectedLayer
- }
+ useAttributeTableSortOrder: true
+ layer: root.selectedLayer
}
clip: true
@@ -91,7 +89,7 @@ MMComponents.MMPage {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.hasToolbar ? __style.margin20 : ( __style.safeAreaBottom + __style.margin8 )
- visible: __inputUtils.isNoGeometryLayer( root.selectedLayer )
+ visible: __inputUtils.isNoGeometryLayer( root.selectedLayer ) && !root.layerIsReadOnly
text: qsTr("Add feature")
diff --git a/app/test/testmodels.cpp b/app/test/testmodels.cpp
index 844fd07ba..0d637bd90 100644
--- a/app/test/testmodels.cpp
+++ b/app/test/testmodels.cpp
@@ -10,7 +10,6 @@
#include "testmodels.h"
#include "testutils.h"
#include "featuresmodel.h"
-#include "featuresproxymodel.h"
#include "valuerelationfeaturesmodel.h"
#include "projectsmodel.h"
#include "projectsproxymodel.h"
@@ -58,10 +57,9 @@ void TestModels::testFeaturesModel()
QCOMPARE( title, QStringLiteral( "First" ) );
}
-void TestModels::testFeaturesProxyModel()
+void TestModels::testFeaturesModelSorted()
{
FeaturesModel model;
- FeaturesProxyModel proxy;
QSignalSpy spy( &model, &FeaturesModel::fetchingResultsChanged );
@@ -71,66 +69,62 @@ void TestModels::testFeaturesProxyModel()
QVERIFY( layer && layer->isValid() );
// enable sorting
+ model.mUseAttributeTableSortOrder = true;
QgsAttributeTableConfig conf = layer->attributeTableConfig();
conf.setSortExpression( QStringLiteral( "Name" ) );
layer->setAttributeTableConfig( conf );
- proxy.setFeaturesSourceModel( &model );
model.setLayer( layer );
model.reloadFeatures();
spy.wait();
- QCOMPARE( proxy.rowCount(), layer->dataProvider()->featureCount() );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::SortValue ), QLatin1String( "A1" ) );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::SortValue ), QLatin1String( "A2" ) );
- QCOMPARE( proxy.data( proxy.index( 2, 0 ), FeaturesModel::SortValue ), QLatin1String( "B1" ) );
- QCOMPARE( proxy.data( proxy.index( 3, 0 ), FeaturesModel::SortValue ), QLatin1String( "B2" ) );
- QCOMPARE( proxy.data( proxy.index( 4, 0 ), FeaturesModel::SortValue ), QLatin1String( "C1" ) );
- QCOMPARE( proxy.data( proxy.index( 5, 0 ), FeaturesModel::SortValue ), QLatin1String( "C2" ) );
- QCOMPARE( proxy.data( proxy.index( 6, 0 ), FeaturesModel::SortValue ), QLatin1String( "D1" ) );
- QCOMPARE( proxy.data( proxy.index( 7, 0 ), FeaturesModel::SortValue ), QLatin1String( "D2" ) );
- QCOMPARE( proxy.data( proxy.index( 8, 0 ), FeaturesModel::SortValue ), QLatin1String( "VERYBIG" ) );
-
- // filter the fModel (this is not proxy model filtering)
- // and reverse sort order
+ QCOMPARE( model.rowCount(), layer->dataProvider()->featureCount() );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A1" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A2" ) );
+ QCOMPARE( model.data( model.index( 2, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "B1" ) );
+ QCOMPARE( model.data( model.index( 3, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "B2" ) );
+ QCOMPARE( model.data( model.index( 4, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "C1" ) );
+ QCOMPARE( model.data( model.index( 5, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "C2" ) );
+ QCOMPARE( model.data( model.index( 6, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D1" ) );
+ QCOMPARE( model.data( model.index( 7, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D2" ) );
+ QCOMPARE( model.data( model.index( 8, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "VERYBIG" ) );
+
+ // filter the model and reverse sort order
conf.setSortOrder( Qt::DescendingOrder );
layer->setAttributeTableConfig( conf );
- model.setupSorting();
model.setSearchExpression( QStringLiteral( "D" ) );
spy.wait();
- QCOMPARE( proxy.rowCount(), 2 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::SortValue ), QLatin1String( "D2" ) );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::SortValue ), QLatin1String( "D1" ) );
+ QCOMPARE( model.rowCount(), 2 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D2" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D1" ) );
// disable sorting and filtering
// should get all items with default ordering
- conf.setSortExpression( QString() );
- layer->setAttributeTableConfig( conf );
- model.setupSorting();
+ model.mUseAttributeTableSortOrder = false;
model.setSearchExpression( QString() );
spy.wait();
- QCOMPARE( proxy.rowCount(), layer->dataProvider()->featureCount() );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::FeatureId ), 1 );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::FeatureId ), 2 );
- QCOMPARE( proxy.data( proxy.index( 2, 0 ), FeaturesModel::FeatureId ), 3 );
- QCOMPARE( proxy.data( proxy.index( 3, 0 ), FeaturesModel::FeatureId ), 4 );
- QCOMPARE( proxy.data( proxy.index( 4, 0 ), FeaturesModel::FeatureId ), 5 );
- QCOMPARE( proxy.data( proxy.index( 5, 0 ), FeaturesModel::FeatureId ), 6 );
- QCOMPARE( proxy.data( proxy.index( 6, 0 ), FeaturesModel::FeatureId ), 7 );
- QCOMPARE( proxy.data( proxy.index( 7, 0 ), FeaturesModel::FeatureId ), 8 );
- QCOMPARE( proxy.data( proxy.index( 8, 0 ), FeaturesModel::FeatureId ), 100000000 );
+ QCOMPARE( model.rowCount(), layer->dataProvider()->featureCount() );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureId ), 1 );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureId ), 2 );
+ QCOMPARE( model.data( model.index( 2, 0 ), FeaturesModel::ModelRoles::FeatureId ), 3 );
+ QCOMPARE( model.data( model.index( 3, 0 ), FeaturesModel::ModelRoles::FeatureId ), 4 );
+ QCOMPARE( model.data( model.index( 4, 0 ), FeaturesModel::ModelRoles::FeatureId ), 5 );
+ QCOMPARE( model.data( model.index( 5, 0 ), FeaturesModel::ModelRoles::FeatureId ), 6 );
+ QCOMPARE( model.data( model.index( 6, 0 ), FeaturesModel::ModelRoles::FeatureId ), 7 );
+ QCOMPARE( model.data( model.index( 7, 0 ), FeaturesModel::ModelRoles::FeatureId ), 8 );
+ QCOMPARE( model.data( model.index( 8, 0 ), FeaturesModel::ModelRoles::FeatureId ), 100000000 );
}
-void TestModels::testFeaturesProxyModelWithValueRelation()
+void TestModels::testValueRelationFeaturesModel()
{
QString projectDir = TestUtils::testDataDir() + "/project_value_relations";
QString projectName = "proj.qgz";
@@ -151,8 +145,6 @@ void TestModels::testFeaturesProxyModelWithValueRelation()
FeatureLayerPair pair( f, mainLayer );
ValueRelationFeaturesModel model;
- FeaturesProxyModel proxy;
- proxy.setFeaturesSourceModel( &model );
QSignalSpy spy( &model, &FeaturesModel::fetchingResultsChanged );
@@ -171,78 +163,86 @@ void TestModels::testFeaturesProxyModelWithValueRelation()
QCOMPARE( model.rowCount(), 9 );
QCOMPARE( model.layer()->id(), subsubLayer->id() );
- QCOMPARE( proxy.rowCount(), 9 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::FeatureId ), 1 );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::FeatureId ), 2 );
- QCOMPARE( proxy.data( proxy.index( 2, 0 ), FeaturesModel::FeatureId ), 3 );
- QCOMPARE( proxy.data( proxy.index( 3, 0 ), FeaturesModel::FeatureId ), 4 );
- QCOMPARE( proxy.data( proxy.index( 4, 0 ), FeaturesModel::FeatureId ), 5 );
- QCOMPARE( proxy.data( proxy.index( 5, 0 ), FeaturesModel::FeatureId ), 6 );
- QCOMPARE( proxy.data( proxy.index( 6, 0 ), FeaturesModel::FeatureId ), 7 );
- QCOMPARE( proxy.data( proxy.index( 7, 0 ), FeaturesModel::FeatureId ), 8 );
- QCOMPARE( proxy.data( proxy.index( 8, 0 ), FeaturesModel::FeatureId ), 100000000 );
+ QCOMPARE( model.rowCount(), 9 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureId ), 1 );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureId ), 2 );
+ QCOMPARE( model.data( model.index( 2, 0 ), FeaturesModel::ModelRoles::FeatureId ), 3 );
+ QCOMPARE( model.data( model.index( 3, 0 ), FeaturesModel::ModelRoles::FeatureId ), 4 );
+ QCOMPARE( model.data( model.index( 4, 0 ), FeaturesModel::ModelRoles::FeatureId ), 5 );
+ QCOMPARE( model.data( model.index( 5, 0 ), FeaturesModel::ModelRoles::FeatureId ), 6 );
+ QCOMPARE( model.data( model.index( 6, 0 ), FeaturesModel::ModelRoles::FeatureId ), 7 );
+ QCOMPARE( model.data( model.index( 7, 0 ), FeaturesModel::ModelRoles::FeatureId ), 8 );
+ QCOMPARE( model.data( model.index( 8, 0 ), FeaturesModel::ModelRoles::FeatureId ), 100000000 );
// enable order by value for the value relation
model.reset();
config[ QStringLiteral( "OrderByValue" ) ] = true;
model.setConfig( config );
- model.setupSorting();
model.setPair( pair );
spy.wait();
- QCOMPARE( proxy.rowCount(), 9 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::SortValue ), QLatin1String( "A1" ) );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::SortValue ), QLatin1String( "A2" ) );
- QCOMPARE( proxy.data( proxy.index( 2, 0 ), FeaturesModel::SortValue ), QLatin1String( "B1" ) );
- QCOMPARE( proxy.data( proxy.index( 3, 0 ), FeaturesModel::SortValue ), QLatin1String( "B2" ) );
- QCOMPARE( proxy.data( proxy.index( 4, 0 ), FeaturesModel::SortValue ), QLatin1String( "C1" ) );
- QCOMPARE( proxy.data( proxy.index( 5, 0 ), FeaturesModel::SortValue ), QLatin1String( "C2" ) );
- QCOMPARE( proxy.data( proxy.index( 6, 0 ), FeaturesModel::SortValue ), QLatin1String( "D1" ) );
- QCOMPARE( proxy.data( proxy.index( 7, 0 ), FeaturesModel::SortValue ), QLatin1String( "D2" ) );
- QCOMPARE( proxy.data( proxy.index( 8, 0 ), FeaturesModel::SortValue ), QLatin1String( "VERYBIG" ) );
-
- // add a search expression to base model
+ QCOMPARE( model.rowCount(), 9 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A1" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A2" ) );
+ QCOMPARE( model.data( model.index( 2, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "B1" ) );
+ QCOMPARE( model.data( model.index( 3, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "B2" ) );
+ QCOMPARE( model.data( model.index( 4, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "C1" ) );
+ QCOMPARE( model.data( model.index( 5, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "C2" ) );
+ QCOMPARE( model.data( model.index( 6, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D1" ) );
+ QCOMPARE( model.data( model.index( 7, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D2" ) );
+ QCOMPARE( model.data( model.index( 8, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "VERYBIG" ) );
+
+ // add a search expression to model
model.setSearchExpression( QStringLiteral( "D" ) );
spy.wait();
QCOMPARE( model.rowCount(), 2 );
- QCOMPARE( proxy.rowCount(), 2 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::SortValue ), QLatin1String( "D1" ) );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::SortValue ), QLatin1String( "D2" ) );
+ QCOMPARE( model.rowCount(), 2 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D1" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "D2" ) );
- // add a filter expression to the base model
+ // add a filter expression to the model
config[ QStringLiteral( "FilterExpression" ) ] = "subFk = 1";
model.setConfig( config );
- model.setupSorting();
model.setSearchExpression( QString() );
spy.wait();
QCOMPARE( model.rowCount(), 2 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::SortValue ), QLatin1String( "A1" ) );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::SortValue ), QLatin1String( "A2" ) );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A1" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A2" ) );
- // remove filters and sorting
+ // remove sorting
model.reset();
config.remove( QStringLiteral( "OrderByValue" ) );
+ model.setConfig( config );
+ model.setPair( pair );
+
+ spy.wait();
+
+ QCOMPARE( model.rowCount(), 2 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A2" ) );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureTitle ), QLatin1String( "A1" ) );
+
+ // remove filters
+ model.reset();
config.remove( QStringLiteral( "FilterExpression" ) );
model.setConfig( config );
- model.setupSorting();
model.setPair( pair );
spy.wait();
- QCOMPARE( proxy.rowCount(), 9 );
- QCOMPARE( proxy.data( proxy.index( 0, 0 ), FeaturesModel::FeatureId ), 1 );
- QCOMPARE( proxy.data( proxy.index( 1, 0 ), FeaturesModel::FeatureId ), 2 );
- QCOMPARE( proxy.data( proxy.index( 2, 0 ), FeaturesModel::FeatureId ), 3 );
- QCOMPARE( proxy.data( proxy.index( 3, 0 ), FeaturesModel::FeatureId ), 4 );
- QCOMPARE( proxy.data( proxy.index( 4, 0 ), FeaturesModel::FeatureId ), 5 );
- QCOMPARE( proxy.data( proxy.index( 5, 0 ), FeaturesModel::FeatureId ), 6 );
- QCOMPARE( proxy.data( proxy.index( 6, 0 ), FeaturesModel::FeatureId ), 7 );
- QCOMPARE( proxy.data( proxy.index( 7, 0 ), FeaturesModel::FeatureId ), 8 );
- QCOMPARE( proxy.data( proxy.index( 8, 0 ), FeaturesModel::FeatureId ), 100000000 );
+ QCOMPARE( model.rowCount(), 9 );
+ QCOMPARE( model.data( model.index( 0, 0 ), FeaturesModel::ModelRoles::FeatureId ), 1 );
+ QCOMPARE( model.data( model.index( 1, 0 ), FeaturesModel::ModelRoles::FeatureId ), 2 );
+ QCOMPARE( model.data( model.index( 2, 0 ), FeaturesModel::ModelRoles::FeatureId ), 3 );
+ QCOMPARE( model.data( model.index( 3, 0 ), FeaturesModel::ModelRoles::FeatureId ), 4 );
+ QCOMPARE( model.data( model.index( 4, 0 ), FeaturesModel::ModelRoles::FeatureId ), 5 );
+ QCOMPARE( model.data( model.index( 5, 0 ), FeaturesModel::ModelRoles::FeatureId ), 6 );
+ QCOMPARE( model.data( model.index( 6, 0 ), FeaturesModel::ModelRoles::FeatureId ), 7 );
+ QCOMPARE( model.data( model.index( 7, 0 ), FeaturesModel::ModelRoles::FeatureId ), 8 );
+ QCOMPARE( model.data( model.index( 8, 0 ), FeaturesModel::ModelRoles::FeatureId ), 100000000 );
}
void TestModels::testProjectsModel()
diff --git a/app/test/testmodels.h b/app/test/testmodels.h
index d66b1ba37..4c3e41991 100644
--- a/app/test/testmodels.h
+++ b/app/test/testmodels.h
@@ -23,8 +23,8 @@ class TestModels : public QObject
void cleanup(); // will be called after every testfunction.
void testFeaturesModel();
- void testFeaturesProxyModel();
- void testFeaturesProxyModelWithValueRelation();
+ void testFeaturesModelSorted();
+ void testValueRelationFeaturesModel();
void testProjectsModel();
void testProjectsProxyModel();
diff --git a/app/valuerelationfeaturesmodel.cpp b/app/valuerelationfeaturesmodel.cpp
index 2ed0638de..6ce4e65ae 100644
--- a/app/valuerelationfeaturesmodel.cpp
+++ b/app/valuerelationfeaturesmodel.cpp
@@ -40,6 +40,12 @@ void ValueRelationFeaturesModel::setupFeatureRequest( QgsFeatureRequest &request
request.setExpressionContext( filterContext );
}
}
+
+ if ( mConfig.value( QStringLiteral( "OrderByValue" ) ).toBool() )
+ {
+ // replace any existing order by clause with our value field
+ request.setOrderBy( QgsFeatureRequest::OrderBy( { QgsFeatureRequest::OrderByClause( mTitleField ) } ) );
+ }
}
void ValueRelationFeaturesModel::setup()
@@ -207,9 +213,3 @@ 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 2c660efeb..7a1d178bd 100644
--- a/app/valuerelationfeaturesmodel.h
+++ b/app/valuerelationfeaturesmodel.h
@@ -37,7 +37,6 @@ 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
diff --git a/cmake_templates/AndroidManifest.xml.in b/cmake_templates/AndroidManifest.xml.in
index 729a9c1b1..07fd864ee 100644
--- a/cmake_templates/AndroidManifest.xml.in
+++ b/cmake_templates/AndroidManifest.xml.in
@@ -17,6 +17,7 @@
+