QGIS API Documentation  3.6.0-Noosa (5873452)
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"
30 
32  : QWidget( parent )
33  , mExpressionDialogTitle( tr( "Expression Dialog" ) )
34  , mDa( nullptr )
35 
36 {
37  QHBoxLayout *layout = new QHBoxLayout( this );
38  layout->setContentsMargins( 0, 0, 0, 0 );
39 
40  mCombo = new QComboBox( this );
41  mCombo->setEditable( true );
42  mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
43  int width = mCombo->minimumSizeHint().width();
44  mCombo->setMinimumWidth( width );
45 
46  mFieldProxyModel = new QgsFieldProxyModel( mCombo );
47  mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
48  mCombo->setModel( mFieldProxyModel );
49 
50  mButton = new QToolButton( this );
51  mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
52  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
53 
54  layout->addWidget( mCombo );
55  layout->addWidget( mButton );
56 
57  // give focus to the combo
58  // hence if the widget is used as a delegate
59  // it will allow pressing on the expression dialog button
60  setFocusProxy( mCombo );
61 
62  connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
63  connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
64  connect( mCombo, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
65  connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
66  connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
67  connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
68 
69  mExpressionContext = QgsExpressionContext();
70  mExpressionContext << QgsExpressionContextUtils::globalScope()
72 
73  mCombo->installEventFilter( this );
74 }
75 
77 {
78  mExpressionDialogTitle = title;
79 }
80 
81 void QgsFieldExpressionWidget::setFilters( QgsFieldProxyModel::Filters filters )
82 {
83  mFieldProxyModel->setFilters( filters );
84 }
85 
87 {
88  mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
89  mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
90 }
91 
93 {
94  return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
95 }
96 
98 {
99  QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
100  if ( !layout )
101  return;
102 
103  if ( isLeft )
104  {
105  QLayoutItem *item = layout->takeAt( 1 );
106  layout->insertWidget( 0, item->widget() );
107  }
108  else
109  layout->addWidget( mCombo );
110 }
111 
113 {
114  mDa = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
115 }
116 
118 {
119  return mCombo->currentText();
120 }
121 
123 {
125 }
126 
128 {
129  return asExpression();
130 }
131 
132 bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
133 {
134  QString temp;
135  return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
136 }
137 
139 {
140  return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
141 }
142 
143 QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
144 {
145  QString text = currentText();
146  bool valueIsExpression = this->isExpression();
147  if ( isValid )
148  {
149  // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
150  *isValid = !valueIsExpression || isValidExpression();
151  }
152  if ( isExpression )
153  {
154  *isExpression = valueIsExpression;
155  }
156  return text;
157 }
158 
160 {
161  return mFieldProxyModel->sourceFieldModel()->layer();
162 }
163 
165 {
166  mExpressionContextGenerator = generator;
167 }
168 
170 {
171  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
172 
173  if ( mFieldProxyModel->sourceFieldModel()->layer() )
174  disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
175 
176  if ( vl )
177  mExpressionContext = vl->createExpressionContext();
178  else
179  mExpressionContext = QgsProject::instance()->createExpressionContext();
180 
181  mFieldProxyModel->sourceFieldModel()->setLayer( vl );
182 
183  if ( mFieldProxyModel->sourceFieldModel()->layer() )
184  connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
185 }
186 
187 void QgsFieldExpressionWidget::setField( const QString &fieldName )
188 {
189  if ( fieldName.isEmpty() )
190  {
191  setRow( -1 );
192  emit fieldChanged( QString() );
193  emit fieldChanged( QString(), true );
194  return;
195  }
196 
197  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
198  if ( !idx.isValid() )
199  {
200  // try to remove quotes and white spaces
201  QString simpleFieldName = fieldName.trimmed();
202  if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
203  {
204  simpleFieldName.remove( 0, 1 ).chop( 1 );
205  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
206  }
207 
208  if ( !idx.isValid() )
209  {
210  // new expression
211  mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
212  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
213  }
214  }
215  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
216  mCombo->setCurrentIndex( proxyIndex.row() );
218 }
219 
221 {
222  setField( expression );
223 }
224 
226 {
227  QString currentExpression = currentText();
228  QgsVectorLayer *vl = layer();
229 
230  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
231 
232  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
233  if ( mDa )
234  {
235  dlg.setGeomCalculator( *mDa );
236  }
237  dlg.setWindowTitle( mExpressionDialogTitle );
238  dlg.setAllowEvalErrors( mAllowEvalErrors );
239 
240  if ( dlg.exec() )
241  {
242  QString newExpression = dlg.expressionText();
243  setField( newExpression );
244  }
245 }
246 
248 {
249  updateLineEditStyle( expression );
250  emit fieldChanged( expression, isValidExpression() );
251 }
252 
254 {
255  const QString expression = mCombo->lineEdit()->text();
256  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
257  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
258  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
259  mCombo->setCurrentIndex( proxyIndex.row() );
261 }
262 
264 {
265  if ( event->type() == QEvent::EnabledChange )
266  {
268  }
269 }
270 
271 void QgsFieldExpressionWidget::reloadLayer()
272 {
273  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
274 }
275 
276 void QgsFieldExpressionWidget::beforeResetModel()
277 {
278  // Backup expression
279  mBackupExpression = mCombo->currentText();
280 }
281 
282 void QgsFieldExpressionWidget::afterResetModel()
283 {
284  // Restore expression
285  mCombo->lineEdit()->setText( mBackupExpression );
286 }
287 
288 bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
289 {
290  if ( watched == mCombo && event->type() == QEvent::KeyPress )
291  {
292  QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
293  if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
294  {
296  return true;
297  }
298  }
299  return QObject::eventFilter( watched, event );
300 }
301 
303 {
304  return mAllowEvalErrors;
305 }
306 
308 {
309  if ( allowEvalErrors == mAllowEvalErrors )
310  return;
311 
312  mAllowEvalErrors = allowEvalErrors;
313  emit allowEvalErrorsChanged();
314 }
315 
317 {
319 
320  bool isExpression, isValid;
321  QString fieldName = currentField( &isExpression, &isValid );
322 
323  // display tooltip if widget is shorter than expression
324  QFontMetrics metrics( mCombo->lineEdit()->font() );
325  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
326  {
327  mCombo->setToolTip( fieldName );
328  }
329  else
330  {
331  mCombo->setToolTip( QString() );
332  }
333 
334  emit fieldChanged( fieldName );
335  emit fieldChanged( fieldName, isValid );
336 }
337 
339 {
340  QPalette palette = mCombo->lineEdit()->palette();
341  if ( !isEnabled() )
342  {
343  palette.setColor( QPalette::Text, Qt::gray );
344  }
345  else
346  {
347  bool isExpression, isValid;
348  if ( !expression.isEmpty() )
349  {
350  isExpression = true;
351  isValid = isExpressionValid( expression );
352  }
353  else
354  {
355  currentField( &isExpression, &isValid );
356  }
357  QFont font = mCombo->lineEdit()->font();
358  font.setItalic( isExpression );
359  mCombo->lineEdit()->setFont( font );
360 
361  if ( isExpression && !isValid )
362  {
363  palette.setColor( QPalette::Text, Qt::red );
364  }
365  else
366  {
367  palette.setColor( QPalette::Text, Qt::black );
368  }
369  }
370  mCombo->lineEdit()->setPalette( palette );
371 }
372 
373 bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
374 {
375  QgsExpression expression( expressionStr );
376  expression.prepare( &mExpressionContext );
377  return !expression.hasParserError();
378 }
379 
381 {
382  mExpressionContext.appendScope( scope );
383 }
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Base class for all map layer types.
Definition: qgsmaplayer.h:64
void setExpression(const QString &expression)
Sets the current expression text and if applicable also the field.
void setAllowEmptyFieldName(bool allowEmpty)
Sets whether an optional empty field ("not set") option is shown in the combo box.
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
void changeEvent(QEvent *event) override
QString asExpression() const
Returns the currently selected field or expression.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool allowEmptyFieldName
Definition: qgsfieldmodel.h:42
void setExpression(const QString &expression)
Sets a single expression to be added after the fields at the end of the model.
void editExpression()
open the expression dialog to edit the current or add a new expression
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsFieldProxyModel * setFilters(QgsFieldProxyModel::Filters filters)
Set flags that affect how fields are filtered in the model.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
void setAllowEvalErrors(bool allowEvalErrors)
Allow accepting expressions with evaluation errors.
QgsFieldProxyModel::Filters filters() const
currently used filter on list of fields
static bool checkExpression(const QString &text, const QgsExpressionContext *context, QString &errorMessage)
Tests whether a string is a valid expression.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
void expressionEdited(const QString &expression)
when expression is edited by the user in the line edit, it will be checked for validity ...
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...
bool isExpression() const
If the content is not just a simple field this method will return true.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
bool isValidExpression(QString *expressionError=nullptr) const
Returns true if the current expression is valid.
void setLayer(QgsVectorLayer *layer)
Set the layer from which fields are displayed.
void registerExpressionContextGenerator(const QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the current expression context.
void setAllowExpression(bool allowExpression)
Sets whether custom expressions are accepted and displayed in the model.
bool isExpressionValid(const QString &expressionStr)
void setGeomCalculator(const QgsDistanceArea &da)
Sets the geometry calculator used in the expression dialog.
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Abstract interface for generating an expression context.
QString expression() const
Returns the currently selected field or expression.
bool allowEmptyFieldName() const
Returns true if the combo box allows the empty field ("not set") choice.
bool eventFilter(QObject *watched, QEvent *event) override
void fieldChanged(const QString &fieldName)
the signal is emitted when the currently selected field changes
bool allowEvalErrors() const
Allow accepting expressions with evaluation errors.
QgsVectorLayer layer
Definition: qgsfieldmodel.h:43
A general purpose distance and area calculator, capable of performing ellipsoid based calculations...
void allowEvalErrorsChanged()
Allow accepting expressions with evaluation errors.
void setAllowEvalErrors(bool allowEvalErrors)
Allow accepting expressions with evaluation errors.
void expressionEditingFinished()
when expression has been edited (finished) it will be added to the model
void setRow(int row)
sets the current row in the widget
void setExpressionDialogTitle(const QString &title)
define the title used in the expression dialog
QgsVectorLayer * layer() const
Returns the layer currently associated with the widget.
void setFilters(QgsFieldProxyModel::Filters filters)
setFilters allows fitering according to the type of field
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
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 appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:430
void setField(const QString &fieldName)
sets the current field or expression in the widget
void updateLineEditStyle(const QString &expression=QString())
updateLineEditStyle will re-style (color/font) the line edit depending on content and status ...
QModelIndex indexFromName(const QString &fieldName)
Returns the index corresponding to a given fieldName.
Represents a vector layer which manages a vector based data sets.
void updatedFields()
Is emitted, whenever the fields available from this layer have been changed.
A generic dialog for building expression strings.
QgsFieldExpressionWidget(QWidget *parent=nullptr)
QgsFieldExpressionWidget creates a widget with a combo box to display the fields and expression and a...
QString currentText() const
Returns the current text that is set in the expression area.
QString currentField(bool *isExpression=nullptr, bool *isValid=nullptr) const
currentField returns the currently selected field or expression if allowed