Skip to content

Commit

Permalink
Merge pull request #5595 from opengisch/expression_evaluator_imp
Browse files Browse the repository at this point in the history
Expression template support for title and copyright decorations
  • Loading branch information
nirvn authored Aug 30, 2024
2 parents e866aa3 + ea0c618 commit 8661c38
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 25 deletions.
106 changes: 93 additions & 13 deletions src/core/expressionevaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,126 @@
* *
***************************************************************************/

#include "expressioncontextutils.h"
#include "expressionevaluator.h"
#include "qgsexpressioncontextutils.h"
#include "qgsproject.h"

#include <qgsexpressioncontextutils.h>

ExpressionEvaluator::ExpressionEvaluator( QObject *parent )
: QObject( parent )
{
}

void ExpressionEvaluator::setMode( Mode mode )
{
if ( mMode == mode )
return;

mMode = mode;
emit modeChanged();
}

void ExpressionEvaluator::setExpressionText( const QString &expressionText )
{
if ( mExpressionText == expressionText )
return;

mExpressionText = expressionText;
emit expressionTextChanged( mExpressionText );
emit expressionTextChanged();
}

void ExpressionEvaluator::setFeature( const QgsFeature &feature )
{
if ( mFeature == feature )
return;

mFeature = feature;
emit featureChanged( mFeature );
emit featureChanged();
}

void ExpressionEvaluator::setLayer( QgsMapLayer *layer )
{
if ( mLayer == layer )
return;

mLayer = layer;
emit layerChanged( mLayer );
emit layerChanged();
}

void ExpressionEvaluator::setProject( QgsProject *project )
{
if ( mProject == project )
return;

mProject = project;
emit projectChanged();
}

void ExpressionEvaluator::setMapSettings( QgsQuickMapSettings *mapSettings )
{
if ( mMapSettings == mapSettings )
return;

mMapSettings = mapSettings;
emit mapSettingsChanged();
}

void ExpressionEvaluator::setPositionInformation( const GnssPositionInformation &positionInformation )
{
mPositionInformation = positionInformation;
emit positionInformationChanged();
}

void ExpressionEvaluator::setCloudUserInformation( const CloudUserInformation &cloudUserInformation )
{
mCloudUserInformation = cloudUserInformation;
emit cloudUserInformationChanged();
}

QVariant ExpressionEvaluator::evaluate()
{
if ( !mFeature.isValid() || !mLayer || mExpressionText.isEmpty() )
if ( mExpressionText.isEmpty() )
return QString();

QgsExpression exp( mExpressionText );
QgsExpressionContext expressionContext;
expressionContext.setFeature( mFeature );
expressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
<< QgsExpressionContextUtils::layerScope( mLayer );
expressionContext << QgsExpressionContextUtils::globalScope();

if ( mPositionInformation.isValid() )
{
expressionContext << ExpressionContextUtils::positionScope( mPositionInformation, false );
}
if ( !mCloudUserInformation.username.isEmpty() )
{
expressionContext << ExpressionContextUtils::cloudUserScope( mCloudUserInformation );
}
if ( mMapSettings )
{
expressionContext << QgsExpressionContextUtils::mapSettingsScope( mMapSettings->mapSettings() );
}
if ( mProject )
{
expressionContext << QgsExpressionContextUtils::projectScope( mProject );
}
if ( mLayer )
{
expressionContext << QgsExpressionContextUtils::layerScope( mLayer );
}
if ( mFeature.isValid() )
{
expressionContext.setFeature( mFeature );
}

QVariant value;
if ( mMode == ExpressionMode )
{
QgsExpression expression( mExpressionText );
expression.prepare( &expressionContext );
value = expression.evaluate( &expressionContext );
}
else
{
value = QgsExpression::replaceExpressionText( mExpressionText, &expressionContext );
}

exp.prepare( &expressionContext );
QVariant value = exp.evaluate( &expressionContext );
return value.toString();
}
90 changes: 84 additions & 6 deletions src/core/expressionevaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,119 @@
#ifndef EXPRESSIONEVALUATOR_H
#define EXPRESSIONEVALUATOR_H

#include "gnsspositioninformation.h"
#include "qfieldcloudutils.h"
#include "qgsquickmapsettings.h"

#include <QObject>
#include <qgsexpression.h>
#include <qgsexpressioncontext.h>
#include <qgsmaplayer.h>
#include <qgsproject.h>


/**
* @brief The ExpressionEvaluator class enables evaluation of expression
* strings and expression templates.
*/
class ExpressionEvaluator : public QObject
{
Q_OBJECT

Q_PROPERTY( Mode mode READ mode WRITE setMode NOTIFY modeChanged )

Q_PROPERTY( QString expressionText READ expressionText WRITE setExpressionText NOTIFY expressionTextChanged )

Q_PROPERTY( QgsFeature feature READ feature WRITE setFeature NOTIFY featureChanged )
Q_PROPERTY( QgsMapLayer *layer READ layer WRITE setLayer NOTIFY layerChanged )
Q_PROPERTY( QgsProject *project READ project WRITE setProject NOTIFY projectChanged )
Q_PROPERTY( QgsProject *project READ project WRITE setProject NOTIFY projectChanged )
Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged )
Q_PROPERTY( GnssPositionInformation positionInformation READ positionInformation WRITE setPositionInformation NOTIFY positionInformationChanged )
Q_PROPERTY( CloudUserInformation cloudUserInformation READ cloudUserInformation WRITE setCloudUserInformation NOTIFY cloudUserInformationChanged )

public:
//! Expression evaluator modes
enum Mode
{
ExpressionMode, //!< Handle expression text as expression strings
ExpressionTemplateMode, //!< Handle expression text as expression templates
};
Q_ENUM( Mode )

explicit ExpressionEvaluator( QObject *parent = nullptr );

//! Returns the expression evaluator mode
Mode mode() const { return mMode; }

//! Sets the expression evaluator \a mode
void setMode( Mode mode );

//! Returns the expression text used when evaluating
QString expressionText() { return mExpressionText; }
QgsFeature feature() { return mFeature; }
QgsMapLayer *layer() { return mLayer; }

//! Sets the expression text used when evaluating
void setExpressionText( const QString &expressionText );

//! Returns the feature attached to the expression context
QgsFeature feature() const { return mFeature; }

//! Sets the feature attached to the expression context
void setFeature( const QgsFeature &feature );

//! Returns the map layer attached to the expression context
QgsMapLayer *layer() const { return mLayer; }

//! Sets the map layer attached to the expression context
void setLayer( QgsMapLayer *layer );

//! Returns the evaluated string value
//! Returns the project attached to the expression context
QgsProject *project() const { return mProject; }

//! Sets the project attached to the expression context
void setProject( QgsProject *project );

//! Returns the map settings attached to the expression context
QgsQuickMapSettings *mapSettings() const { return mMapSettings; }

//! Sets the map settings attached to the expression context
void setMapSettings( QgsQuickMapSettings *mapSettings );

//! Returns the position information attached to the expression context
GnssPositionInformation positionInformation() const { return mPositionInformation; }

//! Sets the position information attached to the expression context
void setPositionInformation( const GnssPositionInformation &positionInformation );

//! Returns the cloud user information attached to the expression context
CloudUserInformation cloudUserInformation() const { return mCloudUserInformation; }

//! Sets the cloud user information attached to the expression context
void setCloudUserInformation( const CloudUserInformation &cloudUserInformation );

//! Returns the evaluated expression text value
Q_INVOKABLE QVariant evaluate();

signals:
void layerChanged( QgsMapLayer *layer );
void expressionTextChanged( QString expressionText );
void featureChanged( QgsFeature feature );
void modeChanged();
void expressionTextChanged();
void featureChanged();
void layerChanged();
void projectChanged();
void mapSettingsChanged();
void positionInformationChanged();
void cloudUserInformationChanged();

private:
Mode mMode = ExpressionMode;

QString mExpressionText;

QgsFeature mFeature;
QgsMapLayer *mLayer = nullptr;
QgsProject *mProject = nullptr;
QgsQuickMapSettings *mMapSettings = nullptr;
GnssPositionInformation mPositionInformation;
CloudUserInformation mCloudUserInformation;
};
#endif // EXPRESSIONEVALUATOR_H
2 changes: 2 additions & 0 deletions src/qml/editorwidgets/ExternalResource.qml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ EditorWidgetBase {

ExpressionEvaluator {
id: rootPathEvaluator
project: qgisProject
}
property string prefixToRelativePath: {
if (qgisProject == undefined)
Expand Down Expand Up @@ -111,6 +112,7 @@ EditorWidgetBase {
id: expressionEvaluator
feature: currentFeature
layer: currentLayer
project: qgisProject
expressionText: {
var value;
if (currentLayer && currentLayer.customProperty('QFieldSync/attachment_naming') !== undefined) {
Expand Down
68 changes: 62 additions & 6 deletions src/qml/qgismobileapp.qml
Original file line number Diff line number Diff line change
Expand Up @@ -917,11 +917,53 @@ ApplicationWindow {
anchors.fill: mapCanvas
anchors.bottomMargin: informationDrawer.height

ExpressionEvaluator {
id: decorationExpressionEvaluator
mode: ExpressionEvaluator.ExpressionTemplateMode
mapSettings: mapCanvas.mapSettings
project: qgisProject
positionInformation: positionSource.positionInformation
cloudUserInformation: projectInfo.cloudUserInformation
}

Connections {
target: mapCanvasMap
enabled: titleDecoration.isExpressionTemplate || copyrightDecoration.isExpressionTemplate

function onIsRenderingChanged() {
if (mapCanvasMap.isRendering) {
if (titleDecoration.isExpressionTemplate) {
decorationExpressionEvaluator.expressionText = titleDecoration.decorationText;
titleDecoration.text = decorationExpressionEvaluator.evaluate();
}
if (copyrightDecoration.isExpressionTemplate) {
decorationExpressionEvaluator.expressionText = copyrightDecoration.decorationText;
copyrightDecoration.text = decorationExpressionEvaluator.evaluate();
}
}
}
}

Connections {
target: positionSource
enabled: titleDecoration.isExpressionPositioning || copyrightDecoration.isExpressionPositioning

function onPositionInformationChanged() {
if (titleDecoration.isExpressionPositioning) {
decorationExpressionEvaluator.expressionText = titleDecoration.decorationText;
titleDecoration.text = decorationExpressionEvaluator.evaluate();
}
if (copyrightDecoration.isExpressionPositioning) {
decorationExpressionEvaluator.expressionText = copyrightDecoration.decorationText;
copyrightDecoration.text = decorationExpressionEvaluator.evaluate();
}
}
}

Rectangle {
id: titleDecorationBackground

visible: titleDecoration.text != ''

anchors.left: parent.left
anchors.leftMargin: 56
anchors.top: parent.top
Expand All @@ -936,6 +978,10 @@ ApplicationWindow {
Text {
id: titleDecoration

property string decorationText: ''
property bool isExpressionTemplate: decorationText.match('\[%.*%\]')
property bool isExpressionPositioning: isExpressionTemplate && decorationText.match('\[%.*(@gnss_|@position_).*%\]')

width: parent.width - 4
height: parent.height
leftPadding: 2
Expand Down Expand Up @@ -973,6 +1019,10 @@ ApplicationWindow {
Text {
id: copyrightDecoration

property string decorationText: ''
property bool isExpressionTemplate: decorationText.match('\[%.*%\]')
property bool isExpressionPositioning: isExpressionTemplate && decorationText.match('\[%.*(@gnss_|@position_).*%\]')

anchors.bottom: parent.bottom

width: parent.width - 4
Expand Down Expand Up @@ -3314,19 +3364,25 @@ ApplicationWindow {
dashBoard.activeLayer = activeLayer;
drawingTemplateModel.projectFilePath = path;
mapCanvasBackground.color = mapCanvas.mapSettings.backgroundColor;
var titleDecorationConfiguration = projectInfo.getTitleDecorationConfiguration();
titleDecoration.text = titleDecorationConfiguration["text"];
let titleDecorationConfiguration = projectInfo.getTitleDecorationConfiguration();
titleDecoration.color = titleDecorationConfiguration["color"];
titleDecoration.style = titleDecorationConfiguration["hasOutline"] === true ? Text.Outline : Text.Normal;
titleDecoration.styleColor = titleDecorationConfiguration["outlineColor"];
titleDecorationBackground.color = titleDecorationConfiguration["backgroundColor"];
var copyrightDecorationConfiguration = projectInfo.getCopyrightDecorationConfiguration();
copyrightDecoration.text = copyrightDecorationConfiguration["text"];
titleDecoration.decorationText = titleDecorationConfiguration["text"];
if (!titleDecoration.isExpressionTemplate) {
titleDecoration.text = titleDecorationConfiguration["text"];
}
let copyrightDecorationConfiguration = projectInfo.getCopyrightDecorationConfiguration();
copyrightDecoration.color = copyrightDecorationConfiguration["color"];
copyrightDecoration.style = copyrightDecorationConfiguration["hasOutline"] === true ? Text.Outline : Text.Normal;
copyrightDecoration.styleColor = copyrightDecorationConfiguration["outlineColor"];
copyrightDecorationBackground.color = copyrightDecorationConfiguration["backgroundColor"];
var imageDecorationConfiguration = projectInfo.getImageDecorationConfiguration();
copyrightDecoration.decorationText = copyrightDecorationConfiguration["text"];
if (!titleDecoration.isExpressionTemplate) {
copyrightDecoration.text = copyrightDecorationConfiguration["text"];
}
let imageDecorationConfiguration = projectInfo.getImageDecorationConfiguration();
imageDecoration.source = imageDecorationConfiguration["source"];
imageDecoration.fillColor = imageDecorationConfiguration["fillColor"];
imageDecoration.strokeColor = imageDecorationConfiguration["strokeColor"];
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@ ADD_CATCH2_TEST(digitizingloggertest test_digitizinglogger.cpp FALSE)
ADD_CATCH2_TEST(attributeformmodeltest test_attributeformmodel.cpp FALSE)
ADD_CATCH2_TEST(orderedrelationmodeltest test_orderedrelationmodel.cpp FALSE)
ADD_CATCH2_TEST(referencingfeaturelistmodeltest test_referencingfeaturelistmodel.cpp FALSE)
ADD_CATCH2_TEST(expressionevaluatortest test_expressionevaluator.cpp TRUE)

ADD_QFIELD_QML_TEST(qmltest test_qml.cpp)
Loading

1 comment on commit 8661c38

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.