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