QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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"
29 
31  : QWidget( parent )
32  , mExpressionDialogTitle( tr( "Expression Dialog" ) )
33  , mDa( nullptr )
34 
35 {
36  QHBoxLayout *layout = new QHBoxLayout( this );
37  layout->setContentsMargins( 0, 0, 0, 0 );
38 
39  mCombo = new QComboBox( this );
40  mCombo->setEditable( true );
41  mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
42  int width = mCombo->minimumSizeHint().width();
43  mCombo->setMinimumWidth( width );
44 
45  mFieldProxyModel = new QgsFieldProxyModel( mCombo );
46  mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
47  mCombo->setModel( mFieldProxyModel );
48 
49  mButton = new QToolButton( this );
50  mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
51  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
52 
53  layout->addWidget( mCombo );
54  layout->addWidget( mButton );
55 
56  // give focus to the combo
57  // hence if the widget is used as a delegate
58  // it will allow pressing on the expression dialog button
59  setFocusProxy( mCombo );
60 
61  connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
62  connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
63  connect( mCombo, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
64  connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
65  connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
66  connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
67 
68  mExpressionContext = QgsExpressionContext();
69  mExpressionContext << QgsExpressionContextUtils::globalScope()
71 
72  mCombo->installEventFilter( this );
73 }
74 
76 {
77  mExpressionDialogTitle = title;
78 }
79 
80 void QgsFieldExpressionWidget::setFilters( QgsFieldProxyModel::Filters filters )
81 {
82  mFieldProxyModel->setFilters( filters );
83 }
84 
86 {
87  mCombo->lineEdit()->setClearButtonEnabled( allowEmpty );
88  mFieldProxyModel->sourceFieldModel()->setAllowEmptyFieldName( allowEmpty );
89 }
90 
92 {
93  return mFieldProxyModel->sourceFieldModel()->allowEmptyFieldName();
94 }
95 
97 {
98  QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
99  if ( !layout )
100  return;
101 
102  if ( isLeft )
103  {
104  QLayoutItem *item = layout->takeAt( 1 );
105  layout->insertWidget( 0, item->widget() );
106  }
107  else
108  layout->addWidget( mCombo );
109 }
110 
112 {
113  mDa = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
114 }
115 
117 {
118  return mCombo->currentText();
119 }
120 
122 {
124 }
125 
127 {
128  return asExpression();
129 }
130 
131 bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
132 {
133  QString temp;
134  return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
135 }
136 
138 {
139  return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
140 }
141 
142 QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
143 {
144  QString text = currentText();
145  bool valueIsExpression = this->isExpression();
146  if ( isValid )
147  {
148  // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
149  *isValid = !valueIsExpression || isValidExpression();
150  }
151  if ( isExpression )
152  {
153  *isExpression = valueIsExpression;
154  }
155  return text;
156 }
157 
159 {
160  return mFieldProxyModel->sourceFieldModel()->layer();
161 }
162 
164 {
165  mExpressionContextGenerator = generator;
166 }
167 
169 {
170  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
171 
172  if ( mFieldProxyModel->sourceFieldModel()->layer() )
173  disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
174 
175  if ( vl )
176  mExpressionContext = vl->createExpressionContext();
177  else
178  mExpressionContext = QgsProject::instance()->createExpressionContext();
179 
180  mFieldProxyModel->sourceFieldModel()->setLayer( vl );
181 
182  if ( mFieldProxyModel->sourceFieldModel()->layer() )
183  connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
184 }
185 
186 void QgsFieldExpressionWidget::setField( const QString &fieldName )
187 {
188  if ( fieldName.isEmpty() )
189  {
190  setRow( -1 );
191  emit fieldChanged( QString() );
192  emit fieldChanged( QString(), true );
193  return;
194  }
195 
196  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
197  if ( !idx.isValid() )
198  {
199  // try to remove quotes and white spaces
200  QString simpleFieldName = fieldName.trimmed();
201  if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
202  {
203  simpleFieldName.remove( 0, 1 ).chop( 1 );
204  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
205  }
206 
207  if ( !idx.isValid() )
208  {
209  // new expression
210  mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
211  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
212  }
213  }
214  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
215  mCombo->setCurrentIndex( proxyIndex.row() );
217 }
218 
220 {
221  setField( expression );
222 }
223 
225 {
226  QString currentExpression = currentText();
227  QgsVectorLayer *vl = layer();
228 
229  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
230 
231  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
232  if ( mDa )
233  {
234  dlg.setGeomCalculator( *mDa );
235  }
236  dlg.setWindowTitle( mExpressionDialogTitle );
237  dlg.setAllowEvalErrors( mAllowEvalErrors );
238 
239  if ( dlg.exec() )
240  {
241  QString newExpression = dlg.expressionText();
242  setField( newExpression );
243  }
244 }
245 
247 {
248  updateLineEditStyle( expression );
249  emit fieldChanged( expression, isValidExpression() );
250 }
251 
253 {
254  const QString expression = mCombo->lineEdit()->text();
255  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
256  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
257  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
258  mCombo->setCurrentIndex( proxyIndex.row() );
260 }
261 
263 {
264  if ( event->type() == QEvent::EnabledChange )
265  {
267  }
268 }
269 
270 void QgsFieldExpressionWidget::reloadLayer()
271 {
272  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
273 }
274 
275 void QgsFieldExpressionWidget::beforeResetModel()
276 {
277  // Backup expression
278  mBackupExpression = mCombo->currentText();
279 }
280 
281 void QgsFieldExpressionWidget::afterResetModel()
282 {
283  // Restore expression
284  mCombo->lineEdit()->setText( mBackupExpression );
285 }
286 
287 bool QgsFieldExpressionWidget::eventFilter( QObject *watched, QEvent *event )
288 {
289  if ( watched == mCombo && event->type() == QEvent::KeyPress )
290  {
291  QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
292  if ( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
293  {
295  return true;
296  }
297  }
298  return QObject::eventFilter( watched, event );
299 }
300 
302 {
303  return mAllowEvalErrors;
304 }
305 
307 {
308  if ( allowEvalErrors == mAllowEvalErrors )
309  return;
310 
311  mAllowEvalErrors = allowEvalErrors;
312  emit allowEvalErrorsChanged();
313 }
314 
316 {
318 
319  bool isExpression, isValid;
320  QString fieldName = currentField( &isExpression, &isValid );
321 
322  // display tooltip if widget is shorter than expression
323  QFontMetrics metrics( mCombo->lineEdit()->font() );
324  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
325  {
326  mCombo->setToolTip( fieldName );
327  }
328  else
329  {
330  mCombo->setToolTip( QString() );
331  }
332 
333  emit fieldChanged( fieldName );
334  emit fieldChanged( fieldName, isValid );
335 }
336 
338 {
339  QString stylesheet;
340  if ( !isEnabled() )
341  {
342  stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::gray ).name() );
343  }
344  else
345  {
346  bool isExpression, isValid;
347  if ( !expression.isEmpty() )
348  {
349  isExpression = true;
350  isValid = isExpressionValid( expression );
351  }
352  else
353  {
354  currentField( &isExpression, &isValid );
355  }
356  QFont font = mCombo->lineEdit()->font();
357  font.setItalic( isExpression );
358  mCombo->lineEdit()->setFont( font );
359 
360  if ( isExpression && !isValid )
361  {
362  stylesheet = QStringLiteral( "QLineEdit { color: %1; }" ).arg( QColor( Qt::red ).name() );
363  }
364  }
365  mCombo->lineEdit()->setStyleSheet( stylesheet );
366 }
367 
368 bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
369 {
370  QgsExpression expression( expressionStr );
371  expression.prepare( &mExpressionContext );
372  return !expression.hasParserError();
373 }
374 
376 {
377  mExpressionContext.appendScope( scope );
378 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Base class for all map layer types.
Definition: qgsmaplayer.h:63
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.
bool isField(const QString &expression) const
Returns true if a string represents a field reference, or false if it is an expression consisting of ...
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
bool isValidExpression(QString *expressionError=nullptr) const
Returns true if the current expression is valid.
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
QString expression() const
Returns the currently selected field or expression.
void changeEvent(QEvent *event) override
QString currentField(bool *isExpression=nullptr, bool *isValid=nullptr) const
currentField returns the currently selected field or expression if allowed
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.
QString currentText() const
Returns the current text that is set in the expression area.
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.
QString asExpression() const
Returns the currently selected field or expression.
void setAllowEvalErrors(bool allowEvalErrors)
Allow accepting expressions with evaluation errors.
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...
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.
bool allowEmptyFieldName() const
Returns true if the combo box allows the empty field ("not set") choice.
QgsVectorLayer * layer() const
Returns the layer currently associated with the widget.
bool eventFilter(QObject *watched, QEvent *event) override
void fieldChanged(const QString &fieldName)
the signal is emitted when the currently selected field changes
QgsVectorLayer layer
Definition: qgsfieldmodel.h:43
QgsFieldProxyModel::Filters filters() const
currently used filter on list of fields
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
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.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:411
void setField(const QString &fieldName)
sets the current field or expression in the widget
bool allowEvalErrors() const
Allow accepting expressions with evaluation errors.
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...