QGIS API Documentation 4.1.0-Master (60fea48833c)
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();
73
74 mCombo->installEventFilter( this );
75}
76
78{
79 mExpressionDialogTitle = title;
80}
81
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 const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator
172)
173{
174 mCustomPreviewLabel = label;
175 mCustomChoices = choices;
176 mPreviewContextGenerator = previewContextGenerator;
177}
178
180{
181 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
182
183 if ( mFieldProxyModel->sourceFieldModel()->layer() )
184 disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
185
186 if ( vl )
187 mExpressionContext = vl->createExpressionContext();
188 else
189 mExpressionContext = QgsProject::instance()->createExpressionContext();
190
191 mFieldProxyModel->sourceFieldModel()->setLayer( vl );
192
193 if ( mFieldProxyModel->sourceFieldModel()->layer() )
194 connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
195}
196
197void QgsFieldExpressionWidget::setField( const QString &fieldName )
198{
199 if ( fieldName.isEmpty() )
200 {
201 setRow( -1 );
202 emit fieldChanged( QString() );
203 emit fieldChanged( QString(), true );
204 return;
205 }
206
207 if ( fieldName.size() > mCombo->lineEdit()->maxLength() )
208 {
209 mCombo->lineEdit()->setMaxLength( fieldName.size() );
210 }
211
212 QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
213 if ( !idx.isValid() )
214 {
215 // try to remove quotes and white spaces
216 QString simpleFieldName = fieldName.trimmed();
217 if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
218 {
219 simpleFieldName.remove( 0, 1 ).chop( 1 );
220 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
221 }
222
223 if ( !idx.isValid() )
224 {
225 // new expression
226 mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
227 idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
228 }
229 }
230 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
231 mCombo->setCurrentIndex( proxyIndex.row() );
233}
234
236{
237 mFieldProxyModel->sourceFieldModel()->setFields( fields );
238}
239
244
246{
247 const QString currentExpression = asExpression();
248 QgsVectorLayer *vl = layer();
249
250 const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
251
252 QgsExpressionBuilderDialog dlg( vl, currentExpression, this, u"generic"_s, context );
253 if ( mDistanceArea )
254 {
255 dlg.setGeomCalculator( *mDistanceArea );
256 }
257 dlg.setWindowTitle( mExpressionDialogTitle );
258 dlg.setAllowEvalErrors( mAllowEvalErrors );
259
260 if ( !mCustomChoices.isEmpty() )
261 {
262 dlg.expressionBuilder()->setCustomPreviewGenerator( mCustomPreviewLabel, mCustomChoices, mPreviewContextGenerator );
263 }
264
265 if ( !vl )
266 dlg.expressionBuilder()->expressionTree()->loadFieldNames( mFieldProxyModel->sourceFieldModel()->fields() );
267
268 if ( dlg.exec() )
269 {
270 const QString newExpression = dlg.expressionText();
271 setField( newExpression );
272 }
273}
274
280
282{
283 const QString expression = mCombo->lineEdit()->text();
284 mFieldProxyModel->sourceFieldModel()->setExpression( expression );
285 const QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
286 const QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
287 mCombo->setCurrentIndex( proxyIndex.row() );
289}
290
292{
293 if ( event->type() == QEvent::EnabledChange )
294 {
296 }
297}
298
299void QgsFieldExpressionWidget::reloadLayer()
300{
301 setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
302}
303
304void QgsFieldExpressionWidget::beforeResetModel()
305{
306 // Backup expression
307 mBackupExpression = mCombo->currentText();
308}
309
310void QgsFieldExpressionWidget::afterResetModel()
311{
312 // Restore expression
313 mCombo->lineEdit()->setText( mBackupExpression );
314}
315
316bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
317{
318 if ( watched == mCombo && event->type() == QEvent::KeyPress )
319 {
320 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
321 if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
322 {
324 return true;
325 }
326 }
327 return QObject::eventFilter( watched, event );
328}
329
331{
332 return mAllowEvalErrors;
333}
334
336{
337 if ( allowEvalErrors == mAllowEvalErrors )
338 return;
339
340 mAllowEvalErrors = allowEvalErrors;
342}
343
344
346{
347 return mButton->isVisibleTo( this );
348}
349
351{
352 if ( visible == buttonVisible() )
353 return;
354
355 mButton->setVisible( visible );
357}
358
360{
362
363 bool isExpression, isValid;
364 const QString fieldName = currentField( &isExpression, &isValid );
365
366 // display tooltip if widget is shorter than expression
367 const QFontMetrics metrics( mCombo->lineEdit()->font() );
368 if ( metrics.boundingRect( fieldName ).width() > mCombo->lineEdit()->width() )
369 {
370 mCombo->setToolTip( fieldName );
371 }
372 else
373 {
374 mCombo->setToolTip( QString() );
375 }
376
377 emit fieldChanged( fieldName );
378 emit fieldChanged( fieldName, isValid );
379}
380
382{
383 QString stylesheet;
384 if ( !isEnabled() )
385 {
386 stylesheet = u"QLineEdit { color: %1; }"_s.arg( QColor( Qt::gray ).name() );
387 }
388 else
389 {
390 bool isExpression, isValid;
391 if ( !expression.isEmpty() )
392 {
393 isExpression = true;
394 isValid = isExpressionValid( expression );
395 }
396 else
397 {
398 currentField( &isExpression, &isValid );
399 }
400 QFont font = mCombo->lineEdit()->font();
401 font.setFamily( ( QgsCodeEditor::getMonospaceFont() ).family() );
402 font.setItalic( false );
403 mCombo->lineEdit()->setFont( font );
404
405 if ( isExpression && !isValid )
406 {
407 stylesheet = u"QLineEdit { color: %1; }"_s.arg( QColor( Qt::red ).name() );
408 }
409 }
410 mCombo->lineEdit()->setStyleSheet( stylesheet );
411}
412
413bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
414{
415 QgsExpression expression( expressionStr );
416 expression.prepare( &mExpressionContext );
417 return !expression.hasParserError();
418}
419
421{
422 mExpressionContext.appendScope( scope );
423}
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.