QGIS API Documentation 3.99.0-Master (8e76e220402)
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#include <QString>
33
34#include "moc_qgsfieldexpressionwidget.cpp"
35
36using namespace Qt::StringLiterals;
37
39 : QWidget( parent )
40 , mExpressionDialogTitle( tr( "Expression Builder" ) )
41 , mDistanceArea( nullptr )
42
43{
44 QHBoxLayout *layout = new QHBoxLayout( this );
45 layout->setContentsMargins( 0, 0, 0, 0 );
46
47 mCombo = new QComboBox( this );
48 mCombo->setEditable( true );
49 mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
50 const int width = mCombo->minimumSizeHint().width();
51 mCombo->setMinimumWidth( width );
52
53 mFieldProxyModel = new QgsFieldProxyModel( mCombo );
54 mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
55 mCombo->setModel( mFieldProxyModel );
56
57 mButton = new QToolButton( this );
58 mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
59 mButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
60
61 layout->addWidget( mCombo );
62 layout->addWidget( mButton );
63
64 connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
65 connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
66 connect( mCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
67 connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
68 connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
69 connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
70
71 mExpressionContext = QgsExpressionContext();
72 mExpressionContext << QgsExpressionContextUtils::globalScope()
74
75 mCombo->installEventFilter( this );
76}
77
79{
80 mExpressionDialogTitle = title;
81}
82
84{
85 mFieldProxyModel->setFilters( filters );
86}
87
89{
90 mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
91 mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
92}
93
95{
96 return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
97}
98
100{
101 QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
102 if ( !layout )
103 return;
104
105 if ( isLeft )
106 {
107 QLayoutItem *item = layout->takeAt( 1 );
108 layout->insertWidget( 0, item->widget() );
109 }
110 else
111 layout->addWidget( mCombo );
112}
113
115{
116 mDistanceArea = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
117}
118
120{
121 return mCombo->currentText();
122}
123
128
130{
131 return asExpression();
132}
133
134bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
135{
136 QString temp;
137 return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
138}
139
141{
142 return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
143}
144
145QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
146{
147 QString text = currentText();
148 const bool valueIsExpression = this->isExpression();
149 if ( isValid )
150 {
151 // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
152 *isValid = !valueIsExpression || isValidExpression();
153 }
154 if ( isExpression )
155 {
156 *isExpression = valueIsExpression;
157 }
158 return text;
159}
160
162{
163 return mFieldProxyModel->sourceFieldModel()->layer();
164}
165
167{
168 mExpressionContextGenerator = generator;
169}
170
171void QgsFieldExpressionWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
172{
173 mCustomPreviewLabel = label;
174 mCustomChoices = choices;
175 mPreviewContextGenerator = previewContextGenerator;
176}
177
179{
180 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
181
182 if ( mFieldProxyModel->sourceFieldModel()->layer() )
183 disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
184
185 if ( vl )
186 mExpressionContext = vl->createExpressionContext();
187 else
188 mExpressionContext = QgsProject::instance()->createExpressionContext();
189
190 mFieldProxyModel->sourceFieldModel()->setLayer( vl );
191
192 if ( mFieldProxyModel->sourceFieldModel()->layer() )
193 connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
194}
195
196void QgsFieldExpressionWidget::setField( const QString &fieldName )
197{
198 if ( fieldName.isEmpty() )
199 {
200 setRow( -1 );
201 emit fieldChanged( QString() );
202 emit fieldChanged( QString(), true );
203 return;
204 }
205
206 if ( fieldName.size() > mCombo->lineEdit()->maxLength() )
207 {
208 mCombo->lineEdit()->setMaxLength( fieldName.size() );
209 }
210
211 QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
212 if ( !idx.isValid() )
213 {
214 // try to remove quotes and white spaces
215 QString simpleFieldName = fieldName.trimmed();
216 if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
217 {
218 simpleFieldName.remove( 0, 1 ).chop( 1 );
219 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
220 }
221
222 if ( !idx.isValid() )
223 {
224 // new expression
225 mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
226 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
227 }
228 }
229 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
230 mCombo->setCurrentIndex( proxyIndex.row() );
232}
233
235{
236 mFieldProxyModel->sourceFieldModel()->setFields( fields );
237}
238
243
245{
246 const QString currentExpression = asExpression();
247 QgsVectorLayer *vl = layer();
248
249 const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
250
251 QgsExpressionBuilderDialog dlg( vl, currentExpression, this, u"generic"_s, context );
252 if ( mDistanceArea )
253 {
254 dlg.setGeomCalculator( *mDistanceArea );
255 }
256 dlg.setWindowTitle( mExpressionDialogTitle );
257 dlg.setAllowEvalErrors( mAllowEvalErrors );
258
259 if ( !mCustomChoices.isEmpty() )
260 {
261 dlg.expressionBuilder()->setCustomPreviewGenerator( mCustomPreviewLabel, mCustomChoices, mPreviewContextGenerator );
262 }
263
264 if ( !vl )
265 dlg.expressionBuilder()->expressionTree()->loadFieldNames( mFieldProxyModel->sourceFieldModel()->fields() );
266
267 if ( dlg.exec() )
268 {
269 const QString newExpression = dlg.expressionText();
270 setField( newExpression );
271 }
272}
273
279
281{
282 const QString expression = mCombo->lineEdit()->text();
283 mFieldProxyModel->sourceFieldModel()->setExpression( expression );
284 const QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
285 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
286 mCombo->setCurrentIndex( proxyIndex.row() );
288}
289
291{
292 if ( event->type() == QEvent::EnabledChange )
293 {
295 }
296}
297
298void QgsFieldExpressionWidget::reloadLayer()
299{
300 setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
301}
302
303void QgsFieldExpressionWidget::beforeResetModel()
304{
305 // Backup expression
306 mBackupExpression = mCombo->currentText();
307}
308
309void QgsFieldExpressionWidget::afterResetModel()
310{
311 // Restore expression
312 mCombo->lineEdit()->setText( mBackupExpression );
313}
314
315bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
316{
317 if ( watched == mCombo && event->type() == QEvent::KeyPress )
318 {
319 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
320 if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
321 {
323 return true;
324 }
325 }
326 return QObject::eventFilter( watched, event );
327}
328
330{
331 return mAllowEvalErrors;
332}
333
335{
336 if ( allowEvalErrors == mAllowEvalErrors )
337 return;
338
339 mAllowEvalErrors = allowEvalErrors;
341}
342
343
345{
346 return mButton->isVisibleTo( this );
347}
348
350{
351 if ( visible == buttonVisible() )
352 return;
353
354 mButton->setVisible( visible );
356}
357
359{
361
362 bool isExpression, isValid;
363 const QString fieldName = currentField( &isExpression, &isValid );
364
365 // display tooltip if widget is shorter than expression
366 const QFontMetrics metrics( mCombo->lineEdit()->font() );
367 if ( metrics.boundingRect( fieldName ).width() > mCombo->lineEdit()->width() )
368 {
369 mCombo->setToolTip( fieldName );
370 }
371 else
372 {
373 mCombo->setToolTip( QString() );
374 }
375
376 emit fieldChanged( fieldName );
377 emit fieldChanged( fieldName, isValid );
378}
379
381{
382 QString stylesheet;
383 if ( !isEnabled() )
384 {
385 stylesheet = u"QLineEdit { color: %1; }"_s.arg( QColor( Qt::gray ).name() );
386 }
387 else
388 {
389 bool isExpression, isValid;
390 if ( !expression.isEmpty() )
391 {
392 isExpression = true;
393 isValid = isExpressionValid( expression );
394 }
395 else
396 {
397 currentField( &isExpression, &isValid );
398 }
399 QFont font = mCombo->lineEdit()->font();
400 font.setFamily( ( QgsCodeEditor::getMonospaceFont() ).family() );
401 font.setItalic( false );
402 mCombo->lineEdit()->setFont( font );
403
404 if ( isExpression && !isValid )
405 {
406 stylesheet = u"QLineEdit { color: %1; }"_s.arg( QColor( Qt::red ).name() );
407 }
408 }
409 mCombo->lineEdit()->setStyleSheet( stylesheet );
410}
411
412bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
413{
414 QgsExpression expression( expressionStr );
415 expression.prepare( &mExpressionContext );
416 return !expression.hasParserError();
417}
418
420{
421 mExpressionContext.appendScope( scope );
422}
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:83
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.