mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Merge pull request #6982 from elpaso/currentformfeature-expressions-4
[feature][need-docs] Current feature/current value form context expressions
This commit is contained in:
commit
a44eeae441
@ -178,6 +178,14 @@ which is determined at runtime.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
QSet<QString> referencedFunctions() const;
|
||||
%Docstring
|
||||
Return a list of the names of all functions which are used in this expression.
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
|
||||
QSet<int> referencedAttributeIndexes( const QgsFields &fields ) const;
|
||||
%Docstring
|
||||
Return a list of field name indexes obtained from the provided fields.
|
||||
|
@ -196,6 +196,11 @@ to evaluate child nodes.
|
||||
virtual QSet<QString> referencedVariables() const = 0;
|
||||
%Docstring
|
||||
Return a set of all variables which are used in this expression.
|
||||
%End
|
||||
|
||||
virtual QSet<QString> referencedFunctions() const = 0;
|
||||
%Docstring
|
||||
Return a set of all functions which are used in this expression.
|
||||
%End
|
||||
|
||||
virtual bool needsGeometry() const = 0;
|
||||
|
@ -55,7 +55,7 @@ Returns the node the operator will operate upon.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
|
||||
@ -154,6 +154,8 @@ Returns the node to the right of the operator.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
@ -224,7 +226,7 @@ Returns the list of nodes to search for matching values within.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
|
||||
@ -275,7 +277,8 @@ Returns a list of arguments specified for the function.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
|
||||
@ -323,7 +326,8 @@ The value of the literal.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
|
||||
@ -367,6 +371,8 @@ The name of the column.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
|
||||
|
||||
@ -466,6 +472,9 @@ The ELSE expression used for the condition.
|
||||
|
||||
virtual QSet<QString> referencedVariables() const;
|
||||
|
||||
virtual QSet<QString> referencedFunctions() const;
|
||||
|
||||
|
||||
virtual bool needsGeometry() const;
|
||||
|
||||
virtual QgsExpressionNode *clone() const /Factory/;
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsValueRelationFieldFormatter : QgsFieldFormatter
|
||||
{
|
||||
%Docstring
|
||||
@ -56,25 +57,70 @@ Constructor for QgsValueRelationFieldFormatter.
|
||||
virtual QVariant createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const;
|
||||
|
||||
|
||||
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config );
|
||||
static QStringList valueToStringList( const QVariant &value );
|
||||
%Docstring
|
||||
Utility to convert an array or a string representation of an array ``value`` to a string list
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config, const QgsFeature &formFeature = QgsFeature() );
|
||||
%Docstring
|
||||
Create a cache for a value relation field.
|
||||
This can be used to keep the value map in the local memory
|
||||
if doing multiple lookups in a loop.
|
||||
|
||||
:param config: The widget configuration
|
||||
:param formFeature: The feature currently being edited with current attribute values
|
||||
|
||||
:return: A kvp list of values for the widget
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
static QStringList valueToStringList( const QVariant &value );
|
||||
static bool expressionRequiresFormScope( const QString &expression );
|
||||
%Docstring
|
||||
Utility to convert an array or a string representation of and array ``value`` to a string list
|
||||
Check if the ``expression`` requires a form scope (i.e. if it uses fields
|
||||
or geometry of the currently edited feature).
|
||||
|
||||
:param value: The value to be converted
|
||||
:param expression: The widget's filter expression
|
||||
|
||||
:return: A string list
|
||||
:return: true if the expression requires a form scope
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
static QSet<QString> expressionFormAttributes( const QString &expression );
|
||||
%Docstring
|
||||
Return a list of attributes required by the form context ``expression``
|
||||
|
||||
:param expression: Form filter expression
|
||||
|
||||
:return: list of attributes required by the expression
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
static QSet<QString> expressionFormVariables( const QString &expression );
|
||||
%Docstring
|
||||
Return a list of variables required by the form context ``expression``
|
||||
|
||||
:param expression: Form filter expression
|
||||
|
||||
:return: list of variables required by the expression
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
static bool expressionIsUsable( const QString &expression, const QgsFeature &feature );
|
||||
%Docstring
|
||||
Check whether the ``feature`` has all values required by the ``expression``
|
||||
|
||||
:return: True if the expression can be used
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -763,6 +763,14 @@ Creates a new scope which contains variables and functions relating to the globa
|
||||
For instance, QGIS version numbers and variables specified through QGIS options.
|
||||
|
||||
.. seealso:: :py:func:`setGlobalVariable`
|
||||
%End
|
||||
|
||||
static QgsExpressionContextScope *formScope( const QgsFeature &formFeature = QgsFeature( ) ) /Factory/;
|
||||
%Docstring
|
||||
Creates a new scope which contains functions and variables from the current attribute form/table feature.
|
||||
The variables and values in this scope will reflect the current state of the form/row being edited.
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
static void setGlobalVariable( const QString &name, const QVariant &value );
|
||||
|
@ -213,6 +213,7 @@ This will be disabled when the form is not editable.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void valueChanged( const QVariant &value );
|
||||
@ -279,6 +280,34 @@ change the visual cue.
|
||||
.. versionadded:: 2.16
|
||||
%End
|
||||
|
||||
|
||||
QgsFeature formFeature() const;
|
||||
%Docstring
|
||||
The feature currently being edited, in its current state
|
||||
|
||||
:return: the feature currently being edited, in its current state
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
void setFormFeature( const QgsFeature &feature );
|
||||
%Docstring
|
||||
Set the feature currently being edited to ``feature``
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
bool setFormFeatureAttribute( const QString &attributeName, const QVariant &attributeValue );
|
||||
%Docstring
|
||||
Update the feature currently being edited by changing its
|
||||
attribute ``attributeName`` to ``attributeValue``
|
||||
|
||||
:return: bool true on success
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -180,6 +180,25 @@ QGIS forms
|
||||
|
||||
const QgsAttributeEditorContext *parentContext() const;
|
||||
|
||||
QgsFeature formFeature() const;
|
||||
%Docstring
|
||||
Return current feature from the currently edited form or table row
|
||||
|
||||
.. seealso:: :py:func:`setFormFeature`
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
void setFormFeature( const QgsFeature &feature );
|
||||
%Docstring
|
||||
Set current ``feature`` for the currently edited form or table row
|
||||
|
||||
.. seealso:: :py:func:`formFeature`
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
@ -141,7 +141,8 @@ on all attribute widgets.
|
||||
|
||||
void attributeChanged( const QString &attribute, const QVariant &value ) /Deprecated/;
|
||||
%Docstring
|
||||
Notifies about changes of attributes
|
||||
Notifies about changes of attributes, this signal is not emitted when the value is set
|
||||
back to the original one.
|
||||
|
||||
:param attribute: The name of the attribute that changed.
|
||||
:param value: The new value of the attribute.
|
||||
|
7
resources/function_help/json/current_value
Normal file
7
resources/function_help/json/current_value
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "current_value",
|
||||
"type": "function",
|
||||
"description": "Returns the current, unsaved value of a field in the form or table row currently being edited. This will differ from the feature's actual attribute values for features which are currently being edited or have not yet been added to a layer.",
|
||||
"arguments": [ {"arg":"field_name","description":"a field name in the current form or table row"}],
|
||||
"examples": [ { "expression":"current_value( 'FIELD_NAME' )","returns":"The current value of field 'FIELD_NAME'."} ]
|
||||
}
|
@ -276,6 +276,14 @@ QSet<QString> QgsExpression::referencedVariables() const
|
||||
return d->mRootNode->referencedVariables();
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpression::referencedFunctions() const
|
||||
{
|
||||
if ( !d->mRootNode )
|
||||
return QSet<QString>();
|
||||
|
||||
return d->mRootNode->referencedFunctions();
|
||||
}
|
||||
|
||||
QSet<int> QgsExpression::referencedAttributeIndexes( const QgsFields &fields ) const
|
||||
{
|
||||
if ( !d->mRootNode )
|
||||
@ -771,6 +779,10 @@ void QgsExpression::initVariableHelp()
|
||||
|
||||
//provider notification
|
||||
sVariableHelpTexts.insert( QStringLiteral( "notification_message" ), QCoreApplication::translate( "notification_message", "Content of the notification message sent by the provider (available only for actions triggered by provider notifications)." ) );
|
||||
|
||||
//form context variable
|
||||
sVariableHelpTexts.insert( QStringLiteral( "current_geometry" ), QCoreApplication::translate( "current_geometry", "Represents the geometry of the feature currently being edited in the form or the table row. Can be used for in a form/row context to filter the related features." ) );
|
||||
sVariableHelpTexts.insert( QStringLiteral( "current_feature" ), QCoreApplication::translate( "current_feature", "Represents the feature currently being edited in the form or the table row. Can be used for in a form/row context to filter the related features." ) );
|
||||
}
|
||||
|
||||
QString QgsExpression::variableHelpText( const QString &variableName )
|
||||
@ -945,3 +957,14 @@ bool QgsExpression::isField() const
|
||||
{
|
||||
return d->mRootNode && d->mRootNode->nodeType() == QgsExpressionNode::ntColumnRef;
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpression::nodes() const
|
||||
{
|
||||
if ( !d->mRootNode )
|
||||
return QList<const QgsExpressionNode *>();
|
||||
|
||||
return d->mRootNode->nodes();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgis.h"
|
||||
#include "qgsunittypes.h"
|
||||
#include "qgsinterval.h"
|
||||
#include "qgsexpressionnode.h"
|
||||
|
||||
class QgsFeature;
|
||||
class QgsGeometry;
|
||||
@ -41,7 +42,6 @@ class QgsDistanceArea;
|
||||
class QDomElement;
|
||||
class QgsExpressionContext;
|
||||
class QgsExpressionPrivate;
|
||||
class QgsExpressionNode;
|
||||
class QgsExpressionFunction;
|
||||
|
||||
/**
|
||||
@ -258,6 +258,44 @@ class CORE_EXPORT QgsExpression
|
||||
*/
|
||||
QSet<QString> referencedVariables() const;
|
||||
|
||||
/**
|
||||
* Return a list of the names of all functions which are used in this expression.
|
||||
*
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
QSet<QString> referencedFunctions() const;
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
* Return a list of all nodes which are used in this expression
|
||||
*
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
QList<const QgsExpressionNode *> nodes( ) const;
|
||||
|
||||
/**
|
||||
* Return a list of all nodes of the given class which are used in this expression
|
||||
*
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
template <class T>
|
||||
QList<const T *> findNodes( ) const
|
||||
{
|
||||
QList<const T *> lst;
|
||||
const QList<const QgsExpressionNode *> allNodes( nodes() );
|
||||
for ( const auto &node : allNodes )
|
||||
{
|
||||
const T *n = dynamic_cast<const T *>( node );
|
||||
if ( n )
|
||||
lst << n;
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return a list of field name indexes obtained from the provided fields.
|
||||
*
|
||||
|
@ -57,3 +57,4 @@ void QgsExpressionNode::cloneTo( QgsExpressionNode *target ) const
|
||||
target->parserFirstColumn = parserFirstColumn;
|
||||
target->parserFirstLine = parserFirstLine;
|
||||
}
|
||||
|
||||
|
@ -222,6 +222,19 @@ class CORE_EXPORT QgsExpressionNode SIP_ABSTRACT
|
||||
*/
|
||||
virtual QSet<QString> referencedVariables() const = 0;
|
||||
|
||||
/**
|
||||
* Return a set of all functions which are used in this expression.
|
||||
*/
|
||||
virtual QSet<QString> referencedFunctions() const = 0;
|
||||
|
||||
/**
|
||||
* Return a list of all nodes which are used in this expression.
|
||||
*
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
virtual QList<const QgsExpressionNode *> nodes( ) const = 0; SIP_SKIP
|
||||
|
||||
/**
|
||||
* Abstract virtual method which returns if the geometry is required to evaluate
|
||||
* this expression.
|
||||
|
@ -137,6 +137,19 @@ QSet<QString> QgsExpressionNodeUnaryOperator::referencedVariables() const
|
||||
return mOperand->referencedVariables();
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeUnaryOperator::referencedFunctions() const
|
||||
{
|
||||
return mOperand->referencedFunctions();
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeUnaryOperator::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst.append( this );
|
||||
lst += mOperand->nodes();
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeUnaryOperator::needsGeometry() const
|
||||
{
|
||||
return mOperand->needsGeometry();
|
||||
@ -720,6 +733,19 @@ QSet<QString> QgsExpressionNodeBinaryOperator::referencedVariables() const
|
||||
return mOpLeft->referencedVariables() + mOpRight->referencedVariables();
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeBinaryOperator::referencedFunctions() const
|
||||
{
|
||||
return mOpLeft->referencedFunctions() + mOpRight->referencedFunctions();
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeBinaryOperator::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst << this;
|
||||
lst += mOpLeft->nodes() + mOpRight->nodes();
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeBinaryOperator::needsGeometry() const
|
||||
{
|
||||
return mOpLeft->needsGeometry() || mOpRight->needsGeometry();
|
||||
@ -978,6 +1004,38 @@ QSet<QString> QgsExpressionNodeFunction::referencedVariables() const
|
||||
}
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeFunction::referencedFunctions() const
|
||||
{
|
||||
QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[mFnIndex];
|
||||
QSet<QString> functions = QSet<QString>();
|
||||
functions.insert( fd->name() );
|
||||
|
||||
if ( !mArgs )
|
||||
return functions;
|
||||
|
||||
const QList< QgsExpressionNode * > nodeList = mArgs->list();
|
||||
for ( QgsExpressionNode *n : nodeList )
|
||||
{
|
||||
functions.unite( n->referencedFunctions() );
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeFunction::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst << this;
|
||||
if ( !mArgs )
|
||||
return lst;
|
||||
|
||||
const QList< QgsExpressionNode * > nodeList = mArgs->list();
|
||||
for ( QgsExpressionNode *n : nodeList )
|
||||
{
|
||||
lst += n->nodes();
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeFunction::needsGeometry() const
|
||||
{
|
||||
bool needs = QgsExpression::QgsExpression::Functions()[mFnIndex]->usesGeometry( this );
|
||||
@ -1128,6 +1186,18 @@ QSet<QString> QgsExpressionNodeLiteral::referencedVariables() const
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeLiteral::referencedFunctions() const
|
||||
{
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeLiteral::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst << this;
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeLiteral::needsGeometry() const
|
||||
{
|
||||
return false;
|
||||
@ -1215,6 +1285,18 @@ QSet<QString> QgsExpressionNodeColumnRef::referencedVariables() const
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeColumnRef::referencedFunctions() const
|
||||
{
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeColumnRef::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> result;
|
||||
result << this;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeColumnRef::needsGeometry() const
|
||||
{
|
||||
return false;
|
||||
@ -1338,6 +1420,35 @@ QSet<QString> QgsExpressionNodeCondition::referencedVariables() const
|
||||
return lst;
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeCondition::referencedFunctions() const
|
||||
{
|
||||
QSet<QString> lst;
|
||||
for ( WhenThen *cond : mConditions )
|
||||
{
|
||||
lst += cond->mWhenExp->referencedFunctions() + cond->mThenExp->referencedFunctions();
|
||||
}
|
||||
|
||||
if ( mElseExp )
|
||||
lst += mElseExp->referencedFunctions();
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeCondition::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst << this;
|
||||
for ( WhenThen *cond : mConditions )
|
||||
{
|
||||
lst += cond->mWhenExp->nodes() + cond->mThenExp->nodes();
|
||||
}
|
||||
|
||||
if ( mElseExp )
|
||||
lst += mElseExp->nodes();
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool QgsExpressionNodeCondition::needsGeometry() const
|
||||
{
|
||||
for ( WhenThen *cond : mConditions )
|
||||
@ -1393,6 +1504,25 @@ QSet<QString> QgsExpressionNodeInOperator::referencedVariables() const
|
||||
return lst;
|
||||
}
|
||||
|
||||
QSet<QString> QgsExpressionNodeInOperator::referencedFunctions() const
|
||||
{
|
||||
QSet<QString> lst( mNode->referencedFunctions() );
|
||||
const QList< QgsExpressionNode * > nodeList = mList->list();
|
||||
for ( const QgsExpressionNode *n : nodeList )
|
||||
lst.unite( n->referencedFunctions() );
|
||||
return lst;
|
||||
}
|
||||
|
||||
QList<const QgsExpressionNode *> QgsExpressionNodeInOperator::nodes() const
|
||||
{
|
||||
QList<const QgsExpressionNode *> lst;
|
||||
lst << this;
|
||||
const QList< QgsExpressionNode * > nodeList = mList->list();
|
||||
for ( const QgsExpressionNode *n : nodeList )
|
||||
lst += n->nodes();
|
||||
return lst;
|
||||
}
|
||||
|
||||
QgsExpressionNodeCondition::WhenThen::WhenThen( QgsExpressionNode *whenExp, QgsExpressionNode *thenExp )
|
||||
: mWhenExp( whenExp )
|
||||
, mThenExp( thenExp )
|
||||
@ -1414,3 +1544,4 @@ QString QgsExpressionNodeBinaryOperator::text() const
|
||||
{
|
||||
return BINARY_OPERATOR_TEXT[mOp];
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,8 @@ class CORE_EXPORT QgsExpressionNodeUnaryOperator : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
QList<const QgsExpressionNode *> nodes() const override; SIP_SKIP
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
|
||||
@ -162,6 +164,9 @@ class CORE_EXPORT QgsExpressionNodeBinaryOperator : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
QList<const QgsExpressionNode *> nodes( ) const override; SIP_SKIP
|
||||
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
@ -241,6 +246,8 @@ class CORE_EXPORT QgsExpressionNodeInOperator : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
QList<const QgsExpressionNode *> nodes() const override; SIP_SKIP
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
@ -284,6 +291,9 @@ class CORE_EXPORT QgsExpressionNodeFunction : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
|
||||
QList<const QgsExpressionNode *> nodes() const override; SIP_SKIP
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
@ -321,6 +331,9 @@ class CORE_EXPORT QgsExpressionNodeLiteral : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
|
||||
QList<const QgsExpressionNode *> nodes() const override; SIP_SKIP
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
@ -356,6 +369,9 @@ class CORE_EXPORT QgsExpressionNodeColumnRef : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
QList<const QgsExpressionNode *> nodes( ) const override; SIP_SKIP
|
||||
|
||||
bool needsGeometry() const override;
|
||||
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
@ -456,6 +472,10 @@ class CORE_EXPORT QgsExpressionNodeCondition : public QgsExpressionNode
|
||||
|
||||
QSet<QString> referencedColumns() const override;
|
||||
QSet<QString> referencedVariables() const override;
|
||||
QSet<QString> referencedFunctions() const override;
|
||||
|
||||
QList<const QgsExpressionNode *> nodes() const override; SIP_SKIP
|
||||
|
||||
bool needsGeometry() const override;
|
||||
QgsExpressionNode *clone() const override SIP_FACTORY;
|
||||
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgis.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsexpressionnodeimpl.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
@ -99,7 +100,7 @@ QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int
|
||||
|
||||
}
|
||||
|
||||
QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatter::createCache( const QVariantMap &config )
|
||||
QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatter::createCache( const QVariantMap &config, const QgsFeature &formFeature )
|
||||
{
|
||||
ValueRelationCache cache;
|
||||
|
||||
@ -116,11 +117,19 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte
|
||||
|
||||
request.setFlags( QgsFeatureRequest::NoGeometry );
|
||||
request.setSubsetOfAttributes( QgsAttributeList() << ki << vi );
|
||||
if ( !config.value( QStringLiteral( "FilterExpression" ) ).toString().isEmpty() )
|
||||
|
||||
const QString expression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
|
||||
|
||||
// Skip the filter and build a full cache if the form scope is required and the feature
|
||||
// is not valid or the attributes required for the filter have no valid value
|
||||
if ( ! expression.isEmpty() && ( ! expressionRequiresFormScope( expression )
|
||||
|| expressionIsUsable( expression, formFeature ) ) )
|
||||
{
|
||||
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
|
||||
if ( formFeature.isValid( ) )
|
||||
context.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
|
||||
request.setExpressionContext( context );
|
||||
request.setFilterExpression( config.value( QStringLiteral( "FilterExpression" ) ).toString() );
|
||||
request.setFilterExpression( expression );
|
||||
}
|
||||
|
||||
QgsFeatureIterator fit = layer->getFeatures( request );
|
||||
@ -162,3 +171,56 @@ QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &v
|
||||
}
|
||||
return checkList;
|
||||
}
|
||||
|
||||
|
||||
QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
|
||||
{
|
||||
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
|
||||
QSet< QString > formVariables = scope->variableNames().toSet();
|
||||
const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
|
||||
formVariables.intersect( usedVariables );
|
||||
return formVariables;
|
||||
}
|
||||
|
||||
bool QgsValueRelationFieldFormatter::expressionRequiresFormScope( const QString &expression )
|
||||
{
|
||||
return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
|
||||
}
|
||||
|
||||
QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
|
||||
{
|
||||
QSet<QString> attributes;
|
||||
QgsExpression exp( expression );
|
||||
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
|
||||
// List of form function names used in the expression
|
||||
const QSet<QString> formFunctions( scope->functionNames()
|
||||
.toSet()
|
||||
.intersect( exp.referencedFunctions( ) ) );
|
||||
const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
|
||||
const QgsExpressionContext context;
|
||||
for ( const auto &f : expFunctions )
|
||||
{
|
||||
QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
|
||||
if ( formFunctions.contains( fd->name( ) ) )
|
||||
{
|
||||
for ( const auto ¶m : f->args( )->list() )
|
||||
{
|
||||
attributes.insert( param->eval( &exp, &context ).toString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
bool QgsValueRelationFieldFormatter::expressionIsUsable( const QString &expression, const QgsFeature &feature )
|
||||
{
|
||||
const QSet<QString> attrs = expressionFormAttributes( expression );
|
||||
for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
|
||||
{
|
||||
if ( ! feature.attribute( *it ).isValid() )
|
||||
return false;
|
||||
}
|
||||
if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -18,10 +18,13 @@
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgsfieldformatter.h"
|
||||
#include "qgsexpression.h"
|
||||
#include "qgsexpressioncontext.h"
|
||||
|
||||
#include <QVector>
|
||||
#include <QVariant>
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Field formatter for a value relation field.
|
||||
@ -63,22 +66,59 @@ class CORE_EXPORT QgsValueRelationFieldFormatter : public QgsFieldFormatter
|
||||
QVariant createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const override;
|
||||
|
||||
/**
|
||||
* Create a cache for a value relation field.
|
||||
* This can be used to keep the value map in the local memory
|
||||
* if doing multiple lookups in a loop.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config );
|
||||
|
||||
/**
|
||||
* Utility to convert an array or a string representation of and array \a value to a string list
|
||||
*
|
||||
* \param value The value to be converted
|
||||
* \return A string list
|
||||
* Utility to convert an array or a string representation of an array \a value to a string list
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static QStringList valueToStringList( const QVariant &value );
|
||||
|
||||
/**
|
||||
* Create a cache for a value relation field.
|
||||
* This can be used to keep the value map in the local memory
|
||||
* if doing multiple lookups in a loop.
|
||||
* \param config The widget configuration
|
||||
* \param formFeature The feature currently being edited with current attribute values
|
||||
* \return A kvp list of values for the widget
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config, const QgsFeature &formFeature = QgsFeature() );
|
||||
|
||||
/**
|
||||
* Check if the \a expression requires a form scope (i.e. if it uses fields
|
||||
* or geometry of the currently edited feature).
|
||||
*
|
||||
* \param expression The widget's filter expression
|
||||
* \return true if the expression requires a form scope
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static bool expressionRequiresFormScope( const QString &expression );
|
||||
|
||||
/**
|
||||
* Return a list of attributes required by the form context \a expression
|
||||
*
|
||||
* \param expression Form filter expression
|
||||
* \return list of attributes required by the expression
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static QSet<QString> expressionFormAttributes( const QString &expression );
|
||||
|
||||
/**
|
||||
* Return a list of variables required by the form context \a expression
|
||||
*
|
||||
* \param expression Form filter expression
|
||||
* \return list of variables required by the expression
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static QSet<QString> expressionFormVariables( const QString &expression );
|
||||
|
||||
/**
|
||||
* Check whether the \a feature has all values required by the \a expression
|
||||
*
|
||||
* \return True if the expression can be used
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static bool expressionIsUsable( const QString &expression, const QgsFeature &feature );
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QgsValueRelationFieldFormatter::ValueRelationCache )
|
||||
|
@ -736,6 +736,33 @@ class GetLayerVisibility : public QgsScopedExpressionFunction
|
||||
|
||||
};
|
||||
|
||||
|
||||
class GetCurrentFormFieldValue : public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
GetCurrentFormFieldValue( )
|
||||
: QgsScopedExpressionFunction( QStringLiteral( "current_value" ), QgsExpressionFunction::ParameterList() << QStringLiteral( "field_name" ), QStringLiteral( "Form" ) )
|
||||
{}
|
||||
|
||||
QVariant func( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
|
||||
{
|
||||
QString fieldName( values.at( 0 ).toString() );
|
||||
const QgsFeature feat( context->variable( QStringLiteral( "current_feature" ) ).value<QgsFeature>() );
|
||||
if ( fieldName.isEmpty() || ! feat.isValid( ) )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
return feat.attribute( fieldName ) ;
|
||||
}
|
||||
|
||||
QgsScopedExpressionFunction *clone() const override
|
||||
{
|
||||
return new GetCurrentFormFieldValue( );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class GetProcessingParameterValue : public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
@ -762,6 +789,16 @@ class GetProcessingParameterValue : public QgsScopedExpressionFunction
|
||||
|
||||
///@endcond
|
||||
|
||||
|
||||
QgsExpressionContextScope *QgsExpressionContextUtils::formScope( const QgsFeature &formFeature )
|
||||
{
|
||||
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Form" ) );
|
||||
scope->addFunction( QStringLiteral( "current_value" ), new GetCurrentFormFieldValue( ) );
|
||||
scope->setVariable( QStringLiteral( "current_geometry" ), formFeature.geometry( ), true );
|
||||
scope->setVariable( QStringLiteral( "current_feature" ), formFeature, true );
|
||||
return scope;
|
||||
}
|
||||
|
||||
QgsExpressionContextScope *QgsExpressionContextUtils::projectScope( const QgsProject *project )
|
||||
{
|
||||
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Project" ) );
|
||||
@ -900,6 +937,7 @@ QList<QgsExpressionContextScope *> QgsExpressionContextUtils::globalProjectLayer
|
||||
return scopes;
|
||||
}
|
||||
|
||||
|
||||
void QgsExpressionContextUtils::setLayerVariable( QgsMapLayer *layer, const QString &name, const QVariant &value )
|
||||
{
|
||||
if ( !layer )
|
||||
@ -1256,6 +1294,7 @@ void QgsExpressionContextUtils::registerContextFunctions()
|
||||
QgsExpression::registerFunction( new GetLayoutItemVariables( nullptr ) );
|
||||
QgsExpression::registerFunction( new GetLayerVisibility( QList<QgsMapLayer *>() ) );
|
||||
QgsExpression::registerFunction( new GetProcessingParameterValue( QVariantMap() ) );
|
||||
QgsExpression::registerFunction( new GetCurrentFormFieldValue( ) );
|
||||
}
|
||||
|
||||
bool QgsScopedExpressionFunction::usesGeometry( const QgsExpressionNodeFunction *node ) const
|
||||
|
@ -739,6 +739,13 @@ class CORE_EXPORT QgsExpressionContextUtils
|
||||
*/
|
||||
static QgsExpressionContextScope *globalScope() SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Creates a new scope which contains functions and variables from the current attribute form/table feature.
|
||||
* The variables and values in this scope will reflect the current state of the form/row being edited.
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
static QgsExpressionContextScope *formScope( const QgsFeature &formFeature = QgsFeature( ) ) SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Sets a global context variable. This variable will be contained within scopes retrieved via
|
||||
* globalScope().
|
||||
@ -823,14 +830,14 @@ class CORE_EXPORT QgsExpressionContextUtils
|
||||
static QList<QgsExpressionContextScope *> globalProjectLayerScopes( const QgsMapLayer *layer ) SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Sets a layer context variable. This variable will be contained within scopes retrieved via
|
||||
* layerScope().
|
||||
* \param layer map layer
|
||||
* \param name variable name
|
||||
* \param value variable value
|
||||
* \see setLayerVariables()
|
||||
* \see layerScope()
|
||||
*/
|
||||
* Sets a layer context variable. This variable will be contained within scopes retrieved via
|
||||
* layerScope().
|
||||
* \param layer map layer
|
||||
* \param name variable name
|
||||
* \param value variable value
|
||||
* \see setLayerVariables()
|
||||
* \see layerScope()
|
||||
*/
|
||||
static void setLayerVariable( QgsMapLayer *layer, const QString &name, const QVariant &value );
|
||||
|
||||
/**
|
||||
|
@ -67,8 +67,12 @@ QWidget *QgsAttributeTableDelegate::createEditor( QWidget *parent, const QStyleO
|
||||
return nullptr;
|
||||
|
||||
int fieldIdx = index.model()->data( index, QgsAttributeTableModel::FieldIndexRole ).toInt();
|
||||
|
||||
QgsAttributeEditorContext context( masterModel( index.model() )->editorContext(), QgsAttributeEditorContext::Popup );
|
||||
|
||||
// Update the editor form context with the feature being edited
|
||||
QgsFeatureId fid( index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toLongLong() );
|
||||
context.setFormFeature( vl->getFeature( fid ) );
|
||||
|
||||
QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( vl, fieldIdx, nullptr, parent, context );
|
||||
QWidget *w = eww->widget();
|
||||
|
||||
|
@ -346,7 +346,7 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
|
||||
mutable QgsExpressionContext mExpressionContext;
|
||||
|
||||
/**
|
||||
* Gets mFieldCount, mAttributes and mValueMaps
|
||||
* Gets mFieldCount, mAttributes
|
||||
*/
|
||||
virtual void loadAttributes();
|
||||
|
||||
|
@ -25,9 +25,9 @@
|
||||
|
||||
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent )
|
||||
: QgsWidgetWrapper( vl, editor, parent )
|
||||
, mFieldIdx( fieldIdx )
|
||||
, mValidConstraint( true )
|
||||
, mIsBlockingCommit( false )
|
||||
, mFieldIdx( fieldIdx )
|
||||
{
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ void QgsEditorWidgetWrapper::setEnabled( bool enabled )
|
||||
|
||||
void QgsEditorWidgetWrapper::setFeature( const QgsFeature &feature )
|
||||
{
|
||||
mFeature = feature;
|
||||
mFormFeature = feature;
|
||||
setValue( feature.attribute( mFieldIdx ) );
|
||||
}
|
||||
|
||||
@ -102,6 +102,11 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus()
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsEditorWidgetWrapper::setFormFeatureAttribute( const QString &attributeName, const QVariant &attributeValue )
|
||||
{
|
||||
return mFormFeature.setAttribute( attributeName, attributeValue );
|
||||
}
|
||||
|
||||
QgsEditorWidgetWrapper::ConstraintResult QgsEditorWidgetWrapper::constraintResult() const
|
||||
{
|
||||
return mConstraintResult;
|
||||
|
@ -213,6 +213,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
|
||||
*/
|
||||
void setConstraintResultVisible( bool constraintResultVisible );
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -277,8 +278,44 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
|
||||
*/
|
||||
virtual void updateConstraintWidgetStatus();
|
||||
|
||||
|
||||
/**
|
||||
* The feature currently being edited, in its current state
|
||||
*
|
||||
* \return the feature currently being edited, in its current state
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
QgsFeature formFeature() const { return mFormFeature; }
|
||||
|
||||
/**
|
||||
* Set the feature currently being edited to \a feature
|
||||
*
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
void setFormFeature( const QgsFeature &feature ) { mFormFeature = feature; }
|
||||
|
||||
/**
|
||||
* Update the feature currently being edited by changing its
|
||||
* attribute \a attributeName to \a attributeValue
|
||||
*
|
||||
* \return bool true on success
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
bool setFormFeatureAttribute( const QString &attributeName, const QVariant &attributeValue );
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* mFieldIdx the widget feature field id
|
||||
*/
|
||||
int mFieldIdx = -1;
|
||||
|
||||
/**
|
||||
* The feature currently being edited, in its current state
|
||||
*/
|
||||
QgsFeature mFormFeature;
|
||||
|
||||
/**
|
||||
* Boolean storing the current validity of the constraint for this widget.
|
||||
*/
|
||||
@ -296,8 +333,6 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
|
||||
//! The current constraint result
|
||||
bool mConstraintResultVisible = false;
|
||||
|
||||
int mFieldIdx;
|
||||
QgsFeature mFeature;
|
||||
mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly
|
||||
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ void QgsValueRelationConfigDlg::editExpression()
|
||||
return;
|
||||
|
||||
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vl ) );
|
||||
context << QgsExpressionContextUtils::formScope( );
|
||||
|
||||
QgsExpressionBuilderDialog dlg( vl, mFilterExpression->toPlainText(), this, QStringLiteral( "generic" ), context );
|
||||
dlg.setWindowTitle( tr( "Edit Filter Expression" ) );
|
||||
|
@ -161,7 +161,6 @@ void QgsValueRelationSearchWidgetWrapper::onValueChanged()
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsSettings settings;
|
||||
setExpression( vl.isNull() ? QgsApplication::nullRepresentation() : vl.toString() );
|
||||
emit valueChanged();
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "qgsfilterlineedit.h"
|
||||
#include "qgsfeatureiterator.h"
|
||||
#include "qgsvaluerelationfieldformatter.h"
|
||||
#include "qgsattributeform.h"
|
||||
#include "qgsattributes.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QComboBox>
|
||||
@ -70,7 +72,7 @@ QVariant QgsValueRelationWidgetWrapper::value() const
|
||||
|
||||
if ( mLineEdit )
|
||||
{
|
||||
Q_FOREACH ( const QgsValueRelationFieldFormatter::ValueRelationItem &item, mCache )
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( mCache ) )
|
||||
{
|
||||
if ( item.value == mLineEdit->text() )
|
||||
{
|
||||
@ -85,6 +87,12 @@ QVariant QgsValueRelationWidgetWrapper::value() const
|
||||
|
||||
QWidget *QgsValueRelationWidgetWrapper::createWidget( QWidget *parent )
|
||||
{
|
||||
QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
|
||||
if ( form )
|
||||
connect( form, &QgsAttributeForm::widgetValueChanged, this, &QgsValueRelationWidgetWrapper::widgetValueChanged );
|
||||
|
||||
mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();
|
||||
|
||||
if ( config( QStringLiteral( "AllowMulti" ) ).toBool() )
|
||||
{
|
||||
return new QTableWidget( parent );
|
||||
@ -100,26 +108,18 @@ QWidget *QgsValueRelationWidgetWrapper::createWidget( QWidget *parent )
|
||||
|
||||
void QgsValueRelationWidgetWrapper::initWidget( QWidget *editor )
|
||||
{
|
||||
mCache = QgsValueRelationFieldFormatter::createCache( config() );
|
||||
|
||||
mComboBox = qobject_cast<QComboBox *>( editor );
|
||||
mTableWidget = qobject_cast<QTableWidget *>( editor );
|
||||
mLineEdit = qobject_cast<QLineEdit *>( editor );
|
||||
|
||||
// Read current initial form values from the editor context
|
||||
setFeature( context().formFeature() );
|
||||
|
||||
if ( mComboBox )
|
||||
{
|
||||
if ( config( QStringLiteral( "AllowNull" ) ).toBool() )
|
||||
{
|
||||
mComboBox->addItem( tr( "(no selection)" ), QVariant( field().type() ) );
|
||||
}
|
||||
|
||||
Q_FOREACH ( const QgsValueRelationFieldFormatter::ValueRelationItem &element, mCache )
|
||||
{
|
||||
mComboBox->addItem( element.value, element.key );
|
||||
}
|
||||
|
||||
connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
|
||||
this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ) );
|
||||
this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
|
||||
}
|
||||
else if ( mTableWidget )
|
||||
{
|
||||
@ -130,46 +130,11 @@ void QgsValueRelationWidgetWrapper::initWidget( QWidget *editor )
|
||||
mTableWidget->setShowGrid( false );
|
||||
mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
|
||||
mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
|
||||
if ( mCache.size() > 0 )
|
||||
mTableWidget->setRowCount( ( mCache.size() + config( QStringLiteral( "NofColumns" ) ).toInt() - 1 ) / config( QStringLiteral( "NofColumns" ) ).toInt() );
|
||||
else
|
||||
mTableWidget->setRowCount( 1 );
|
||||
if ( config( QStringLiteral( "NofColumns" ) ).toInt() > 0 )
|
||||
mTableWidget->setColumnCount( config( QStringLiteral( "NofColumns" ) ).toInt() );
|
||||
else
|
||||
mTableWidget->setColumnCount( 1 );
|
||||
|
||||
int row = 0, column = 0;
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
|
||||
{
|
||||
if ( column == config( QStringLiteral( "NofColumns" ) ).toInt() )
|
||||
{
|
||||
row++;
|
||||
column = 0;
|
||||
}
|
||||
QTableWidgetItem *item = nullptr;
|
||||
item = new QTableWidgetItem( element.value );
|
||||
item->setData( Qt::UserRole, element.key );
|
||||
mTableWidget->setItem( row, column, item );
|
||||
column++;
|
||||
}
|
||||
connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ) );
|
||||
connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
|
||||
}
|
||||
else if ( mLineEdit )
|
||||
{
|
||||
QStringList values;
|
||||
values.reserve( mCache.size() );
|
||||
Q_FOREACH ( const QgsValueRelationFieldFormatter::ValueRelationItem &i, mCache )
|
||||
{
|
||||
values << i.value;
|
||||
}
|
||||
|
||||
QStringListModel *m = new QStringListModel( values, mLineEdit );
|
||||
QCompleter *completer = new QCompleter( m, mLineEdit );
|
||||
completer->setCaseSensitivity( Qt::CaseInsensitive );
|
||||
mLineEdit->setCompleter( completer );
|
||||
|
||||
connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & value ) { emit valueChanged( value ); } );
|
||||
connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & value ) { emit valueChanged( value ); }, Qt::UniqueConnection );
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,6 +149,11 @@ void QgsValueRelationWidgetWrapper::setValue( const QVariant &value )
|
||||
{
|
||||
QStringList checkList( QgsValueRelationFieldFormatter::valueToStringList( value ) );
|
||||
|
||||
QTableWidgetItem *lastChangedItem = nullptr;
|
||||
|
||||
// This block is needed because item->setCheckState triggers dataChanged gets back to value()
|
||||
// and iterate over all items again! This can be extremely slow on large items sets.
|
||||
mTableWidget->blockSignals( true );
|
||||
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
|
||||
{
|
||||
for ( int i = 0; i < config( QStringLiteral( "NofColumns" ) ).toInt() ; ++i )
|
||||
@ -192,9 +162,15 @@ void QgsValueRelationWidgetWrapper::setValue( const QVariant &value )
|
||||
if ( item )
|
||||
{
|
||||
item->setCheckState( checkList.contains( item->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
|
||||
lastChangedItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
mTableWidget->blockSignals( false );
|
||||
// let's trigger the signal now, once and for all
|
||||
if ( lastChangedItem )
|
||||
lastChangedItem->setCheckState( checkList.contains( lastChangedItem->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
|
||||
|
||||
}
|
||||
else if ( mComboBox )
|
||||
{
|
||||
@ -202,7 +178,7 @@ void QgsValueRelationWidgetWrapper::setValue( const QVariant &value )
|
||||
}
|
||||
else if ( mLineEdit )
|
||||
{
|
||||
Q_FOREACH ( QgsValueRelationFieldFormatter::ValueRelationItem i, mCache )
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
|
||||
{
|
||||
if ( i.key == value )
|
||||
{
|
||||
@ -213,19 +189,126 @@ void QgsValueRelationWidgetWrapper::setValue( const QVariant &value )
|
||||
}
|
||||
}
|
||||
|
||||
void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
|
||||
{
|
||||
|
||||
// Do nothing if the value has not changed
|
||||
if ( attributeChanged )
|
||||
{
|
||||
setFormFeatureAttribute( attribute, newValue );
|
||||
// Update combos if the value used in the filter expression has changed
|
||||
if ( QgsValueRelationFieldFormatter::expressionRequiresFormScope( mExpression )
|
||||
&& QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
|
||||
{
|
||||
populate();
|
||||
// Restore value
|
||||
setValue( value( ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QgsValueRelationWidgetWrapper::setFeature( const QgsFeature &feature )
|
||||
{
|
||||
setFormFeature( feature );
|
||||
whileBlocking( this )->populate();
|
||||
whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
|
||||
// A bit of logic to set the default value if AllowNull is false and this is a new feature
|
||||
// Note that this needs to be here after the cache has been created/updated by populate()
|
||||
// and signals unblocked (we want this to propagate to the feature itself)
|
||||
if ( formFeature().isValid()
|
||||
&& ! formFeature().attribute( fieldIdx() ).isValid()
|
||||
&& mCache.size() > 0
|
||||
&& ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
|
||||
{
|
||||
// This is deferred because at the time the feature is set in one widget it is not
|
||||
// set in the next, which is typically the "down" in a drill-down
|
||||
QTimer::singleShot( 0, [ = ]
|
||||
{
|
||||
setValue( mCache.at( 0 ).key );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QgsValueRelationWidgetWrapper::populate( )
|
||||
{
|
||||
// Initialize, note that signals are blocked, to avoid double signals on new features
|
||||
if ( QgsValueRelationFieldFormatter::expressionRequiresFormScope( mExpression ) )
|
||||
{
|
||||
mCache = QgsValueRelationFieldFormatter::createCache( config( ), formFeature() );
|
||||
}
|
||||
else if ( mCache.isEmpty() )
|
||||
{
|
||||
mCache = QgsValueRelationFieldFormatter::createCache( config( ) );
|
||||
}
|
||||
|
||||
if ( mComboBox )
|
||||
{
|
||||
mComboBox->clear();
|
||||
if ( config( QStringLiteral( "AllowNull" ) ).toBool( ) )
|
||||
{
|
||||
whileBlocking( mComboBox )->addItem( tr( "(no selection)" ), QVariant( field().type( ) ) );
|
||||
}
|
||||
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
|
||||
{
|
||||
whileBlocking( mComboBox )->addItem( element.value, element.key );
|
||||
}
|
||||
}
|
||||
else if ( mTableWidget )
|
||||
{
|
||||
if ( mCache.size() > 0 )
|
||||
mTableWidget->setRowCount( ( mCache.size() + config( QStringLiteral( "NofColumns" ) ).toInt() - 1 ) / config( QStringLiteral( "NofColumns" ) ).toInt() );
|
||||
else
|
||||
mTableWidget->setRowCount( 1 );
|
||||
if ( config( QStringLiteral( "NofColumns" ) ).toInt() > 0 )
|
||||
mTableWidget->setColumnCount( config( QStringLiteral( "NofColumns" ) ).toInt() );
|
||||
else
|
||||
mTableWidget->setColumnCount( 1 );
|
||||
|
||||
whileBlocking( mTableWidget )->clear();
|
||||
int row = 0, column = 0;
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
|
||||
{
|
||||
if ( column == config( QStringLiteral( "NofColumns" ) ).toInt() )
|
||||
{
|
||||
row++;
|
||||
column = 0;
|
||||
}
|
||||
QTableWidgetItem *item = nullptr;
|
||||
item = new QTableWidgetItem( element.value );
|
||||
item->setData( Qt::UserRole, element.key );
|
||||
whileBlocking( mTableWidget )->setItem( row, column, item );
|
||||
column++;
|
||||
}
|
||||
}
|
||||
else if ( mLineEdit )
|
||||
{
|
||||
QStringList values;
|
||||
values.reserve( mCache.size() );
|
||||
for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
|
||||
{
|
||||
values << i.value;
|
||||
}
|
||||
QStringListModel *m = new QStringListModel( values, mLineEdit );
|
||||
QCompleter *completer = new QCompleter( m, mLineEdit );
|
||||
completer->setCaseSensitivity( Qt::CaseInsensitive );
|
||||
mLineEdit->setCompleter( completer );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsValueRelationWidgetWrapper::showIndeterminateState()
|
||||
{
|
||||
if ( mTableWidget )
|
||||
{
|
||||
mTableWidget->blockSignals( true );
|
||||
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
|
||||
{
|
||||
for ( int i = 0; i < config( QStringLiteral( "NofColumns" ) ).toInt(); ++i )
|
||||
{
|
||||
mTableWidget->item( j, i )->setCheckState( Qt::PartiallyChecked );
|
||||
whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
|
||||
}
|
||||
}
|
||||
mTableWidget->blockSignals( false );
|
||||
}
|
||||
else if ( mComboBox )
|
||||
{
|
||||
@ -246,6 +329,7 @@ void QgsValueRelationWidgetWrapper::setEnabled( bool enabled )
|
||||
|
||||
if ( mTableWidget )
|
||||
{
|
||||
mTableWidget->blockSignals( true );
|
||||
for ( int j = 0; j < mTableWidget->rowCount(); j++ )
|
||||
{
|
||||
for ( int i = 0; i < mTableWidget->columnCount(); ++i )
|
||||
@ -260,6 +344,7 @@ void QgsValueRelationWidgetWrapper::setEnabled( bool enabled )
|
||||
}
|
||||
}
|
||||
}
|
||||
mTableWidget->blockSignals( false );
|
||||
}
|
||||
else
|
||||
QgsEditorWidgetWrapper::setEnabled( enabled );
|
||||
|
@ -68,9 +68,39 @@ class GUI_EXPORT QgsValueRelationWidgetWrapper : public QgsEditorWidgetWrapper
|
||||
bool valid() const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void setValue( const QVariant &value ) override;
|
||||
|
||||
/**
|
||||
* Will be called when a value in the current edited form or table row
|
||||
* changes
|
||||
*
|
||||
* Update widget cache if the value is used in the filter expression and
|
||||
* stores current field values to be used in expression form scope context
|
||||
*
|
||||
* \param attribute The name of the attribute that changed.
|
||||
* \param newValue The new value of the attribute.
|
||||
* \param attributeChanged If true, it corresponds to an actual change of the feature attribute
|
||||
* \since QGIS 3.2.0
|
||||
*/
|
||||
void widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged );
|
||||
|
||||
/**
|
||||
* Will be called when the feature changes
|
||||
*
|
||||
* Is forwarded to the slot setValue() and updates the widget cache if
|
||||
* the filter expression context contains values from the current feature
|
||||
*
|
||||
* \param feature The new feature
|
||||
*/
|
||||
void setFeature( const QgsFeature &feature ) override;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
//! Set the values for the widgets, re-creates the cache when required
|
||||
void populate( );
|
||||
|
||||
QComboBox *mComboBox = nullptr;
|
||||
QTableWidget *mTableWidget = nullptr;
|
||||
QLineEdit *mLineEdit = nullptr;
|
||||
@ -79,6 +109,7 @@ class GUI_EXPORT QgsValueRelationWidgetWrapper : public QgsEditorWidgetWrapper
|
||||
QgsVectorLayer *mLayer = nullptr;
|
||||
|
||||
bool mEnabled = true;
|
||||
QString mExpression;
|
||||
|
||||
friend class QgsValueRelationWidgetFactory;
|
||||
friend class TestQgsValueRelationWidgetWrapper;
|
||||
|
@ -63,6 +63,7 @@ class GUI_EXPORT QgsAttributeEditorContext
|
||||
, mVectorLayerTools( parentContext.mVectorLayerTools )
|
||||
, mMapCanvas( parentContext.mMapCanvas )
|
||||
, mDistanceArea( parentContext.mDistanceArea )
|
||||
, mFormFeature( parentContext.mFormFeature )
|
||||
, mFormMode( formMode )
|
||||
{
|
||||
Q_ASSERT( parentContext.vectorLayerTools() );
|
||||
@ -189,6 +190,21 @@ class GUI_EXPORT QgsAttributeEditorContext
|
||||
|
||||
inline const QgsAttributeEditorContext *parentContext() const { return mParentContext; }
|
||||
|
||||
/**
|
||||
* Return current feature from the currently edited form or table row
|
||||
* \see setFormFeature()
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
QgsFeature formFeature() const { return mFormFeature; }
|
||||
|
||||
/**
|
||||
* Set current \a feature for the currently edited form or table row
|
||||
* \see formFeature()
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
void setFormFeature( const QgsFeature &feature ) { mFormFeature = feature ; }
|
||||
|
||||
|
||||
private:
|
||||
const QgsAttributeEditorContext *mParentContext = nullptr;
|
||||
QgsVectorLayer *mLayer = nullptr;
|
||||
@ -197,6 +213,8 @@ class GUI_EXPORT QgsAttributeEditorContext
|
||||
QgsDistanceArea mDistanceArea;
|
||||
QgsRelation mRelation;
|
||||
RelationMode mRelationMode = Undefined;
|
||||
//! Store the values of the currently edited form or table row
|
||||
QgsFeature mFormFeature;
|
||||
FormMode mFormMode = Embed;
|
||||
bool mAllowCustomUi = true;
|
||||
};
|
||||
|
@ -690,6 +690,7 @@ QString QgsAttributeForm::createFilterExpression() const
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
void QgsAttributeForm::onAttributeChanged( const QVariant &value )
|
||||
{
|
||||
QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
|
||||
|
@ -174,7 +174,8 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Notifies about changes of attributes
|
||||
* Notifies about changes of attributes, this signal is not emitted when the value is set
|
||||
* back to the original one.
|
||||
*
|
||||
* \param attribute The name of the attribute that changed.
|
||||
* \param value The new value of the attribute.
|
||||
|
@ -1861,6 +1861,59 @@ class TestQgsExpression: public QObject
|
||||
QCOMPARE( refVar, expectedVars );
|
||||
}
|
||||
|
||||
|
||||
void referenced_functions()
|
||||
{
|
||||
QSet<QString> expectedFunctions;
|
||||
expectedFunctions << QStringLiteral( "current_value" )
|
||||
<< QStringLiteral( "var" )
|
||||
<< QStringLiteral( "intersects" )
|
||||
<< QStringLiteral( "$geometry" )
|
||||
<< QStringLiteral( "buffer" );
|
||||
|
||||
QgsExpression exp( QStringLiteral( "current_value( 'FIELD_NAME' ) = 'A_VALUE' AND intersects(buffer($geometry, 10), @current_geometry)" ) );
|
||||
QCOMPARE( exp.hasParserError(), false );
|
||||
QSet<QString> refVar = exp.referencedFunctions();
|
||||
|
||||
QCOMPARE( refVar, expectedFunctions );
|
||||
}
|
||||
|
||||
void findNodes()
|
||||
{
|
||||
QSet<QString> expectedFunctions;
|
||||
expectedFunctions << QStringLiteral( "current_value" )
|
||||
<< QStringLiteral( "intersects" )
|
||||
<< QStringLiteral( "var" )
|
||||
<< QStringLiteral( "$geometry" )
|
||||
<< QStringLiteral( "buffer" );
|
||||
QgsExpression exp( QStringLiteral( "current_value( 'FIELD_NAME' ) = 'A_VALUE' AND intersects(buffer($geometry, 10), @current_geometry)" ) );
|
||||
QList<const QgsExpressionNodeFunction *> functionNodes( exp.findNodes<QgsExpressionNodeFunction>() );
|
||||
QCOMPARE( functionNodes.size(), 5 );
|
||||
QgsExpressionFunction *fd;
|
||||
QSet<QString> actualFunctions;
|
||||
for ( const auto &f : functionNodes )
|
||||
{
|
||||
QCOMPARE( f->nodeType(), QgsExpressionNode::NodeType::ntFunction );
|
||||
fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
|
||||
actualFunctions << fd->name();
|
||||
}
|
||||
QCOMPARE( actualFunctions, expectedFunctions );
|
||||
|
||||
QSet<QgsExpressionNodeBinaryOperator::BinaryOperator> expectedBinaryOps;
|
||||
expectedBinaryOps << QgsExpressionNodeBinaryOperator::BinaryOperator::boAnd;
|
||||
expectedBinaryOps << QgsExpressionNodeBinaryOperator::BinaryOperator::boEQ;
|
||||
QList<const QgsExpressionNodeBinaryOperator *> binaryOpsNodes( exp.findNodes<QgsExpressionNodeBinaryOperator>() );
|
||||
QCOMPARE( binaryOpsNodes.size(), 2 );
|
||||
QSet<QgsExpressionNodeBinaryOperator::BinaryOperator> actualBinaryOps;
|
||||
for ( const auto &f : binaryOpsNodes )
|
||||
{
|
||||
QCOMPARE( f->nodeType(), QgsExpressionNode::NodeType::ntBinaryOperator );
|
||||
actualBinaryOps << f->op();
|
||||
}
|
||||
QCOMPARE( actualBinaryOps, expectedBinaryOps );
|
||||
|
||||
}
|
||||
|
||||
void referenced_columns_all_attributes()
|
||||
{
|
||||
QgsExpression exp( QStringLiteral( "attribute($currentfeature,'test')" ) );
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "qgseditorwidgetwrapper.h"
|
||||
#include <editorwidgets/qgsvaluerelationwidgetwrapper.h>
|
||||
#include <QTableWidget>
|
||||
#include <QComboBox>
|
||||
#include "qgsgui.h"
|
||||
|
||||
class TestQgsValueRelationWidgetWrapper : public QObject
|
||||
@ -39,6 +40,8 @@ class TestQgsValueRelationWidgetWrapper : public QObject
|
||||
void cleanup(); // will be called after every testfunction.
|
||||
|
||||
void testScrollBarUnlocked();
|
||||
void testDrillDown();
|
||||
void testDrillDownMulti();
|
||||
};
|
||||
|
||||
void TestQgsValueRelationWidgetWrapper::initTestCase()
|
||||
@ -59,6 +62,7 @@ void TestQgsValueRelationWidgetWrapper::init()
|
||||
|
||||
void TestQgsValueRelationWidgetWrapper::cleanup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TestQgsValueRelationWidgetWrapper::testScrollBarUnlocked()
|
||||
@ -110,5 +114,202 @@ void TestQgsValueRelationWidgetWrapper::testScrollBarUnlocked()
|
||||
QCOMPARE( itemEnabled, true );
|
||||
}
|
||||
|
||||
void TestQgsValueRelationWidgetWrapper::testDrillDown()
|
||||
{
|
||||
// create a vector layer
|
||||
QgsVectorLayer vl1( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) );
|
||||
QgsVectorLayer vl2( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
|
||||
QgsProject::instance()->addMapLayer( &vl1, false, false );
|
||||
QgsProject::instance()->addMapLayer( &vl2, false, false );
|
||||
|
||||
// insert some features
|
||||
QgsFeature f1( vl1.fields() );
|
||||
f1.setAttribute( QStringLiteral( "pk" ), 1 );
|
||||
f1.setAttribute( QStringLiteral( "province" ), 123 );
|
||||
f1.setAttribute( QStringLiteral( "municipality" ), QStringLiteral( "Some Place By The River" ) );
|
||||
f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POLYGON(( 0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) );
|
||||
QVERIFY( f1.isValid() );
|
||||
QgsFeature f2( vl1.fields() );
|
||||
f2.setAttribute( QStringLiteral( "pk" ), 2 );
|
||||
f2.setAttribute( QStringLiteral( "province" ), 245 );
|
||||
f2.setAttribute( QStringLiteral( "municipality" ), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
f2.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POLYGON(( 1 0, 1 1, 2 1, 2 0, 1 0 ))" ) ) );
|
||||
QVERIFY( f2.isValid() );
|
||||
QVERIFY( vl1.dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 ) );
|
||||
|
||||
QgsFeature f3( vl2.fields() );
|
||||
f3.setAttribute( QStringLiteral( "fk_province" ), 123 );
|
||||
f3.setAttribute( QStringLiteral( "fk_municipality" ), 1 );
|
||||
f3.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT( 0.5 0.5)" ) ) );
|
||||
QVERIFY( f3.isValid() );
|
||||
QVERIFY( f3.geometry().isGeosValid() );
|
||||
QVERIFY( vl2.dataProvider()->addFeature( f3 ) );
|
||||
|
||||
// build a value relation widget wrapper for municipality
|
||||
QgsValueRelationWidgetWrapper w_municipality( &vl2, vl2.fields().indexOf( QStringLiteral( "fk_municipality" ) ), nullptr, nullptr );
|
||||
QVariantMap cfg_municipality;
|
||||
cfg_municipality.insert( QStringLiteral( "Layer" ), vl1.id() );
|
||||
cfg_municipality.insert( QStringLiteral( "Key" ), QStringLiteral( "pk" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "Value" ), QStringLiteral( "municipality" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "AllowMulti" ), false );
|
||||
cfg_municipality.insert( QStringLiteral( "NofColumns" ), 1 );
|
||||
cfg_municipality.insert( QStringLiteral( "AllowNull" ), false );
|
||||
cfg_municipality.insert( QStringLiteral( "OrderByValue" ), true );
|
||||
cfg_municipality.insert( QStringLiteral( "FilterExpression" ), QStringLiteral( "\"province\" = current_value('fk_province')" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "UseCompleter" ), false );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.widget();
|
||||
w_municipality.setEnabled( true );
|
||||
|
||||
QCOMPARE( w_municipality.mCache.size(), 2 );
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 2 );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mCache.size(), 1 );
|
||||
|
||||
// Check first is selected
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 1 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "Some Place By The River" ) );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "1" ) );
|
||||
|
||||
// Filter by geometry
|
||||
cfg_municipality[ QStringLiteral( "FilterExpression" ) ] = QStringLiteral( "contains(buffer(@current_geometry, 1 ), $geometry)" );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 1 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "Some Place By The River" ) );
|
||||
|
||||
// Move the point to 1.5 0.5
|
||||
f3.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT( 1.5 0.5)" ) ) );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 1 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
|
||||
// Enlarge the buffer
|
||||
cfg_municipality[ QStringLiteral( "FilterExpression" ) ] = QStringLiteral( "contains(buffer(@current_geometry, 3 ), $geometry)" );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 2 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 1 ), QStringLiteral( "Some Place By The River" ) );
|
||||
|
||||
// Check with allow null
|
||||
cfg_municipality[QStringLiteral( "AllowNull" )] = true;
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( QgsFeature() );
|
||||
|
||||
// Check null is selected
|
||||
QCOMPARE( w_municipality.mComboBox->count(), 3 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "(no selection)" ) );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "" ) );
|
||||
|
||||
// Check order by value false
|
||||
cfg_municipality[QStringLiteral( "AllowNull" )] = false;
|
||||
cfg_municipality[QStringLiteral( "OrderByValue" )] = false;
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 1 ), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
QCOMPARE( w_municipality.mComboBox->itemText( 0 ), QStringLiteral( "Some Place By The River" ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TestQgsValueRelationWidgetWrapper::testDrillDownMulti()
|
||||
{
|
||||
// create a vector layer
|
||||
QgsVectorLayer vl1( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) );
|
||||
QgsVectorLayer vl2( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
|
||||
QgsProject::instance()->addMapLayer( &vl1, false, false );
|
||||
QgsProject::instance()->addMapLayer( &vl2, false, false );
|
||||
|
||||
// insert some features
|
||||
QgsFeature f1( vl1.fields() );
|
||||
f1.setAttribute( QStringLiteral( "pk" ), 1 );
|
||||
f1.setAttribute( QStringLiteral( "province" ), 123 );
|
||||
f1.setAttribute( QStringLiteral( "municipality" ), QStringLiteral( "Some Place By The River" ) );
|
||||
f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POLYGON(( 0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) );
|
||||
QVERIFY( f1.isValid() );
|
||||
QgsFeature f2( vl1.fields() );
|
||||
f2.setAttribute( QStringLiteral( "pk" ), 2 );
|
||||
f2.setAttribute( QStringLiteral( "province" ), 245 );
|
||||
f2.setAttribute( QStringLiteral( "municipality" ), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
f2.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POLYGON(( 1 0, 1 1, 2 1, 2 0, 1 0 ))" ) ) );
|
||||
QVERIFY( f2.isValid() );
|
||||
QVERIFY( vl1.dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 ) );
|
||||
|
||||
QgsFeature f3( vl2.fields() );
|
||||
f3.setAttribute( QStringLiteral( "fk_province" ), 123 );
|
||||
f3.setAttribute( QStringLiteral( "fk_municipality" ), QStringLiteral( "{1}" ) );
|
||||
f3.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT( 0.5 0.5)" ) ) );
|
||||
QVERIFY( f3.isValid() );
|
||||
QVERIFY( f3.geometry().isGeosValid() );
|
||||
QVERIFY( vl2.dataProvider()->addFeature( f3 ) );
|
||||
|
||||
// build a value relation widget wrapper for municipality
|
||||
QgsValueRelationWidgetWrapper w_municipality( &vl2, vl2.fields().indexOf( QStringLiteral( "fk_municipality" ) ), nullptr, nullptr );
|
||||
QVariantMap cfg_municipality;
|
||||
cfg_municipality.insert( QStringLiteral( "Layer" ), vl1.id() );
|
||||
cfg_municipality.insert( QStringLiteral( "Key" ), QStringLiteral( "pk" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "Value" ), QStringLiteral( "municipality" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "AllowMulti" ), true );
|
||||
cfg_municipality.insert( QStringLiteral( "NofColumns" ), 1 );
|
||||
cfg_municipality.insert( QStringLiteral( "AllowNull" ), false );
|
||||
cfg_municipality.insert( QStringLiteral( "OrderByValue" ), true );
|
||||
cfg_municipality.insert( QStringLiteral( "FilterExpression" ), QStringLiteral( "\"province\" = current_value('fk_province')" ) );
|
||||
cfg_municipality.insert( QStringLiteral( "UseCompleter" ), false );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.widget();
|
||||
w_municipality.setEnabled( true );
|
||||
|
||||
QCOMPARE( w_municipality.mCache.size(), 2 );
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 2 );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mCache.size(), 1 );
|
||||
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 1 );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->text(), QStringLiteral( "Some Place By The River" ) );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "{1}" ) );
|
||||
|
||||
// Filter by geometry
|
||||
cfg_municipality[ QStringLiteral( "FilterExpression" ) ] = QStringLiteral( "contains(buffer(@current_geometry, 1 ), $geometry)" );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 1 );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->text(), QStringLiteral( "Some Place By The River" ) );
|
||||
|
||||
// Move the point to 1.5 0.5
|
||||
f3.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT( 1.5 0.5)" ) ) );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 1 );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->text(), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
|
||||
// Enlarge the buffer
|
||||
cfg_municipality[ QStringLiteral( "FilterExpression" ) ] = QStringLiteral( "contains(buffer(@current_geometry, 3 ), $geometry)" );
|
||||
w_municipality.setConfig( cfg_municipality );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 2 );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->text(), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->data( Qt::UserRole ).toString(), QStringLiteral( "2" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->text(), QStringLiteral( "Some Place By The River" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->data( Qt::UserRole ).toString(), QStringLiteral( "1" ) );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "{1}" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->checkState(), Qt::Unchecked );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->checkState(), Qt::Checked );
|
||||
w_municipality.setValue( QStringLiteral( "{1,2}" ) );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "{2,1}" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->checkState(), Qt::Checked );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->checkState(), Qt::Checked );
|
||||
|
||||
// Check values are checked
|
||||
f3.setAttribute( QStringLiteral( "fk_municipality" ), QStringLiteral( "{1,2}" ) );
|
||||
w_municipality.setFeature( f3 );
|
||||
QCOMPARE( w_municipality.mTableWidget->rowCount(), 2 );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->text(), QStringLiteral( "Dreamland By The Clouds" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->text(), QStringLiteral( "Some Place By The River" ) );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 0, 0 )->checkState(), Qt::Checked );
|
||||
QCOMPARE( w_municipality.mTableWidget->item( 1, 0 )->checkState(), Qt::Checked );
|
||||
QCOMPARE( w_municipality.value().toString(), QStringLiteral( "{2,1}" ) );
|
||||
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsValueRelationWidgetWrapper )
|
||||
#include "testqgsvaluerelationwidgetwrapper.moc"
|
||||
|
@ -16,7 +16,8 @@ import qgis # NOQA
|
||||
|
||||
from qgis.core import (QgsFeature, QgsProject, QgsRelation, QgsVectorLayer,
|
||||
QgsValueMapFieldFormatter, QgsValueRelationFieldFormatter,
|
||||
QgsRelationReferenceFieldFormatter, QgsRangeFieldFormatter, QgsSettings)
|
||||
QgsRelationReferenceFieldFormatter, QgsRangeFieldFormatter,
|
||||
QgsSettings, QgsGeometry, QgsPointXY)
|
||||
|
||||
from qgis.PyQt.QtCore import QCoreApplication, QLocale
|
||||
from qgis.testing import start_app, unittest
|
||||
@ -133,6 +134,39 @@ class TestQgsValueRelationFieldFormatter(unittest.TestCase):
|
||||
_test(['1', '2', '3'], ["1", "2", "3"])
|
||||
_test('not an array', ['not an array'])
|
||||
|
||||
def test_expressionRequiresFormScope(self):
|
||||
|
||||
res = list(QgsValueRelationFieldFormatter.expressionFormAttributes("current_value('ONE') AND current_value('TWO')"))
|
||||
res = sorted(res)
|
||||
self.assertEqual(res, ['ONE', 'TWO'])
|
||||
|
||||
res = list(QgsValueRelationFieldFormatter.expressionFormVariables("@current_geometry"))
|
||||
self.assertEqual(res, ['current_geometry'])
|
||||
|
||||
self.assertFalse(QgsValueRelationFieldFormatter.expressionRequiresFormScope(""))
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionRequiresFormScope("current_value('TWO')"))
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionRequiresFormScope("current_value ( 'TWO' )"))
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionRequiresFormScope("@current_geometry"))
|
||||
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionIsUsable("", QgsFeature()))
|
||||
self.assertFalse(QgsValueRelationFieldFormatter.expressionIsUsable("@current_geometry", QgsFeature()))
|
||||
self.assertFalse(QgsValueRelationFieldFormatter.expressionIsUsable("current_value ( 'TWO' )", QgsFeature()))
|
||||
|
||||
layer = QgsVectorLayer("none?field=pkid:integer&field=decoded:string",
|
||||
"layer", "memory")
|
||||
self.assertTrue(layer.isValid())
|
||||
QgsProject.instance().addMapLayer(layer)
|
||||
f = QgsFeature(layer.fields())
|
||||
f.setAttributes([1, 'value'])
|
||||
point = QgsGeometry.fromPointXY(QgsPointXY(123, 456))
|
||||
f.setGeometry(point)
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionIsUsable("current_geometry", f))
|
||||
self.assertFalse(QgsValueRelationFieldFormatter.expressionIsUsable("current_value ( 'TWO' )", f))
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionIsUsable("current_value ( 'pkid' )", f))
|
||||
self.assertTrue(QgsValueRelationFieldFormatter.expressionIsUsable("@current_geometry current_value ( 'pkid' )", f))
|
||||
|
||||
QgsProject.instance().removeMapLayer(layer.id())
|
||||
|
||||
|
||||
class TestQgsRelationReferenceFieldFormatter(unittest.TestCase):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user