QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
19 #include "qgsapplication.h"
22 #include "qgsfieldproxymodel.h"
23 #include "qgsdistancearea.h"
24 #include "qgsfieldmodel.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsproject.h"
27 
29  : QWidget( parent )
30  , mExpressionDialogTitle( tr( "Expression Dialog" ) )
31  , mDa( nullptr )
32 
33 {
34  QHBoxLayout *layout = new QHBoxLayout( this );
35  layout->setContentsMargins( 0, 0, 0, 0 );
36 
37  mCombo = new QComboBox( this );
38  mCombo->setEditable( true );
39  mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
40  int width = mCombo->minimumSizeHint().width();
41  mCombo->setMinimumWidth( width );
42 
43  mFieldProxyModel = new QgsFieldProxyModel( mCombo );
44  mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
45  mCombo->setModel( mFieldProxyModel );
46 
47  mButton = new QToolButton( this );
48  mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
49  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
50 
51  layout->addWidget( mCombo );
52  layout->addWidget( mButton );
53 
54  // give focus to the combo
55  // hence if the widget is used as a delegate
56  // it will allow pressing on the expression dialog button
57  setFocusProxy( mCombo );
58 
59  connect( mCombo->lineEdit(), &QLineEdit::textEdited, this, &QgsFieldExpressionWidget::expressionEdited );
60  connect( mCombo->lineEdit(), &QLineEdit::editingFinished, this, &QgsFieldExpressionWidget::expressionEditingFinished );
61  connect( mCombo, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::activated ), this, &QgsFieldExpressionWidget::currentFieldChanged );
62  connect( mButton, &QAbstractButton::clicked, this, &QgsFieldExpressionWidget::editExpression );
63  connect( mFieldProxyModel, &QAbstractItemModel::modelAboutToBeReset, this, &QgsFieldExpressionWidget::beforeResetModel );
64  connect( mFieldProxyModel, &QAbstractItemModel::modelReset, this, &QgsFieldExpressionWidget::afterResetModel );
65  // NW TODO - Fix in 2.6
66 // connect( mCombo->lineEdit(), SIGNAL( returnPressed() ), this, SIGNAL( returnPressed() ) );
67 
68  mExpressionContext = QgsExpressionContext();
69  mExpressionContext << QgsExpressionContextUtils::globalScope()
71 }
72 
74 {
75  mExpressionDialogTitle = title;
76 }
77 
78 void QgsFieldExpressionWidget::setFilters( QgsFieldProxyModel::Filters filters )
79 {
80  mFieldProxyModel->setFilters( filters );
81 }
82 
84 {
85  QHBoxLayout *layout = dynamic_cast<QHBoxLayout *>( this->layout() );
86  if ( !layout )
87  return;
88 
89  if ( isLeft )
90  {
91  QLayoutItem *item = layout->takeAt( 1 );
92  layout->insertWidget( 0, item->widget() );
93  }
94  else
95  layout->addWidget( mCombo );
96 }
97 
99 {
100  mDa = std::shared_ptr<const QgsDistanceArea>( new QgsDistanceArea( da ) );
101 }
102 
104 {
105  return mCombo->currentText();
106 }
107 
109 {
111 }
112 
114 {
115  return asExpression();
116 }
117 
118 bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const
119 {
120  QString temp;
121  return QgsExpression::checkExpression( currentText(), &mExpressionContext, expressionError ? *expressionError : temp );
122 }
123 
125 {
126  return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
127 }
128 
129 QString QgsFieldExpressionWidget::currentField( bool *isExpression, bool *isValid ) const
130 {
131  QString text = currentText();
132  bool valueIsExpression = this->isExpression();
133  if ( isValid )
134  {
135  // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
136  *isValid = !valueIsExpression || isValidExpression();
137  }
138  if ( isExpression )
139  {
140  *isExpression = valueIsExpression;
141  }
142  return text;
143 }
144 
146 {
147  return mFieldProxyModel->sourceFieldModel()->layer();
148 }
149 
151 {
152  mExpressionContextGenerator = generator;
153 }
154 
156 {
157  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer );
158 
159  if ( mFieldProxyModel->sourceFieldModel()->layer() )
160  disconnect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer );
161 
162  if ( vl )
163  mExpressionContext = vl->createExpressionContext();
164  else
165  mExpressionContext = QgsProject::instance()->createExpressionContext();
166 
167  mFieldProxyModel->sourceFieldModel()->setLayer( vl );
168 
169  if ( mFieldProxyModel->sourceFieldModel()->layer() )
170  connect( mFieldProxyModel->sourceFieldModel()->layer(), &QgsVectorLayer::updatedFields, this, &QgsFieldExpressionWidget::reloadLayer, Qt::UniqueConnection );
171 }
172 
173 void QgsFieldExpressionWidget::setField( const QString &fieldName )
174 {
175  if ( fieldName.isEmpty() )
176  {
177  setRow( -1 );
178  return;
179  }
180 
181  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
182  if ( !idx.isValid() )
183  {
184  // try to remove quotes and white spaces
185  QString simpleFieldName = fieldName.trimmed();
186  if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
187  {
188  simpleFieldName.remove( 0, 1 ).chop( 1 );
189  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
190  }
191 
192  if ( !idx.isValid() )
193  {
194  // new expression
195  mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
196  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
197  }
198  }
199  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
200  mCombo->setCurrentIndex( proxyIndex.row() );
202 }
203 
205 {
206  setField( expression );
207 }
208 
210 {
211  QString currentExpression = currentText();
212  QgsVectorLayer *vl = layer();
213 
214  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
215 
216  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
217  if ( mDa )
218  {
219  dlg.setGeomCalculator( *mDa );
220  }
221  dlg.setWindowTitle( mExpressionDialogTitle );
222  dlg.setAllowEvalErrors( mAllowEvalErrors );
223 
224  if ( dlg.exec() )
225  {
226  QString newExpression = dlg.expressionText();
227  setField( newExpression );
228  }
229 }
230 
232 {
233  updateLineEditStyle( expression );
234  emit fieldChanged( expression, isValidExpression() );
235 }
236 
238 {
239  const QString expression = mCombo->lineEdit()->text();
240  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
241  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
242  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
243  mCombo->setCurrentIndex( proxyIndex.row() );
245 }
246 
248 {
249  if ( event->type() == QEvent::EnabledChange )
250  {
252  }
253 }
254 
255 void QgsFieldExpressionWidget::reloadLayer()
256 {
257  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
258 }
259 
260 void QgsFieldExpressionWidget::beforeResetModel()
261 {
262  // Backup expression
263  mBackupExpression = mCombo->currentText();
264 }
265 
266 void QgsFieldExpressionWidget::afterResetModel()
267 {
268  // Restore expression
269  mCombo->lineEdit()->setText( mBackupExpression );
270 }
271 
273 {
274  return mAllowEvalErrors;
275 }
276 
278 {
279  if ( allowEvalErrors == mAllowEvalErrors )
280  return;
281 
282  mAllowEvalErrors = allowEvalErrors;
283  emit allowEvalErrorsChanged();
284 }
285 
287 {
289 
290  bool isExpression, isValid;
291  QString fieldName = currentField( &isExpression, &isValid );
292 
293  // display tooltip if widget is shorter than expression
294  QFontMetrics metrics( mCombo->lineEdit()->font() );
295  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
296  {
297  mCombo->setToolTip( fieldName );
298  }
299  else
300  {
301  mCombo->setToolTip( QLatin1String( "" ) );
302  }
303 
304  emit fieldChanged( fieldName );
305  emit fieldChanged( fieldName, isValid );
306 }
307 
309 {
310  QPalette palette;
311  if ( !isEnabled() )
312  {
313  palette.setColor( QPalette::Text, Qt::gray );
314  }
315  else
316  {
317  bool isExpression, isValid;
318  if ( !expression.isEmpty() )
319  {
320  isExpression = true;
321  isValid = isExpressionValid( expression );
322  }
323  else
324  {
325  currentField( &isExpression, &isValid );
326  }
327  QFont font = mCombo->lineEdit()->font();
328  font.setItalic( isExpression );
329  mCombo->lineEdit()->setFont( font );
330 
331  if ( isExpression && !isValid )
332  {
333  palette.setColor( QPalette::Text, Qt::red );
334  }
335  else
336  {
337  palette.setColor( QPalette::Text, Qt::black );
338  }
339  }
340  mCombo->lineEdit()->setPalette( palette );
341 }
342 
343 bool QgsFieldExpressionWidget::isExpressionValid( const QString &expressionStr )
344 {
345  QgsExpression expression( expressionStr );
346  expression.prepare( &mExpressionContext );
347  return !expression.hasParserError();
348 }
349 
351 {
352  mExpressionContext.appendScope( scope );
353 }
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:61
void setExpression(const QString &expression)
Sets the current expression text and if applicable also the field.
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...
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 ...
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.
Abstract interface for generating an expression context.
QString expression() 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...
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:391
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