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