QGIS API Documentation  2.99.0-Master (e077efd)
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  , mExpressionContextGenerator( nullptr )
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(), SIGNAL( textEdited( QString ) ), this, SLOT( expressionEdited( QString ) ) );
60  connect( mCombo->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( expressionEditingFinished() ) );
61  connect( mCombo, SIGNAL( activated( int ) ), this, SLOT( currentFieldChanged() ) );
62  connect( mButton, SIGNAL( clicked() ), this, SLOT( editExpression() ) );
63  connect( mFieldProxyModel, SIGNAL( modelAboutToBeReset() ), this, SLOT( beforeResetModel() ) );
64  connect( mFieldProxyModel, SIGNAL( modelReset() ), this, SLOT( 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 = QSharedPointer<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(), SIGNAL( updatedFields() ), this, SLOT( 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(), SIGNAL( updatedFields() ), SLOT( reloadLayer() ), Qt::UniqueConnection );
171 }
172 
173 void QgsFieldExpressionWidget::setField( const QString &fieldName )
174 {
175  if ( fieldName.isEmpty() )
176  return;
177 
178  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
179  if ( !idx.isValid() )
180  {
181  // try to remove quotes and white spaces
182  QString simpleFieldName = fieldName.trimmed();
183  if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
184  {
185  simpleFieldName.remove( 0, 1 ).chop( 1 );
186  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
187  }
188 
189  if ( !idx.isValid() )
190  {
191  // new expression
192  mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
193  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
194  }
195  }
196  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
197  mCombo->setCurrentIndex( proxyIndex.row() );
199 }
200 
202 {
203  setField( expression );
204 }
205 
207 {
208  QString currentExpression = currentText();
209  QgsVectorLayer* vl = layer();
210 
211  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
212 
213  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, QStringLiteral( "generic" ), context );
214  if ( !mDa.isNull() )
215  {
216  dlg.setGeomCalculator( *mDa );
217  }
218  dlg.setWindowTitle( mExpressionDialogTitle );
219 
220  if ( dlg.exec() )
221  {
222  QString newExpression = dlg.expressionText();
223  setField( newExpression );
224  }
225 }
226 
228 {
229  updateLineEditStyle( expression );
230  emit fieldChanged( expression, isValidExpression() );
231 }
232 
234 {
235  const QString expression = mCombo->lineEdit()->text();
236  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
237  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
238  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
239  mCombo->setCurrentIndex( proxyIndex.row() );
241 }
242 
244 {
245  if ( event->type() == QEvent::EnabledChange )
246  {
248  }
249 }
250 
251 void QgsFieldExpressionWidget::reloadLayer()
252 {
253  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
254 }
255 
256 void QgsFieldExpressionWidget::beforeResetModel()
257 {
258  // Backup expression
259  mBackupExpression = mCombo->currentText();
260 }
261 
262 void QgsFieldExpressionWidget::afterResetModel()
263 {
264  // Restore expression
265  mCombo->lineEdit()->setText( mBackupExpression );
266 }
267 
269 {
271 
272  bool isExpression, isValid;
273  QString fieldName = currentField( &isExpression, &isValid );
274 
275  // display tooltip if widget is shorter than expression
276  QFontMetrics metrics( mCombo->lineEdit()->font() );
277  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
278  {
279  mCombo->setToolTip( fieldName );
280  }
281  else
282  {
283  mCombo->setToolTip( QLatin1String( "" ) );
284  }
285 
286  emit fieldChanged( fieldName );
287  emit fieldChanged( fieldName, isValid );
288 }
289 
291 {
292  QPalette palette;
293  if ( !isEnabled() )
294  {
295  palette.setColor( QPalette::Text, Qt::gray );
296  }
297  else
298  {
299  bool isExpression, isValid;
300  if ( !expression.isEmpty() )
301  {
302  isExpression = true;
303  isValid = isExpressionValid( expression );
304  }
305  else
306  {
307  currentField( &isExpression, &isValid );
308  }
309  QFont font = mCombo->lineEdit()->font();
310  font.setItalic( isExpression );
311  mCombo->lineEdit()->setFont( font );
312 
313  if ( isExpression && !isValid )
314  {
315  palette.setColor( QPalette::Text, Qt::red );
316  }
317  else
318  {
319  palette.setColor( QPalette::Text, Qt::black );
320  }
321  }
322  mCombo->lineEdit()->setPalette( palette );
323 }
324 
325 bool QgsFieldExpressionWidget::isExpressionValid( const QString& expressionStr )
326 {
327  QgsExpression expression( expressionStr );
328  expression.prepare( &mExpressionContext );
329  return !expression.hasParserError();
330 }
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:49
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
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
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
QgsFieldProxyModel * setFilters(QgsFieldProxyModel::Filters filters)
Set flags that affect how fields are filtered in the model.
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
Return 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 setAllowExpression(bool allowExpression)
Sets whether custom expressions are accepted and displayed in the model.
bool isExpressionValid(const QString &expressionStr)
void setGeomCalculator(const QgsDistanceArea &da)
set the geometry calculator used in the expression dialog
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
QgsVectorLayer layer
Definition: qgsfieldmodel.h:39
General purpose distance and area calculator.
void expressionEditingFinished()
when expression has been edited (finished) it will be added to the model
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)
Get 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 ...
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:348
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 ...
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
QModelIndex indexFromName(const QString &fieldName)
Returns the index corresponding to a given fieldName.
Represents a vector layer which manages a vector based data sets.
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
Return 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