QGIS API Documentation 3.28.14-Firenze (exported)
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
7 Email : denis.rouzaud@gmail.com
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 Dialog" ) )
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 // give focus to the combo
59 // hence if the widget is used as a delegate
60 // it will allow pressing on the expression dialog button
61 setFocusProxy( mCombo );
62
63 connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
64 connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
65 connect( mCombo, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
66 connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
67 connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
68 connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
69
70 mExpressionContext = QgsExpressionContext();
71 mExpressionContext << QgsExpressionContextUtils::globalScope()
73
74 mCombo->installEventFilter( this );
75}
76
78{
79 mExpressionDialogTitle = title;
80}
81
82void QgsFieldExpressionWidget::setFilters( QgsFieldProxyModel::Filters filters )
83{
84 mFieldProxyModel->setFilters( filters );
85}
86
88{
89 mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
90 mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
91}
92
94{
95 return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
96}
97
99{
100 QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
101 if ( !layout )
102 return;
103
104 if ( isLeft )
105 {
106 QLayoutItem *item = layout->takeAt( 1 );
107 layout->insertWidget( 0, item->widget() );
108 }
109 else
110 layout->addWidget( mCombo );
111}
112
114{
115 mDistanceArea = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
116}
117
119{
120 return mCombo->currentText();
121}
122
127
129{
130 return asExpression();
131}
132
133bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
134{
135 QString temp;
136 return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
137}
138
140{
141 return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
142}
143
144QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
145{
146 QString text = currentText();
147 const bool valueIsExpression = this->isExpression();
148 if ( isValid )
149 {
150 // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
151 *isValid = !valueIsExpression || isValidExpression();
152 }
153 if ( isExpression )
154 {
155 *isExpression = valueIsExpression;
156 }
157 return text;
158}
159
161{
162 return mFieldProxyModel->sourceFieldModel()->layer();
163}
164
166{
167 mExpressionContextGenerator = generator;
168}
169
171{
172 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
173
174 if ( mFieldProxyModel->sourceFieldModel()->layer() )
175 disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
176
177 if ( vl )
178 mExpressionContext = vl->createExpressionContext();
179 else
180 mExpressionContext = QgsProject::instance()->createExpressionContext();
181
182 mFieldProxyModel->sourceFieldModel()->setLayer( vl );
183
184 if ( mFieldProxyModel->sourceFieldModel()->layer() )
185 connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
186}
187
188void QgsFieldExpressionWidget::setField( const QString &fieldName )
189{
190 if ( fieldName.isEmpty() )
191 {
192 setRow( -1 );
193 emit fieldChanged( QString() );
194 emit fieldChanged( QString(), true );
195 return;
196 }
197
198 if ( fieldName.size() > mCombo->lineEdit()->maxLength() )
199 {
200 mCombo->lineEdit()->setMaxLength( fieldName.size() );
201 }
202
203 QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
204 if ( !idx.isValid() )
205 {
206 // try to remove quotes and white spaces
207 QString simpleFieldName = fieldName.trimmed();
208 if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
209 {
210 simpleFieldName.remove( 0, 1 ).chop( 1 );
211 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
212 }
213
214 if ( !idx.isValid() )
215 {
216 // new expression
217 mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
218 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
219 }
220 }
221 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
222 mCombo->setCurrentIndex( proxyIndex.row() );
224}
225
227{
228 mFieldProxyModel->sourceFieldModel()->setFields( fields );
229}
230
231void QgsFieldExpressionWidget::setExpression( const QString &expression )
232{
234}
235
237{
238 const QString currentExpression = asExpression();
239 QgsVectorLayer *vl = layer();
240
241 const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
242
243 QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
244 if ( mDistanceArea )
245 {
246 dlg.setGeomCalculator( *mDistanceArea );
247 }
248 dlg.setWindowTitle( mExpressionDialogTitle );
249 dlg.setAllowEvalErrors( mAllowEvalErrors );
250
251 if ( !vl )
252 dlg.expressionBuilder()->expressionTree()->loadFieldNames( mFieldProxyModel->sourceFieldModel()->fields() );
253
254 if ( dlg.exec() )
255 {
256 const QString newExpression = dlg.expressionText();
257 setField( newExpression );
258 }
259}
260
266
268{
269 const QString expression = mCombo->lineEdit()->text();
270 mFieldProxyModel->sourceFieldModel()->setExpression( expression );
271 const QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
272 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
273 mCombo->setCurrentIndex( proxyIndex.row() );
275}
276
278{
279 if ( event->type() == QEvent::EnabledChange )
280 {
282 }
283}
284
285void QgsFieldExpressionWidget::reloadLayer()
286{
287 setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
288}
289
290void QgsFieldExpressionWidget::beforeResetModel()
291{
292 // Backup expression
293 mBackupExpression = mCombo->currentText();
294}
295
296void QgsFieldExpressionWidget::afterResetModel()
297{
298 // Restore expression
299 mCombo->lineEdit()->setText( mBackupExpression );
300}
301
302bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
303{
304 if ( watched == mCombo && event->type() == QEvent::KeyPress )
305 {
306 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
307 if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
308 {
310 return true;
311 }
312 }
313 return QObject::eventFilter( watched, event );
314}
315
317{
318 return mAllowEvalErrors;
319}
320
322{
323 if ( allowEvalErrors == mAllowEvalErrors )
324 return;
325
326 mAllowEvalErrors = allowEvalErrors;
328}
329
331{
333
334 bool isExpression, isValid;
335 const QString fieldName = currentField( &isExpression, &isValid );
336
337 // display tooltip if widget is shorter than expression
338 const QFontMetrics metrics( mCombo->lineEdit()->font() );
339 if ( metrics.boundingRect( fieldName ).width() > mCombo->lineEdit()->width() )
340 {
341 mCombo->setToolTip( fieldName );
342 }
343 else
344 {
345 mCombo->setToolTip( QString() );
346 }
347
348 emit fieldChanged( fieldName );
349 emit fieldChanged( fieldName, isValid );
350}
351
352void QgsFieldExpressionWidget::updateLineEditStyle( const QString &expression )
353{
354 QString stylesheet;
355 if ( !isEnabled() )
356 {
357 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::gray ).name() );
358 }
359 else
360 {
361 bool isExpression, isValid;
362 if ( !expression.isEmpty() )
363 {
364 isExpression = true;
365 isValid = isExpressionValid( expression );
366 }
367 else
368 {
369 currentField( &isExpression, &isValid );
370 }
371 QFont font = mCombo->lineEdit()->font();
372 font.setItalic( isExpression );
373 mCombo->lineEdit()->setFont( font );
374
375 if ( isExpression && !isValid )
376 {
377 stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::red ).name() );
378 }
379 }
380 mCombo->lineEdit()->setStyleSheet( stylesheet );
381}
382
383bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
384{
385 QgsExpression expression( expressionStr );
386 expression.prepare( &mExpressionContext );
387 return !expression.hasParserError();
388}
389
391{
392 mExpressionContext.appendScope( scope );
393}
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.
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
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 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.
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:73
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.