QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsfieldexpressionwidget.cpp
Go to the documentation of this file.
1
2/***************************************************************************
3 qgsfieldexpressionwidget.cpp
4 --------------------------------------
5 Date : 01.04.2014
6 Copyright : (C) 2014 Denis Rouzaud
8***************************************************************************
9* *
10* This program is free software; you can redistribute it and/or modify *
11* it under the terms of the GNU General Public License as published by *
12* the Free Software Foundation; either version 2 of the License, or *
13* (at your option) any later version. *
14* *
15***************************************************************************/
16
17#include <QHBoxLayout>
18#include <QObject>
19#include <QKeyEvent>
20
21#include "qgsapplication.h"
24#include "qgsfieldproxymodel.h"
25#include "qgsdistancearea.h"
26#include "qgsfieldmodel.h"
27#include "qgsvectorlayer.h"
28#include "qgsproject.h"
31
33 : QWidget( parent )
34 , mExpressionDialogTitle( tr( "Expression Builder" ) )
35 , mDistanceArea( nullptr )
36
37{
38 QHBoxLayout *layout = new QHBoxLayout( this );
39 layout->setContentsMargins( 0, 0, 0, 0 );
40
41 mCombo = new QComboBox( this );
42 mCombo->setEditable( true );
43 mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
44 const int width = mCombo->minimumSizeHint().width();
45 mCombo->setMinimumWidth( width );
46
47 mFieldProxyModel = new QgsFieldProxyModel( mCombo );
48 mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
49 mCombo->setModel( mFieldProxyModel );
50
51 mButton = new QToolButton( this );
52 mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
53 mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
54
55 layout->addWidget( mCombo );
56 layout->addWidget( mButton );
57
58 connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
59 connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
60 connect( mCombo, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
61 connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
62 connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
63 connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
64
65 mExpressionContext = QgsExpressionContext();
66 mExpressionContext << QgsExpressionContextUtils::globalScope()
68
69 mCombo->installEventFilter( this );
70}
71
73{
74 mExpressionDialogTitle = title;
75}
76
78{
79 mFieldProxyModel->setFilters( filters );
80}
81
83{
84 mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
85 mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
86}
87
89{
90 return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
91}
92
94{
95 QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
96 if ( !layout )
97 return;
98
99 if ( isLeft )
100 {
101 QLayoutItem *item = layout->takeAt( 1 );
102 layout->insertWidget( 0, item->widget() );
103 }
104 else
105 layout->addWidget( mCombo );
106}
107
109{
110 mDistanceArea = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
111}
112
114{
115 return mCombo->currentText();
116}
117
119{
121}
122
124{
125 return asExpression();
126}
127
128bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
129{
130 QString temp;
131 return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
132}
133
135{
136 return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
137}
138
139QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
140{
141 QString text = currentText();
142 const bool valueIsExpression = this->isExpression();
143 if ( isValid )
144 {
145 // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
146 *isValid = !valueIsExpression || isValidExpression();
147 }
148 if ( isExpression )
149 {
150 *isExpression = valueIsExpression;
151 }
152 return text;
153}
154
156{
157 return mFieldProxyModel->sourceFieldModel()->layer();
158}
159
161{
162 mExpressionContextGenerator = generator;
163}
164
165void QgsFieldExpressionWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant> > &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
166{
167 mCustomPreviewLabel = label;
168 mCustomChoices = choices;
169 mPreviewContextGenerator = previewContextGenerator;
170}
171
173{
174 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
175
176 if ( mFieldProxyModel->sourceFieldModel()->layer() )
177 disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
178
179 if ( vl )
180 mExpressionContext = vl->createExpressionContext();
181 else
182 mExpressionContext = QgsProject::instance()->createExpressionContext();
183
184 mFieldProxyModel->sourceFieldModel()->setLayer( vl );
185
186 if ( mFieldProxyModel->sourceFieldModel()->layer() )
187 connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
188}
189
190void QgsFieldExpressionWidget::setField( const QString &fieldName )
191{
192 if ( fieldName.isEmpty() )
193 {
194 setRow( -1 );
195 emit fieldChanged( QString() );
196 emit fieldChanged( QString(), true );
197 return;
198 }
199
200 if ( fieldName.size() > mCombo->lineEdit()->maxLength() )
201 {
202 mCombo->lineEdit()->setMaxLength( fieldName.size() );
203 }
204
205 QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
206 if ( !idx.isValid() )
207 {
208 // try to remove quotes and white spaces
209 QString simpleFieldName = fieldName.trimmed();
210 if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
211 {
212 simpleFieldName.remove( 0, 1 ).chop( 1 );
213 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
214 }
215
216 if ( !idx.isValid() )
217 {
218 // new expression
219 mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
220 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
221 }
222 }
223 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
224 mCombo->setCurrentIndex( proxyIndex.row() );
226}
227
229{
230 mFieldProxyModel->sourceFieldModel()->setFields( fields );
231}
232
233void QgsFieldExpressionWidget::setExpression( const QString &expression )
234{
236}
237
239{
240 const QString currentExpression = asExpression();
241 QgsVectorLayer *vl = layer();
242
243 const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
244
245 QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
246 if ( mDistanceArea )
247 {
248 dlg.setGeomCalculator( *mDistanceArea );
249 }
250 dlg.setWindowTitle( mExpressionDialogTitle );
251 dlg.setAllowEvalErrors( mAllowEvalErrors );
252
253 if ( !mCustomChoices.isEmpty() )
254 {
255 dlg.expressionBuilder()->setCustomPreviewGenerator( mCustomPreviewLabel, mCustomChoices, mPreviewContextGenerator );
256 }
257
258 if ( !vl )
259 dlg.expressionBuilder()->expressionTree()->loadFieldNames( mFieldProxyModel->sourceFieldModel()->fields() );
260
261 if ( dlg.exec() )
262 {
263 const QString newExpression = dlg.expressionText();
264 setField( newExpression );
265 }
266}
267
268void QgsFieldExpressionWidget::expressionEdited( const QString &expression )
269{
272}
273
275{
276 const QString expression = mCombo->lineEdit()->text();
277 mFieldProxyModel->sourceFieldModel()->setExpression( expression );
278 const QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
279 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
280 mCombo->setCurrentIndex( proxyIndex.row() );
282}
283
285{
286 if ( event->type() == QEvent::EnabledChange )
287 {
289 }
290}
291
292void QgsFieldExpressionWidget::reloadLayer()
293{
294 setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
295}
296
297void QgsFieldExpressionWidget::beforeResetModel()
298{
299 // Backup expression
300 mBackupExpression = mCombo->currentText();
301}
302
303void QgsFieldExpressionWidget::afterResetModel()
304{
305 // Restore expression
306 mCombo->lineEdit()->setText( mBackupExpression );
307}
308
309bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
310{
311 if ( watched == mCombo && event->type() == QEvent::KeyPress )
312 {
313 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
314 if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
315 {
317 return true;
318 }
319 }
320 return QObject::eventFilter( watched, event );
321}
322
324{
325 return mAllowEvalErrors;
326}
327
329{
330 if ( allowEvalErrors == mAllowEvalErrors )
331 return;
332
333 mAllowEvalErrors = allowEvalErrors;
335}
336
337
339{
340 return mButton->isVisibleTo( this );
341}
342
344{
345 if ( visible == buttonVisible() )
346 return;
347
348 mButton->setVisible( visible );
350}
351
353{
355
356 bool isExpression, isValid;
357 const QString fieldName = currentField( &isExpression, &isValid );
358
359 // display tooltip if widget is shorter than expression
360 const QFontMetrics metrics( mCombo->lineEdit()->font() );
361 if ( metrics.boundingRect( fieldName ).width() > mCombo->lineEdit()->width() )
362 {
363 mCombo->setToolTip( fieldName );
364 }
365 else
366 {
367 mCombo->setToolTip( QString() );
368 }
369
370 emit fieldChanged( fieldName );
371 emit fieldChanged( fieldName, isValid );
372}
373
374void QgsFieldExpressionWidget::updateLineEditStyle( const QString &expression )
375{
376 QString stylesheet;
377 if ( !isEnabled() )
378 {
379 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::gray ).name() );
380 }
381 else
382 {
383 bool isExpression, isValid;
384 if ( !expression.isEmpty() )
385 {
386 isExpression = true;
387 isValid = isExpressionValid( expression );
388 }
389 else
390 {
391 currentField( &isExpression, &isValid );
392 }
393 QFont font = mCombo->lineEdit()->font();
394 font.setItalic( isExpression );
395 mCombo->lineEdit()->setFont( font );
396
397 if ( isExpression && !isValid )
398 {
399 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::red ).name() );
400 }
401 }
402 mCombo->lineEdit()->setStyleSheet( stylesheet );
403}
404
405bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
406{
407 QgsExpression expression( expressionStr );
408 expression.prepare( &mExpressionContext );
409 return !expression.hasParserError();
410}
411
413{
414 mExpressionContext.appendScope( scope );
415}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
A generic dialog for building expression strings.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
QgsExpressionBuilderWidget * expressionBuilder()
The builder widget that is used by the dialog.
void setAllowEvalErrors(bool allowEvalErrors)
Allow accepting expressions with evaluation errors.
QgsExpressionTreeView * expressionTree() const
Returns the expression tree.
void setCustomPreviewGenerator(const QString &label, const QList< QPair< QString, QVariant > > &choices, const std::function< QgsExpressionContext(const QVariant &) > &previewContextGenerator)
Sets the widget to run using a custom preview generator.
Abstract interface for generating an expression context.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void loadFieldNames(const QgsFields &fields)
This allows loading fields without specifying a layer.
Class for parsing and evaluation of expressions (formerly called "search strings").
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
static bool checkExpression(const QString &text, const QgsExpressionContext *context, QString &errorMessage)
Tests whether a string is a valid expression.
void setExpressionDialogTitle(const QString &title)
define the title used in the expression dialog
void setAllowEmptyFieldName(bool allowEmpty)
Sets whether an optional empty field ("not set") option is shown in the combo box.
void setField(const QString &fieldName)
sets the current field or expression in the widget
bool isExpression() const
If the content is not just a simple field this method will return true.
void setCustomPreviewGenerator(const QString &label, const QList< QPair< QString, QVariant > > &choices, const std::function< QgsExpressionContext(const QVariant &) > &previewContextGenerator)
Sets the widget to run using a custom preview generator.
QgsFieldExpressionWidget(QWidget *parent=nullptr)
QgsFieldExpressionWidget creates a widget with a combo box to display the fields and expression and a...
void expressionEdited(const QString &expression)
when expression is edited by the user in the line edit, it will be checked for validity
void setButtonVisible(bool visible)
Set the visibility of the button.
QString asExpression() const
Returns the currently selected field or expression.
void setExpression(const QString &expression)
Sets the current expression text and if applicable also the field.
void setGeomCalculator(const QgsDistanceArea &da)
Sets the geometry calculator used in the expression dialog.
bool isValidExpression(QString *expressionError=nullptr) const
Returns true if the current expression is valid.
bool isExpressionValid(const QString &expressionStr)
void setFields(const QgsFields &fields)
Sets the fields used in the widget to fields, this allows the widget to work without a layer.
void changeEvent(QEvent *event) override
QgsFieldProxyModel::Filters filters
QString expression() const
Returns the currently selected field or expression.
void setRow(int row)
sets the current row in the widget
QgsVectorLayer * layer() const
Returns the layer currently associated with the widget.
void editExpression()
open the expression dialog to edit the current or add a new expression
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the current expression context.
void expressionEditingFinished()
when expression has been edited (finished) it will be added to the model
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
void updateLineEditStyle(const QString &expression=QString())
updateLineEditStyle will re-style (color/font) the line edit depending on content and status
void buttonVisibleChanged()
Emitted when the button visibility changes.
void registerExpressionContextGenerator(const QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
void setAllowEvalErrors(bool allowEvalErrors)
Allow accepting expressions with evaluation errors.
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
bool eventFilter(QObject *watched, QEvent *event) override
QString currentText() const
Returns the current text that is set in the expression area.
void setFilters(QgsFieldProxyModel::Filters filters)
setFilters allows filtering according to the type of field
void allowEvalErrorsChanged()
Allow accepting expressions with evaluation errors.
QString currentField(bool *isExpression=nullptr, bool *isValid=nullptr) const
currentField returns the currently selected field or expression if allowed
bool isField(const QString &expression) const
Returns true if a string represents a field reference, or false if it is an expression consisting of ...
void setAllowExpression(bool allowExpression)
Sets whether custom expressions are accepted and displayed in the model.
void setLayer(QgsVectorLayer *layer)
Set the layer from which fields are displayed.
void setExpression(const QString &expression)
Sets a single expression to be added after the fields at the end of the model.
QgsFields fields() const
Returns the fields currently shown in the model.
void setFields(const QgsFields &fields)
Manually sets the fields to use for the model.
bool allowEmptyFieldName
Definition: qgsfieldmodel.h:42
QgsVectorLayer * layer
Definition: qgsfieldmodel.h:43
QModelIndex indexFromName(const QString &fieldName)
Returns the index corresponding to a given fieldName.
void setAllowEmptyFieldName(bool allowEmpty)
Sets whether an optional empty field ("not set") option is present in the model.
The QgsFieldProxyModel class provides an easy to use model to display the list of fields of a layer.
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
QFlags< Filter > Filters
QgsFieldProxyModel * setFilters(QgsFieldProxyModel::Filters filters)
Set flags that affect how fields are filtered in the model.
Container of fields for a vector layer.
Definition: qgsfields.h:45
Base class for all map layer types.
Definition: qgsmaplayer.h:75
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Represents a vector layer which manages a vector based data sets.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void updatedFields()
Emitted whenever the fields available from this layer have been changed.