QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 Dialog" ) )
35  , mDa( 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  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 
82 void 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  mDa = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
116 }
117 
119 {
120  return mCombo->currentText();
121 }
122 
124 {
126 }
127 
129 {
130  return asExpression();
131 }
132 
133 bool 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 
144 QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
145 {
146  QString text = currentText();
147  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 
188 void 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  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  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
217  mCombo->setCurrentIndex( proxyIndex.row() );
219 }
220 
222 {
223  setField( expression );
224 }
225 
227 {
228  QString currentExpression = currentText();
229  QgsVectorLayer *vl = layer();
230 
231  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
232 
233  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
234  if ( mDa )
235  {
236  dlg.setGeomCalculator( *mDa );
237  }
238  dlg.setWindowTitle( mExpressionDialogTitle );
239  dlg.setAllowEvalErrors( mAllowEvalErrors );
240 
241  if ( dlg.exec() )
242  {
243  QString newExpression = dlg.expressionText();
244  setField( newExpression );
245  }
246 }
247 
249 {
250  updateLineEditStyle( expression );
251  emit fieldChanged( expression, isValidExpression() );
252 }
253 
255 {
256  const QString expression = mCombo->lineEdit()->text();
257  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
258  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
259  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
260  mCombo->setCurrentIndex( proxyIndex.row() );
262 }
263 
265 {
266  if ( event->type() == QEvent::EnabledChange )
267  {
269  }
270 }
271 
272 void QgsFieldExpressionWidget::reloadLayer()
273 {
274  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
275 }
276 
277 void QgsFieldExpressionWidget::beforeResetModel()
278 {
279  // Backup expression
280  mBackupExpression = mCombo->currentText();
281 }
282 
283 void QgsFieldExpressionWidget::afterResetModel()
284 {
285  // Restore expression
286  mCombo->lineEdit()->setText( mBackupExpression );
287 }
288 
289 bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
290 {
291  if ( watched == mCombo && event->type() == QEvent::KeyPress )
292  {
293  QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
294  if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
295  {
297  return true;
298  }
299  }
300  return QObject::eventFilter( watched, event );
301 }
302 
304 {
305  return mAllowEvalErrors;
306 }
307 
309 {
310  if ( allowEvalErrors == mAllowEvalErrors )
311  return;
312 
313  mAllowEvalErrors = allowEvalErrors;
314  emit allowEvalErrorsChanged();
315 }
316 
318 {
320 
321  bool isExpression, isValid;
322  QString fieldName = currentField( &isExpression, &isValid );
323 
324  // display tooltip if widget is shorter than expression
325  QFontMetrics metrics( mCombo->lineEdit()->font() );
326  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
327  {
328  mCombo->setToolTip( fieldName );
329  }
330  else
331  {
332  mCombo->setToolTip( QString() );
333  }
334 
335  emit fieldChanged( fieldName );
336  emit fieldChanged( fieldName, isValid );
337 }
338 
340 {
341  QString stylesheet;
342  if ( !isEnabled() )
343  {
344  stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::gray ).name() );
345  }
346  else
347  {
348  bool isExpression, isValid;
349  if ( !expression.isEmpty() )
350  {
351  isExpression = true;
352  isValid = isExpressionValid( expression );
353  }
354  else
355  {
356  currentField( &isExpression, &isValid );
357  }
358  QFont font = mCombo->lineEdit()->font();
359  font.setItalic( isExpression );
360  mCombo->lineEdit()->setFont( font );
361 
362  if ( isExpression && !isValid )
363  {
364  stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::red ).name() );
365  }
366  }
367  mCombo->lineEdit()->setStyleSheet( stylesheet );
368 }
369 
370 bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
371 {
372  QgsExpression expression( expressionStr );
373  expression.prepare( &mExpressionContext );
374  return !expression.hasParserError();
375 }
376 
378 {
379  mExpressionContext.appendScope( scope );
380 }
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:79
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)
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:442
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()
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