Skip to content

Commit ada9ce4

Browse files
authoredJul 8, 2017
Merge pull request #4825
Various new expression functions and possibilities
2 parents 6b6a52b + 89a06f6 commit ada9ce4

26 files changed

+402
-75
lines changed
 

‎python/core/expression/qgsexpressionfunction.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ The help text for the function.
249249
:rtype: QVariant
250250
%End
251251

252+
virtual QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent );
253+
%Docstring
254+
:rtype: QVariant
255+
%End
256+
252257
bool operator==( const QgsExpressionFunction &other ) const;
253258

254259
virtual bool handlesNull() const;

‎python/core/geometry/qgscompoundcurve.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class QgsCompoundCurve: QgsCurve
7777

7878
void addCurve( QgsCurve *c /Transfer/ );
7979
%Docstring
80-
Adds a curve to the geometr (takes ownership)
80+
Adds a curve to the geometry (takes ownership)
8181
%End
8282

8383
void removeCurve( int i );

‎python/core/qgsexpressioncontext.sip

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,15 @@ class QgsExpressionContextUtils
809809
:rtype: QgsExpressionContextScope
810810
%End
811811

812+
static QgsExpressionContextScope *mapToolCaptureScope( const QList<QgsPointLocator::Match> &matches ) /Factory/;
813+
%Docstring
814+
Sets the expression context variables which are available for expressions triggered by
815+
a map tool capture like add feature.
816+
817+
.. versionadded:: 3.0
818+
:rtype: QgsExpressionContextScope
819+
%End
820+
812821
static QgsExpressionContextScope *updateSymbolScope( const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope = 0 );
813822
%Docstring
814823
Updates a symbol scope related to a QgsSymbol to an expression context.

‎python/gui/qgsmaptoolcapture.sip

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ Adds a whole curve (e.g. circularstring) to the captured geometry. Curve must be
4242
:rtype: QgsCompoundCurve
4343
%End
4444

45+
QList<QgsPointLocator::Match> snappingMatches() const;
46+
%Docstring
47+
Return a list of matches for each point on the captureCurve.
48+
49+
.. versionadded:: 3.0
50+
:rtype: list of QgsPointLocator.Match
51+
%End
52+
4553
virtual void cadCanvasMoveEvent( QgsMapMouseEvent *e );
4654

4755
virtual void keyPressEvent( QKeyEvent *e );
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "array_first",
3+
"type": "function",
4+
"description": "Returns the first value of an array.",
5+
"arguments": [ {"arg":"array","description":"an array"} ],
6+
"examples": [ { "expression":"array_first(array('a','b','c'))", "returns":"'a'"}]
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "array_last",
3+
"type": "function",
4+
"description": "Returns the last value of an array.",
5+
"arguments": [ {"arg":"array","description":"an array"} ],
6+
"examples": [ { "expression":"array_last(array('a','b','c'))", "returns":"'c'"}]
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "get_feature_by_id",
3+
"type": "function",
4+
"description": "Returns the feature with an id on a layer.",
5+
"arguments": [ {"arg":"layer","description":"layer, layer name or layer id"},
6+
{"arg":"feature_id","description":"the id of the feature which should be returned"}],
7+
"examples": [ { "expression":"get_feature('streets', 1)", "returns":"the feature with the id 1 on the layer \"streets\""}]
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "with_variable",
3+
"type": "function",
4+
"description": "This function sets a variable for any expression code that will be provided as 3rd argument. This is only useful for complicated expressions, where the same calculated value needs to be used in different places.",
5+
"arguments": [
6+
{"arg":"name","description":"the name of the variable to set"},
7+
{"arg":"value","description":"the value to set"},
8+
{"arg":"node","description":"the expression for which the variable will be available"}
9+
],
10+
"examples": [ { "expression":"with_variable('my_sum', 1 + 2 + 3, @my_sum * 2 + @my_sum * 5)", "returns":"42"}]
11+
}

‎src/app/qgsattributetypedialog.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx
7171
isFieldEditableCheckBox->setEnabled( false );
7272
}
7373

74+
mExpressionWidget->registerExpressionContextGenerator( this );
75+
7476
connect( mExpressionWidget, &QgsExpressionLineEdit::expressionChanged, this, &QgsAttributeTypeDialog::defaultExpressionChanged );
7577
connect( mUniqueCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
7678
{
@@ -293,6 +295,18 @@ void QgsAttributeTypeDialog::setDefaultValueExpression( const QString &expressio
293295
mExpressionWidget->setExpression( expression );
294296
}
295297

298+
QgsExpressionContext QgsAttributeTypeDialog::createExpressionContext() const
299+
{
300+
QgsExpressionContext context;
301+
context
302+
<< QgsExpressionContextUtils::globalScope()
303+
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
304+
<< QgsExpressionContextUtils::layerScope( mLayer )
305+
<< QgsExpressionContextUtils::mapToolCaptureScope( QList<QgsPointLocator::Match>() );
306+
307+
return context;
308+
}
309+
296310
QString QgsAttributeTypeDialog::constraintExpression() const
297311
{
298312
return constraintExpressionWidget->asExpression();

‎src/app/qgsattributetypedialog.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
class QDialog;
2828

29-
class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttributeTypeDialog
29+
class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttributeTypeDialog, QgsExpressionContextGenerator
3030
{
3131
Q_OBJECT
3232

@@ -163,6 +163,8 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
163163
*/
164164
void setDefaultValueExpression( const QString &expression );
165165

166+
QgsExpressionContext createExpressionContext() const override;
167+
166168
private slots:
167169

168170
/**

‎src/app/qgsfeatureaction.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ bool QgsFeatureAction::editFeature( bool showModal )
161161
return true;
162162
}
163163

164-
bool QgsFeatureAction::addFeature( const QgsAttributeMap &defaultAttributes, bool showModal )
164+
bool QgsFeatureAction::addFeature( const QgsAttributeMap &defaultAttributes, bool showModal, QgsExpressionContextScope *scope SIP_TRANSFER )
165165
{
166166
if ( !mLayer || !mLayer->isEditable() )
167167
return false;
@@ -188,6 +188,9 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap &defaultAttributes, boo
188188
// create new feature template - this will initialize the attributes to valid values, handling default
189189
// values and field constraints
190190
QgsExpressionContext context = mLayer->createExpressionContext();
191+
if ( scope )
192+
context.appendScope( scope );
193+
191194
QgsFeature newFeature = QgsVectorLayerUtils::createFeature( mLayer, mFeature->geometry(), initialAttributeValues,
192195
&context );
193196
*mFeature = newFeature;

‎src/app/qgsfeatureaction.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class QgsIdentifyResultsDialog;
2929
class QgsVectorLayer;
3030
class QgsHighlight;
3131
class QgsAttributeDialog;
32+
class QgsExpressionContextScope;
3233

3334
class APP_EXPORT QgsFeatureAction : public QAction
3435
{
@@ -51,7 +52,7 @@ class APP_EXPORT QgsFeatureAction : public QAction
5152
*
5253
* \returns true if feature was added if showModal is true. If showModal is false, returns true in every case
5354
*/
54-
bool addFeature( const QgsAttributeMap &defaultAttributes = QgsAttributeMap(), bool showModal = true );
55+
bool addFeature( const QgsAttributeMap &defaultAttributes = QgsAttributeMap(), bool showModal = true, QgsExpressionContextScope *scope = nullptr );
5556

5657
private slots:
5758
void onFeatureSaved( const QgsFeature &feature );

‎src/app/qgsmaptooladdfeature.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ QgsMapToolAddFeature::~QgsMapToolAddFeature()
4848

4949
bool QgsMapToolAddFeature::addFeature( QgsVectorLayer *vlayer, QgsFeature *f, bool showModal )
5050
{
51+
QgsExpressionContextScope *scope = QgsExpressionContextUtils::mapToolCaptureScope( snappingMatches() );
5152
QgsFeatureAction *action = new QgsFeatureAction( tr( "add feature" ), *f, vlayer, QString(), -1, this );
52-
bool res = action->addFeature( QgsAttributeMap(), showModal );
53+
bool res = action->addFeature( QgsAttributeMap(), showModal, scope );
5354
if ( showModal )
5455
delete action;
5556
return res;
@@ -250,6 +251,7 @@ void QgsMapToolAddFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
250251
bool hasCurvedSegments = captureCurve()->hasCurvedSegments();
251252
bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::CircularGeometries;
252253

254+
QList<QgsPointLocator::Match> snappingMatchesList;
253255
QgsCurve *curveToAdd = nullptr;
254256
if ( hasCurvedSegments && providerSupportsCurvedSegments )
255257
{
@@ -258,13 +260,13 @@ void QgsMapToolAddFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
258260
else
259261
{
260262
curveToAdd = captureCurve()->curveToLine();
263+
snappingMatchesList = snappingMatches();
261264
}
262265

263266
if ( mode() == CaptureLine )
264267
{
265-
QgsGeometry *g = new QgsGeometry( curveToAdd );
266-
f->setGeometry( *g );
267-
delete g;
268+
QgsGeometry g( curveToAdd );
269+
f->setGeometry( g );
268270
}
269271
else
270272
{
@@ -278,9 +280,8 @@ void QgsMapToolAddFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
278280
poly = new QgsPolygonV2();
279281
}
280282
poly->setExteriorRing( curveToAdd );
281-
QgsGeometry *g = new QgsGeometry( poly );
282-
f->setGeometry( *g );
283-
delete g;
283+
QgsGeometry g( poly );
284+
f->setGeometry( g );
284285

285286
QgsGeometry featGeom = f->geometry();
286287
int avoidIntersectionsReturn = featGeom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() );

‎src/app/qgsmaptooladdfeature.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class APP_EXPORT QgsMapToolAddFeature : public QgsMapToolCapture
4444
void setCheckGeometryType( bool checkGeometryType );
4545

4646
private:
47-
QVariant snappingMatchesAsVariable() const;
4847

4948
/** Check if CaptureMode matches layer type. Default is true.
5049
* \since QGIS 2.12 */

‎src/core/expression/qgsexpression.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,19 @@ void QgsExpression::initVariableHelp()
673673
sVariableHelpTexts.insert( QStringLiteral( "grid_number" ), QCoreApplication::translate( "variable_help", "Current grid annotation value." ) );
674674
sVariableHelpTexts.insert( QStringLiteral( "grid_axis" ), QCoreApplication::translate( "variable_help", "Current grid annotation axis (e.g., 'x' for longitude, 'y' for latitude)." ) );
675675

676+
// map tool capture variables
677+
sVariableHelpTexts.insert( QStringLiteral( "snapping_results" ), QCoreApplication::translate( "variable_help",
678+
"<p>An array with an item for each snapped point.</p>"
679+
"<p>Each item is a map with the following keys:</p>"
680+
"<dl>"
681+
"<dt>valid</dt><dd>Boolean that indicates if the snapping result is valid</dd>"
682+
"<dt>layer</dt><dd>The layer on which the snapped feature is</dd>"
683+
"<dt>feature_id</dt><dd>The feature id of the snapped feature</dd>"
684+
"<dt>vertex_index</dt><dd>The index of the snapped vertex</dd>"
685+
"<dt>distance</dt><dd>The distance between the mouse cursor and the snapped point at the time of snapping</dd>"
686+
"</dl>" ) );
687+
688+
676689
//symbol variables
677690
sVariableHelpTexts.insert( QStringLiteral( "geometry_part_count" ), QCoreApplication::translate( "variable_help", "Number of parts in rendered feature's geometry." ) );
678691
sVariableHelpTexts.insert( QStringLiteral( "geometry_part_num" ), QCoreApplication::translate( "variable_help", "Current geometry part number for feature being rendered." ) );

‎src/core/expression/qgsexpressionfunction.cpp

Lines changed: 165 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,34 @@ const QString QgsExpressionFunction::helpText() const
4747
return mHelpText.isEmpty() ? QgsExpression::helpText( mName ) : mHelpText;
4848
}
4949

50+
QVariant QgsExpressionFunction::run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent )
51+
{
52+
// evaluate arguments
53+
QVariantList argValues;
54+
if ( args )
55+
{
56+
Q_FOREACH ( QgsExpressionNode *n, args->list() )
57+
{
58+
QVariant v;
59+
if ( lazyEval() )
60+
{
61+
// Pass in the node for the function to eval as it needs.
62+
v = QVariant::fromValue( n );
63+
}
64+
else
65+
{
66+
v = n->eval( parent, context );
67+
ENSURE_NO_EVAL_ERROR;
68+
if ( QgsExpressionUtils::isNull( v ) && !handlesNull() )
69+
return QVariant(); // all "normal" functions return NULL, when any QgsExpressionFunction::Parameter is NULL (so coalesce is abnormal)
70+
}
71+
argValues.append( v );
72+
}
73+
}
74+
75+
return func( argValues, context, parent );
76+
}
77+
5078
bool QgsExpressionFunction::usesGeometry( const QgsExpressionNodeFunction *node ) const
5179
{
5280
Q_UNUSED( node )
@@ -3287,6 +3315,26 @@ static QVariant fcnTransformGeometry( const QVariantList &values, const QgsExpre
32873315
}
32883316

32893317

3318+
static QVariant fcnGetFeatureById( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
3319+
{
3320+
QVariant result;
3321+
QgsVectorLayer *vl = QgsExpressionUtils::getVectorLayer( values.at( 0 ), parent );
3322+
if ( vl )
3323+
{
3324+
QgsFeatureId fid = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
3325+
3326+
QgsFeatureRequest req;
3327+
req.setFilterFid( fid );
3328+
QgsFeatureIterator fIt = vl->getFeatures( req );
3329+
3330+
QgsFeature fet;
3331+
if ( fIt.nextFeature( fet ) )
3332+
result = QVariant::fromValue( fet );
3333+
}
3334+
3335+
return result;
3336+
}
3337+
32903338
static QVariant fcnGetFeature( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
32913339
{
32923340
//arguments: 1. layer id / name, 2. key attribute, 3. eq value
@@ -3325,18 +3373,7 @@ static QVariant fcnGetFeature( const QVariantList &values, const QgsExpressionCo
33253373

33263374
static QVariant fcnGetLayerProperty( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
33273375
{
3328-
QString layerIdOrName = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
3329-
3330-
//try to find a matching layer by name
3331-
QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIdOrName ); //search by id first
3332-
if ( !layer )
3333-
{
3334-
QList<QgsMapLayer *> layersByName = QgsProject::instance()->mapLayersByName( layerIdOrName );
3335-
if ( !layersByName.isEmpty() )
3336-
{
3337-
layer = layersByName.at( 0 );
3338-
}
3339-
}
3376+
QgsMapLayer *layer = QgsExpressionUtils::getMapLayer( values.at( 0 ), parent );
33403377

33413378
if ( !layer )
33423379
return QVariant();
@@ -3501,6 +3538,18 @@ static QVariant fcnArrayGet( const QVariantList &values, const QgsExpressionCont
35013538
return list.at( pos );
35023539
}
35033540

3541+
static QVariant fcnArrayFirst( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
3542+
{
3543+
const QVariantList list = QgsExpressionUtils::getListValue( values.at( 0 ), parent );
3544+
return list.value( 0 );
3545+
}
3546+
3547+
static QVariant fcnArrayLast( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
3548+
{
3549+
const QVariantList list = QgsExpressionUtils::getListValue( values.at( 0 ), parent );
3550+
return list.value( list.size() - 1 );
3551+
}
3552+
35043553
static QVariant convertToSameType( const QVariant &value, QVariant::Type type )
35053554
{
35063555
QVariant result = value;
@@ -4104,7 +4153,8 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
41044153
sFunctions << uuidFunc;
41054154

41064155
sFunctions
4107-
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature" ), 3, fcnGetFeature, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "QgsExpressionUtils::getFeature" ) );
4156+
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature" ), 3, fcnGetFeature, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "QgsExpressionUtils::getFeature" ) )
4157+
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature_by_id" ), 2, fcnGetFeatureById, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false );
41084158

41094159
QgsStaticExpressionFunction *isSelectedFunc = new QgsStaticExpressionFunction(
41104160
QStringLiteral( "is_selected" ),
@@ -4190,6 +4240,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
41904240

41914241
sFunctions
41924242
<< new QgsStaticExpressionFunction( QStringLiteral( "env" ), 1, fcnEnvVar, QStringLiteral( "General" ), QString() )
4243+
<< new QgsWithVariableExpressionFunction()
41934244
<< new QgsStaticExpressionFunction( QStringLiteral( "attribute" ), 2, fcnAttribute, QStringLiteral( "Record" ), QString(), false, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES )
41944245

41954246
// functions for arrays
@@ -4198,6 +4249,8 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
41984249
<< new QgsStaticExpressionFunction( QStringLiteral( "array_contains" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayContains, QStringLiteral( "Arrays" ) )
41994250
<< new QgsStaticExpressionFunction( QStringLiteral( "array_find" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayFind, QStringLiteral( "Arrays" ) )
42004251
<< new QgsStaticExpressionFunction( QStringLiteral( "array_get" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "pos" ) ), fcnArrayGet, QStringLiteral( "Arrays" ) )
4252+
<< new QgsStaticExpressionFunction( QStringLiteral( "array_first" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ), fcnArrayFirst, QStringLiteral( "Arrays" ) )
4253+
<< new QgsStaticExpressionFunction( QStringLiteral( "array_last" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ), fcnArrayLast, QStringLiteral( "Arrays" ) )
42014254
<< new QgsStaticExpressionFunction( QStringLiteral( "array_append" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayAppend, QStringLiteral( "Arrays" ) )
42024255
<< new QgsStaticExpressionFunction( QStringLiteral( "array_prepend" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayPrepend, QStringLiteral( "Arrays" ) )
42034256
<< new QgsStaticExpressionFunction( QStringLiteral( "array_insert" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "array" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "pos" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnArrayInsert, QStringLiteral( "Arrays" ) )
@@ -4232,3 +4285,102 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
42324285
}
42334286
return sFunctions;
42344287
}
4288+
4289+
QgsWithVariableExpressionFunction::QgsWithVariableExpressionFunction()
4290+
: QgsExpressionFunction( QStringLiteral( "with_variable" ), 3, QCoreApplication::tr( "General" ) )
4291+
{
4292+
4293+
}
4294+
4295+
bool QgsWithVariableExpressionFunction::isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const
4296+
{
4297+
bool isStatic = false;
4298+
4299+
QgsExpressionNode::NodeList *args = node->args();
4300+
4301+
if ( args->count() < 3 )
4302+
return false;
4303+
4304+
// We only need to check if the node evaluation is static, if both - name and value - are static.
4305+
if ( args->at( 0 )->isStatic( parent, context ) && args->at( 1 )->isStatic( parent, context ) )
4306+
{
4307+
QVariant name = args->at( 0 )->eval( parent, context );
4308+
QVariant value = args->at( 1 )->eval( parent, context );
4309+
4310+
// Temporarily append a new scope to provide the variable
4311+
appendTemporaryVariable( context, name.toString(), value );
4312+
if ( args->at( 2 )->isStatic( parent, context ) )
4313+
isStatic = true;
4314+
popTemporaryVariable( context );
4315+
}
4316+
4317+
return false;
4318+
}
4319+
4320+
QVariant QgsWithVariableExpressionFunction::run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent )
4321+
{
4322+
QVariant result;
4323+
4324+
if ( args->count() < 3 )
4325+
// error
4326+
return result;
4327+
4328+
QVariant name = args->at( 0 )->eval( parent, context );
4329+
QVariant value = args->at( 1 )->eval( parent, context );
4330+
4331+
QgsExpressionContext *updatedContext = const_cast<QgsExpressionContext *>( context );
4332+
if ( !context )
4333+
updatedContext = new QgsExpressionContext();
4334+
4335+
appendTemporaryVariable( updatedContext, name.toString(), value );
4336+
result = args->at( 2 )->eval( parent, updatedContext );
4337+
popTemporaryVariable( updatedContext );
4338+
if ( !context )
4339+
delete updatedContext;
4340+
4341+
return result;
4342+
}
4343+
4344+
QVariant QgsWithVariableExpressionFunction::func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent )
4345+
{
4346+
// This is a dummy function, all the real handling is in run
4347+
Q_UNUSED( values )
4348+
Q_UNUSED( context )
4349+
Q_UNUSED( parent )
4350+
4351+
Q_ASSERT( false );
4352+
return QVariant();
4353+
}
4354+
4355+
bool QgsWithVariableExpressionFunction::prepare( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const
4356+
{
4357+
QgsExpressionNode::NodeList *args = node->args();
4358+
4359+
if ( args->count() < 3 )
4360+
// error
4361+
return false;
4362+
4363+
QVariant name = args->at( 0 )->prepare( parent, context );
4364+
QVariant value = args->at( 1 )->prepare( parent, context );
4365+
4366+
appendTemporaryVariable( context, name.toString(), value );
4367+
args->at( 2 )->prepare( parent, context );
4368+
popTemporaryVariable( context );
4369+
4370+
return true;
4371+
}
4372+
4373+
void QgsWithVariableExpressionFunction::popTemporaryVariable( const QgsExpressionContext *context ) const
4374+
{
4375+
QgsExpressionContext *updatedContext = const_cast<QgsExpressionContext *>( context );
4376+
delete updatedContext->popScope();
4377+
}
4378+
4379+
void QgsWithVariableExpressionFunction::appendTemporaryVariable( const QgsExpressionContext *context, const QString &name, const QVariant &value ) const
4380+
{
4381+
QgsExpressionContextScope *scope = new QgsExpressionContextScope();
4382+
scope->setVariable( name, value );
4383+
4384+
QgsExpressionContext *updatedContext = const_cast<QgsExpressionContext *>( context );
4385+
updatedContext->appendScope( scope );
4386+
}

‎src/core/expression/qgsexpressionfunction.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424

2525
#include "qgis.h"
2626
#include "qgis_core.h"
27+
#include "qgsexpressionnode.h"
2728

2829
class QgsExpressionNodeFunction;
2930
class QgsExpression;
3031
class QgsExpressionContext;
32+
class QgsExpressionContextScope;
3133

3234
/** \ingroup core
3335
* A abstract base class for defining QgsExpression functions.
@@ -273,6 +275,8 @@ class CORE_EXPORT QgsExpressionFunction
273275
*/
274276
virtual QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) = 0;
275277

278+
virtual QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent );
279+
276280
bool operator==( const QgsExpressionFunction &other ) const;
277281

278282
virtual bool handlesNull() const;
@@ -451,6 +455,41 @@ class QgsStaticExpressionFunction : public QgsExpressionFunction
451455
QSet<QString> mReferencedColumns;
452456
bool mIsStatic = false;
453457
};
458+
459+
/**
460+
* Handles the ``with_variable(name, value, node)`` expression function.
461+
* It temporarily appends a new scope to the expression context for all nested
462+
* nodes.
463+
*
464+
* \note Not available in Python bindings
465+
* \since QGIS 3.0
466+
*/
467+
class QgsWithVariableExpressionFunction : public QgsExpressionFunction
468+
{
469+
public:
470+
QgsWithVariableExpressionFunction();
471+
472+
bool isStatic( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const override;
473+
474+
QVariant run( QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent ) override;
475+
476+
QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) override;
477+
478+
bool prepare( const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context ) const override;
479+
480+
private:
481+
482+
/**
483+
* Append a scope with a single variable definition (``name``=``value``)
484+
*/
485+
void appendTemporaryVariable( const QgsExpressionContext *context, const QString &name, const QVariant &value ) const;
486+
487+
/**
488+
* Pop the temporary scope again
489+
*/
490+
void popTemporaryVariable( const QgsExpressionContext *context ) const;
491+
};
492+
454493
#endif
455494

456495
#endif // QGSEXPRESSIONFUNCTION_H

‎src/core/expression/qgsexpressionnodeimpl.cpp

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -842,31 +842,7 @@ QVariant QgsExpressionNodeFunction::evalNode( QgsExpression *parent, const QgsEx
842842
QString name = QgsExpression::QgsExpression::Functions()[mFnIndex]->name();
843843
QgsExpressionFunction *fd = context && context->hasFunction( name ) ? context->function( name ) : QgsExpression::QgsExpression::Functions()[mFnIndex];
844844

845-
// evaluate arguments
846-
QVariantList argValues;
847-
if ( mArgs )
848-
{
849-
Q_FOREACH ( QgsExpressionNode *n, mArgs->list() )
850-
{
851-
QVariant v;
852-
if ( fd->lazyEval() )
853-
{
854-
// Pass in the node for the function to eval as it needs.
855-
v = QVariant::fromValue( n );
856-
}
857-
else
858-
{
859-
v = n->eval( parent, context );
860-
ENSURE_NO_EVAL_ERROR;
861-
if ( QgsExpressionUtils::isNull( v ) && !fd->handlesNull() )
862-
return QVariant(); // all "normal" functions return NULL, when any QgsExpressionFunction::Parameter is NULL (so coalesce is abnormal)
863-
}
864-
argValues.append( v );
865-
}
866-
}
867-
868-
// run the function
869-
QVariant res = fd->func( argValues, context, parent );
845+
QVariant res = fd->run( mArgs, context, parent );
870846
ENSURE_NO_EVAL_ERROR;
871847

872848
// everything went fine

‎src/core/expression/qgsexpressionutils.h

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -333,26 +333,26 @@ class QgsExpressionUtils
333333
return nullptr;
334334
}
335335

336-
static QgsVectorLayer *getVectorLayer( const QVariant &value, QgsExpression * )
336+
static QgsMapLayer *getMapLayer( const QVariant &value, QgsExpression * )
337337
{
338+
// First check if we already received a layer pointer
338339
QgsMapLayer *ml = value.value< QgsWeakMapLayerPointer >().data();
339-
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
340-
if ( !vl )
341-
{
342-
QString layerString = value.toString();
343-
vl = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerString ) ); //search by id first
340+
QgsProject *project = QgsProject::instance();
344341

345-
if ( !vl )
346-
{
347-
QList<QgsMapLayer *> layersByName = QgsProject::instance()->mapLayersByName( layerString );
348-
if ( !layersByName.isEmpty() )
349-
{
350-
vl = qobject_cast<QgsVectorLayer *>( layersByName.at( 0 ) );
351-
}
352-
}
353-
}
342+
// No pointer yet, maybe it's a layer id?
343+
if ( !ml )
344+
ml = project->mapLayer( value.toString() );
354345

355-
return vl;
346+
// Still nothing? Check for layer name
347+
if ( !ml )
348+
ml = project->mapLayersByName( value.toString() ).value( 0 );
349+
350+
return ml;
351+
}
352+
353+
static QgsVectorLayer *getVectorLayer( const QVariant &value, QgsExpression *e )
354+
{
355+
return qobject_cast<QgsVectorLayer *>( getMapLayer( value, e ) );
356356
}
357357

358358

‎src/core/geometry/qgscompoundcurve.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
7474
*/
7575
const QgsCurve *curveAt( int i ) const;
7676

77-
/** Adds a curve to the geometr (takes ownership)
77+
/** Adds a curve to the geometry (takes ownership)
7878
*/
7979
void addCurve( QgsCurve *c SIP_TRANSFER );
8080

‎src/core/qgsexpressioncontext.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,30 @@ QgsExpressionContextScope *QgsExpressionContextUtils::mapSettingsScope( const Qg
912912
return scope;
913913
}
914914

915+
QgsExpressionContextScope *QgsExpressionContextUtils::mapToolCaptureScope( const QList<QgsPointLocator::Match> &matches )
916+
{
917+
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Map Tool Capture" ) );
918+
919+
QVariantList matchList;
920+
921+
for ( const QgsPointLocator::Match &match : matches )
922+
{
923+
QVariantMap matchMap;
924+
925+
matchMap.insert( QStringLiteral( "valid" ), match.isValid() );
926+
matchMap.insert( QStringLiteral( "layer" ), QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( match.layer() ) ) );
927+
matchMap.insert( QStringLiteral( "feature_id" ), match.featureId() );
928+
matchMap.insert( QStringLiteral( "vertex_index" ), match.vertexIndex() );
929+
matchMap.insert( QStringLiteral( "distance" ), match.distance() );
930+
931+
matchList.append( matchMap );
932+
}
933+
934+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "snapping_results" ), matchList ) );
935+
936+
return scope;
937+
}
938+
915939
QgsExpressionContextScope *QgsExpressionContextUtils::updateSymbolScope( const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope )
916940
{
917941
if ( !symbolScope )

‎src/core/qgsexpressioncontext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "qgsfeature.h"
2727
#include "qgsexpression.h"
2828
#include "qgsexpressionfunction.h"
29+
#include "qgspointlocator.h"
2930

3031
class QgsExpression;
3132
class QgsExpressionNodeFunction;
@@ -743,6 +744,14 @@ class CORE_EXPORT QgsExpressionContextUtils
743744
*/
744745
static QgsExpressionContextScope *mapSettingsScope( const QgsMapSettings &mapSettings ) SIP_FACTORY;
745746

747+
/**
748+
* Sets the expression context variables which are available for expressions triggered by
749+
* a map tool capture like add feature.
750+
*
751+
* \since QGIS 3.0
752+
*/
753+
static QgsExpressionContextScope *mapToolCaptureScope( const QList<QgsPointLocator::Match> &matches ) SIP_FACTORY;
754+
746755
/**
747756
* Updates a symbol scope related to a QgsSymbol to an expression context.
748757
* \param symbol symbol to extract properties from

‎src/core/qgssnappingutils.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,21 +164,21 @@ static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY
164164
}
165165

166166

167-
static void _replaceIfBetter( QgsPointLocator::Match &mBest, const QgsPointLocator::Match &mNew, double maxDistance )
167+
static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
168168
{
169-
// is other match relevant?
170-
if ( !mNew.isValid() || mNew.distance() > maxDistance )
169+
// is candidate match relevant?
170+
if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
171171
return;
172172

173-
// is other match actually better?
174-
if ( mBest.isValid() && mBest.type() == mNew.type() && mBest.distance() - 10e-6 < mNew.distance() )
173+
// is candidate match actually better?
174+
if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
175175
return;
176176

177-
// prefer vertex matches to edge matches (even if they are closer)
178-
if ( mBest.type() == QgsPointLocator::Vertex && mNew.type() == QgsPointLocator::Edge )
177+
// prefer vertex matches over edge matches (even if they are closer)
178+
if ( bestMatch.type() == QgsPointLocator::Vertex && candidateMatch.type() == QgsPointLocator::Edge )
179179
return;
180180

181-
mBest = mNew; // the other match is better!
181+
bestMatch = candidateMatch; // the other match is better!
182182
}
183183

184184

‎src/gui/qgsmaptoolcapture.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
222222

223223
mRubberBand->addPoint( point );
224224
mCaptureCurve.addVertex( layerPoint );
225+
mSnappingMatches.append( QgsPointLocator::Match() );
225226
}
226227
return res;
227228
}
@@ -251,6 +252,7 @@ bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
251252
continue; // avoid duplicate vertices if there are any
252253
mRubberBand->addPoint( points[i], i == points.count() - 1 );
253254
mCaptureCurve.addVertex( layerPoints[i - 1] );
255+
mSnappingMatches.append( QgsPointLocator::Match() );
254256
}
255257

256258
tracer->reportError( QgsTracer::ErrNone, true ); // clear messagebar if there was any error
@@ -433,6 +435,7 @@ int QgsMapToolCapture::addVertex( const QgsPointXY &point, const QgsPointLocator
433435
// ordinary digitizing
434436
mRubberBand->addPoint( point );
435437
mCaptureCurve.addVertex( layerPoint );
438+
mSnappingMatches.append( match );
436439
}
437440

438441
if ( mCaptureMode == CaptureLine )
@@ -493,10 +496,17 @@ int QgsMapToolCapture::addCurve( QgsCurve *c )
493496
c->transform( ct, QgsCoordinateTransform::ReverseTransform );
494497
}
495498
mCaptureCurve.addCurve( c );
499+
for ( int i = 0; i < c->length(); ++i )
500+
mSnappingMatches.append( QgsPointLocator::Match() );
496501

497502
return 0;
498503
}
499504

505+
QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
506+
{
507+
return mSnappingMatches;
508+
}
509+
500510

501511
void QgsMapToolCapture::undo()
502512
{
@@ -531,6 +541,7 @@ void QgsMapToolCapture::undo()
531541
vertexToRemove.ring = 0;
532542
vertexToRemove.vertex = size() - 1;
533543
mCaptureCurve.deleteVertex( vertexToRemove );
544+
mSnappingMatches.removeAt( vertexToRemove.vertex );
534545

535546
validateGeometry();
536547
}
@@ -599,6 +610,7 @@ void QgsMapToolCapture::stopCapturing()
599610

600611
mCapturing = false;
601612
mCaptureCurve.clear();
613+
mSnappingMatches.clear();
602614
if ( currentVectorLayer() )
603615
currentVectorLayer()->triggerRepaint();
604616
}
@@ -717,6 +729,9 @@ void QgsMapToolCapture::setPoints( const QList<QgsPointXY> &pointList )
717729
QgsLineString *line = new QgsLineString( pointList );
718730
mCaptureCurve.clear();
719731
mCaptureCurve.addCurve( line );
732+
mSnappingMatches.clear();
733+
for ( int i = 0; i < line->length(); ++i )
734+
mSnappingMatches.append( QgsPointLocator::Match() );
720735
}
721736

722737
#ifdef Q_OS_WIN

‎src/gui/qgsmaptoolcapture.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
5656
*/
5757
const QgsCompoundCurve *captureCurve() const { return &mCaptureCurve; }
5858

59+
/**
60+
* Return a list of matches for each point on the captureCurve.
61+
*
62+
* \since QGIS 3.0
63+
*/
64+
QList<QgsPointLocator::Match> snappingMatches() const;
65+
5966
virtual void cadCanvasMoveEvent( QgsMapMouseEvent *e ) override;
6067

6168
/**
@@ -193,6 +200,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
193200
//! List to store the points of digitized lines and polygons (in layer coordinates)
194201
QgsCompoundCurve mCaptureCurve;
195202

203+
QList<QgsPointLocator::Match> mSnappingMatches;
204+
196205
void validateGeometry();
197206
QStringList mValidationWarnings;
198207
QgsGeometryValidator *mValidator = nullptr;

‎tests/src/core/testqgsexpression.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,18 @@ class TestQgsExpression: public QObject
11231123
// not like
11241124
QTest::newRow( "'a' not like 'a%'" ) << QStringLiteral( "'a' not like 'a%'" ) << false << QVariant( 0 );
11251125
QTest::newRow( "'a' not like 'a%'" ) << QStringLiteral( "'a' not like 'a%'" ) << false << QVariant( 0 );
1126+
1127+
// with_variable
1128+
QTest::newRow( "with_variable('five', 5, @five * 2)" ) << QStringLiteral( "with_variable('five', 5, @five * 2)" ) << false << QVariant( 10 );
1129+
QTest::newRow( "with_variable('nothing', NULL, COALESCE(@nothing, 'something'))" ) << QStringLiteral( "with_variable('nothing', NULL, COALESCE(@nothing, 'something'))" ) << false << QVariant( "something" );
1130+
1131+
// array_first, array_last
1132+
QTest::newRow( "array_first(array('a', 'b', 'c'))" ) << QStringLiteral( "array_first(array('a', 'b', 'c'))" ) << false << QVariant( "a" );
1133+
QTest::newRow( "array_first(array())" ) << QStringLiteral( "array_first(array())" ) << false << QVariant();
1134+
QTest::newRow( "array_last(array('a', 'b', 'c'))" ) << QStringLiteral( "array_last(array('a', 'b', 'c'))" ) << false << QVariant( "c" );
1135+
QTest::newRow( "array_last(array())" ) << QStringLiteral( "array_last(array())" ) << false << QVariant();
1136+
1137+
//
11261138
}
11271139

11281140
void run_evaluation_test( QgsExpression &exp, bool evalError, QVariant &expected )
@@ -1314,6 +1326,9 @@ class TestQgsExpression: public QObject
13141326
QTest::newRow( "get_feature no match2" ) << "get_feature('test','col2','no match!')" << false << -1;
13151327
//no matching layer
13161328
QTest::newRow( "get_feature no match layer" ) << "get_feature('not a layer!','col1',10)" << false << -1;
1329+
1330+
// get_feature_by_id
1331+
QTest::newRow( "get_feature_by_id" ) << "get_feature_by_id('test', 1)" << true << 1;
13171332
}
13181333

13191334
void eval_get_feature()

0 commit comments

Comments
 (0)
Please sign in to comment.