QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
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
18
19#include "qgsapplication.h"
20#include "qgsdistancearea.h"
24#include "qgsfieldmodel.h"
25#include "qgsfieldproxymodel.h"
26#include "qgsproject.h"
27#include "qgsvectorlayer.h"
28
29#include <QHBoxLayout>
30#include <QKeyEvent>
31#include <QObject>
32
33#include "moc_qgsfieldexpressionwidget.cpp"
34
36 : QWidget( parent )
37 , mExpressionDialogTitle( tr( "Expression Builder" ) )
38 , mDistanceArea( nullptr )
39
40{
41 QHBoxLayout *layout = new QHBoxLayout( this );
42 layout->setContentsMargins( 0, 0, 0, 0 );
43
44 mCombo = new QComboBox( this );
45 mCombo->setEditable( true );
46 mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
47 const int width = mCombo->minimumSizeHint().width();
48 mCombo->setMinimumWidth( width );
49
50 mFieldProxyModel = new QgsFieldProxyModel( mCombo );
51 mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
52 mCombo->setModel( mFieldProxyModel );
53
54 mButton = new QToolButton( this );
55 mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
56 mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
57
58 layout->addWidget( mCombo );
59 layout->addWidget( mButton );
60
61 connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
62 connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
63 connect( mCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
64 connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
65 connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
66 connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
67
68 mExpressionContext = QgsExpressionContext();
69 mExpressionContext << QgsExpressionContextUtils::globalScope()
71
72 mCombo->installEventFilter( this );
73}
74
76{
77 mExpressionDialogTitle = title;
78}
79
81{
82 mFieldProxyModel->setFilters( filters );
83}
84
86{
87 mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
88 mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
89}
90
92{
93 return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
94}
95
97{
98 QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
99 if ( !layout )
100 return;
101
102 if ( isLeft )
103 {
104 QLayoutItem *item = layout->takeAt( 1 );
105 layout->insertWidget( 0, item->widget() );
106 }
107 else
108 layout->addWidget( mCombo );
109}
110
112{
113 mDistanceArea = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
114}
115
117{
118 return mCombo->currentText();
119}
120
125
127{
128 return asExpression();
129}
130
131bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
132{
133 QString temp;
134 return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
135}
136
138{
139 return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
140}
141
142QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
143{
144 QString text = currentText();
145 const bool valueIsExpression = this->isExpression();
146 if ( isValid )
147 {
148 // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
149 *isValid = !valueIsExpression || isValidExpression();
150 }
151 if ( isExpression )
152 {
153 *isExpression = valueIsExpression;
154 }
155 return text;
156}
157
159{
160 return mFieldProxyModel->sourceFieldModel()->layer();
161}
162
164{
165 mExpressionContextGenerator = generator;
166}
167
168void QgsFieldExpressionWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
169{
170 mCustomPreviewLabel = label;
171 mCustomChoices = choices;
172 mPreviewContextGenerator = previewContextGenerator;
173}
174
176{
177 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
178
179 if ( mFieldProxyModel->sourceFieldModel()->layer() )
180 disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
181
182 if ( vl )
183 mExpressionContext = vl->createExpressionContext();
184 else
185 mExpressionContext = QgsProject::instance()->createExpressionContext();
186
187 mFieldProxyModel->sourceFieldModel()->setLayer( vl );
188
189 if ( mFieldProxyModel->sourceFieldModel()->layer() )
190 connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
191}
192
193void QgsFieldExpressionWidget::setField( const QString &fieldName )
194{
195 if ( fieldName.isEmpty() )
196 {
197 setRow( -1 );
198 emit fieldChanged( QString() );
199 emit fieldChanged( QString(), true );
200 return;
201 }
202
203 if ( fieldName.size() > mCombo->lineEdit()->maxLength() )
204 {
205 mCombo->lineEdit()->setMaxLength( fieldName.size() );
206 }
207
208 QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
209 if ( !idx.isValid() )
210 {
211 // try to remove quotes and white spaces
212 QString simpleFieldName = fieldName.trimmed();
213 if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
214 {
215 simpleFieldName.remove( 0, 1 ).chop( 1 );
216 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
217 }
218
219 if ( !idx.isValid() )
220 {
221 // new expression
222 mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
223 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
224 }
225 }
226 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
227 mCombo->setCurrentIndex( proxyIndex.row() );
229}
230
232{
233 mFieldProxyModel->sourceFieldModel()->setFields( fields );
234}
235
240
242{
243 const QString currentExpression = asExpression();
244 QgsVectorLayer *vl = layer();
245
246 const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
247
248 QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
249 if ( mDistanceArea )
250 {
251 dlg.setGeomCalculator( *mDistanceArea );
252 }
253 dlg.setWindowTitle( mExpressionDialogTitle );
254 dlg.setAllowEvalErrors( mAllowEvalErrors );
255
256 if ( !mCustomChoices.isEmpty() )
257 {
258 dlg.expressionBuilder()->setCustomPreviewGenerator( mCustomPreviewLabel, mCustomChoices, mPreviewContextGenerator );
259 }
260
261 if ( !vl )
262 dlg.expressionBuilder()->expressionTree()->loadFieldNames( mFieldProxyModel->sourceFieldModel()->fields() );
263
264 if ( dlg.exec() )
265 {
266 const QString newExpression = dlg.expressionText();
267 setField( newExpression );
268 }
269}
270
276
278{
279 const QString expression = mCombo->lineEdit()->text();
280 mFieldProxyModel->sourceFieldModel()->setExpression( expression );
281 const QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
282 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
283 mCombo->setCurrentIndex( proxyIndex.row() );
285}
286
288{
289 if ( event->type() == QEvent::EnabledChange )
290 {
292 }
293}
294
295void QgsFieldExpressionWidget::reloadLayer()
296{
297 setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
298}
299
300void QgsFieldExpressionWidget::beforeResetModel()
301{
302 // Backup expression
303 mBackupExpression = mCombo->currentText();
304}
305
306void QgsFieldExpressionWidget::afterResetModel()
307{
308 // Restore expression
309 mCombo->lineEdit()->setText( mBackupExpression );
310}
311
312bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
313{
314 if ( watched == mCombo && event->type() == QEvent::KeyPress )
315 {
316 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
317 if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
318 {
320 return true;
321 }
322 }
323 return QObject::eventFilter( watched, event );
324}
325
327{
328 return mAllowEvalErrors;
329}
330
332{
333 if ( allowEvalErrors == mAllowEvalErrors )
334 return;
335
336 mAllowEvalErrors = allowEvalErrors;
338}
339
340
342{
343 return mButton->isVisibleTo( this );
344}
345
347{
348 if ( visible == buttonVisible() )
349 return;
350
351 mButton->setVisible( visible );
353}
354
356{
358
359 bool isExpression, isValid;
360 const QString fieldName = currentField( &isExpression, &isValid );
361
362 // display tooltip if widget is shorter than expression
363 const QFontMetrics metrics( mCombo->lineEdit()->font() );
364 if ( metrics.boundingRect( fieldName ).width() > mCombo->lineEdit()->width() )
365 {
366 mCombo->setToolTip( fieldName );
367 }
368 else
369 {
370 mCombo->setToolTip( QString() );
371 }
372
373 emit fieldChanged( fieldName );
374 emit fieldChanged( fieldName, isValid );
375}
376
378{
379 QString stylesheet;
380 if ( !isEnabled() )
381 {
382 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::gray ).name() );
383 }
384 else
385 {
386 bool isExpression, isValid;
387 if ( !expression.isEmpty() )
388 {
389 isExpression = true;
390 isValid = isExpressionValid( expression );
391 }
392 else
393 {
394 currentField( &isExpression, &isValid );
395 }
396 QFont font = mCombo->lineEdit()->font();
397 font.setFamily( ( QgsCodeEditor::getMonospaceFont() ).family() );
398 font.setItalic( false );
399 mCombo->lineEdit()->setFont( font );
400
401 if ( isExpression && !isValid )
402 {
403 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::red ).name() );
404 }
405 }
406 mCombo->lineEdit()->setStyleSheet( stylesheet );
407}
408
409bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
410{
411 QgsExpression expression( expressionStr );
412 expression.prepare( &mExpressionContext );
413 return !expression.hasParserError();
414}
415
417{
418 mExpressionContext.appendScope( scope );
419}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QFont getMonospaceFont()
Returns the monospaced font to use for code editors.
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.
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 loadFieldNames(const QgsFields &fields)
This allows loading fields without specifying a layer.
Handles 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.
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
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.
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
QgsVectorLayer * layer
A proxy model to filter the list of fields of a layer.
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
QFlags< Filter > Filters
Container of fields for a vector layer.
Definition qgsfields.h:46
Base class for all map layer types.
Definition qgsmaplayer.h:80
static QgsProject * instance()
Returns the QgsProject singleton instance.
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 dataset.
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.