QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
26  : QWidget( parent )
27  , mExpressionDialogTitle( tr( "Expression dialog" ) )
28  , mDa( nullptr )
29  , mExpressionContextCallback( nullptr )
30  , mExpressionContextCallbackContext( nullptr )
31 {
32  QHBoxLayout* layout = new QHBoxLayout( this );
33  layout->setContentsMargins( 0, 0, 0, 0 );
34 
35  mCombo = new QComboBox( this );
36  mCombo->setEditable( true );
37  mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
38  int width = mCombo->minimumSizeHint().width();
39  mCombo->setMinimumWidth( width );
40 
41  mFieldProxyModel = new QgsFieldProxyModel( mCombo );
42  mFieldProxyModel->sourceFieldModel()->setAllowExpression( true );
43  mCombo->setModel( mFieldProxyModel );
44 
45  mButton = new QToolButton( this );
46  mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
47  mButton->setIcon( QgsApplication::getThemeIcon( "/mIconExpression.svg" ) );
48 
49  layout->addWidget( mCombo );
50  layout->addWidget( mButton );
51 
52  // give focus to the combo
53  // hence if the widget is used as a delegate
54  // it will allow pressing on the expression dialog button
55  setFocusProxy( mCombo );
56 
57  connect( mCombo->lineEdit(), SIGNAL( textEdited( QString ) ), this, SLOT( expressionEdited( QString ) ) );
58  connect( mCombo->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( expressionEditingFinished() ) );
59  connect( mCombo, SIGNAL( activated( int ) ), this, SLOT( currentFieldChanged() ) );
60  connect( mButton, SIGNAL( clicked() ), this, SLOT( editExpression() ) );
61  connect( mFieldProxyModel, SIGNAL( modelAboutToBeReset() ), this, SLOT( beforeResetModel() ) );
62  connect( mFieldProxyModel, SIGNAL( modelReset() ), this, SLOT( afterResetModel() ) );
63  // NW TODO - Fix in 2.6
64 // connect( mCombo->lineEdit(), SIGNAL( returnPressed() ), this, SIGNAL( returnPressed() ) );
65 
66  mExpressionContext.reset( new QgsExpressionContext() );
67  mExpressionContext->appendScope( QgsExpressionContextUtils::globalScope() );
68  mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
69 }
70 
72 {
73  mExpressionDialogTitle = title;
74 }
75 
76 void QgsFieldExpressionWidget::setFilters( const QgsFieldProxyModel::Filters& filters )
77 {
78  mFieldProxyModel->setFilters( filters );
79 }
80 
82 {
83  QHBoxLayout* layout = dynamic_cast<QHBoxLayout*>( this->layout() );
84  if ( !layout )
85  return;
86 
87  if ( isLeft )
88  {
89  QLayoutItem* item = layout->takeAt( 1 );
90  layout->insertWidget( 0, item->widget() );
91  }
92  else
93  layout->addWidget( mCombo );
94 }
95 
97 {
99 }
100 
102 {
103  return mCombo->currentText();
104 }
105 
107 {
109 }
110 
112 {
113  return asExpression();
114 }
115 
117 {
118  QString temp;
119  return QgsExpression::isValid( currentText(), mExpressionContext.data(), expressionError ? *expressionError : temp );
120 }
121 
123 {
124  return !mFieldProxyModel->sourceFieldModel()->isField( currentText() );
125 }
126 
128 {
129  QString text = currentText();
130  bool valueIsExpression = this->isExpression();
131  if ( isValid )
132  {
133  // valid if not an expression (ie, set to a field), or set to an expression and expression is valid
134  *isValid = !valueIsExpression || isValidExpression();
135  }
136  if ( isExpression )
137  {
138  *isExpression = valueIsExpression;
139  }
140  return text;
141 }
142 
144 {
145  return mFieldProxyModel->sourceFieldModel()->layer();
146 }
147 
149 {
150  mExpressionContextCallback = fnGetExpressionContext;
151  mExpressionContextCallbackContext = context;
152 }
153 
155 {
156  QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( layer );
157  if ( vl )
158  {
159  setLayer( vl );
160  }
161 }
162 
164 {
165  if ( mFieldProxyModel->sourceFieldModel()->layer() )
166  disconnect( mFieldProxyModel->sourceFieldModel()->layer(), SIGNAL( updatedFields() ), this, SLOT( reloadLayer() ) );
167 
168  mExpressionContext.reset( new QgsExpressionContext() );
169  mExpressionContext->appendScope( QgsExpressionContextUtils::globalScope() );
170  mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
171  if ( layer )
172  mExpressionContext->appendScope( QgsExpressionContextUtils::layerScope( layer ) );
173 
174  mFieldProxyModel->sourceFieldModel()->setLayer( layer );
175 
176  if ( mFieldProxyModel->sourceFieldModel()->layer() )
177  connect( mFieldProxyModel->sourceFieldModel()->layer(), SIGNAL( updatedFields() ), SLOT( reloadLayer() ), Qt::UniqueConnection );
178 }
179 
181 {
182  if ( fieldName.isEmpty() )
183  return;
184 
185  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
186  if ( !idx.isValid() )
187  {
188  // try to remove quotes and white spaces
189  QString simpleFieldName = fieldName.trimmed();
190  if ( simpleFieldName.startsWith( '"' ) && simpleFieldName.endsWith( '"' ) )
191  {
192  simpleFieldName.remove( 0, 1 ).chop( 1 );
193  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( simpleFieldName );
194  }
195 
196  if ( !idx.isValid() )
197  {
198  // new expression
199  mFieldProxyModel->sourceFieldModel()->setExpression( fieldName );
200  idx = mFieldProxyModel->sourceFieldModel()->indexFromName( fieldName );
201  }
202  }
203  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
204  mCombo->setCurrentIndex( proxyIndex.row() );
206 }
207 
209 {
210  setField( expression );
211 }
212 
214 {
215  QString currentExpression = currentText();
216  QgsVectorLayer* vl = layer();
217 
218  QgsExpressionContext context = mExpressionContextCallback ? mExpressionContextCallback( mExpressionContextCallbackContext ) : *mExpressionContext;
219 
220  QgsExpressionBuilderDialog dlg( vl, currentExpression, this, "generic", context );
221  if ( !mDa.isNull() )
222  {
223  dlg.setGeomCalculator( *mDa );
224  }
225  dlg.setWindowTitle( mExpressionDialogTitle );
226 
227  if ( dlg.exec() )
228  {
229  QString newExpression = dlg.expressionText();
230  setField( newExpression );
231  }
232 }
233 
235 {
236  updateLineEditStyle( expression );
237  emit fieldChanged( expression, isValidExpression() );
238 }
239 
241 {
242  QgsDebugMsg( "Editing finished" );
243  const QString expression = mCombo->lineEdit()->text();
244  mFieldProxyModel->sourceFieldModel()->setExpression( expression );
245  QModelIndex idx = mFieldProxyModel->sourceFieldModel()->indexFromName( expression );
246  QModelIndex proxyIndex = mFieldProxyModel->mapFromSource( idx );
247  mCombo->setCurrentIndex( proxyIndex.row() );
249 }
250 
252 {
253  if ( event->type() == QEvent::EnabledChange )
254  {
256  }
257 }
258 
259 void QgsFieldExpressionWidget::reloadLayer()
260 {
261  setLayer( mFieldProxyModel->sourceFieldModel()->layer() );
262 }
263 
264 void QgsFieldExpressionWidget::beforeResetModel()
265 {
266  // Backup expression
267  mBackupExpression = mCombo->currentText();
268 }
269 
270 void QgsFieldExpressionWidget::afterResetModel()
271 {
272  // Restore expression
273  mCombo->lineEdit()->setText( mBackupExpression );
274 }
275 
277 {
279 
280  bool isExpression, isValid;
281  QString fieldName = currentField( &isExpression, &isValid );
282 
283  // display tooltip if widget is shorter than expression
284  QFontMetrics metrics( mCombo->lineEdit()->font() );
285  if ( metrics.width( fieldName ) > mCombo->lineEdit()->width() )
286  {
287  mCombo->setToolTip( fieldName );
288  }
289  else
290  {
291  mCombo->setToolTip( "" );
292  }
293 
294  emit fieldChanged( fieldName );
295  emit fieldChanged( fieldName, isValid );
296 }
297 
299 {
301  if ( !isEnabled() )
302  {
303  palette.setColor( QPalette::Text, Qt::gray );
304  }
305  else
306  {
307  bool isExpression, isValid;
308  if ( !expression.isEmpty() )
309  {
310  isExpression = true;
311  isValid = isExpressionValid( expression );
312  }
313  else
314  {
315  currentField( &isExpression, &isValid );
316  }
317  QFont font = mCombo->lineEdit()->font();
318  font.setItalic( isExpression );
319  mCombo->lineEdit()->setFont( font );
320 
321  if ( isExpression && !isValid )
322  {
323  palette.setColor( QPalette::Text, Qt::red );
324  }
325  else
326  {
327  palette.setColor( QPalette::Text, Qt::black );
328  }
329  }
330  mCombo->lineEdit()->setPalette( palette );
331 }
332 
334 {
335  QgsExpression expression( expressionStr );
336  expression.prepare( mExpressionContext.data() );
337  return !expression.hasParserError();
338 }
QLayout * layout() const
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.
QgsFieldProxyModel * setFilters(const QgsFieldProxyModel::Filters &filters)
Set flags that affect how fields are filtered in the model.
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.
Type type() const
void setContentsMargins(int left, int top, int right, int bottom)
const QPalette & palette() const
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
int width() const
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
void changeEvent(QEvent *event) override
Q_DECL_DEPRECATED bool prepare(const QgsFields &fields)
Get the expression ready for evaluation - find out column indexes.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
QString asExpression() const
Returns the currently selected field or expression.
virtual QWidget * widget()
void setMinimumWidth(int minw)
void registerGetExpressionContextCallback(ExpressionContextCallback fnGetExpressionContext, const void *context)
Register callback function for retrieving the expression context for the expression.
void setExpression(const QString &expression)
setExpression sets a single expression to be added after the fields at the end of the model ...
int exec()
void editExpression()
open the expression dialog to edit the current or add a new expression
void setFilters(const QgsFieldProxyModel::Filters &filters)
setFilters allows fitering according to the type of field
void setLayer(QgsVectorLayer *layer)
set the layer used to display the fields and expression
QString & remove(int position, int n)
void setEditable(bool editable)
void chop(int n)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual QLayoutItem * takeAt(int index)
void setIcon(const QIcon &icon)
QgsVectorLayer * layer()
returns the currently used layer
Definition: qgsfieldmodel.h:69
bool isField(const QString &expression)
QgsFieldProxyModel::Filters filters() const
currently used filter on list of fields
void reset(T *other)
int width() const
bool isValid() const
bool isEnabled() const
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
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...
virtual QSize minimumSizeHint() const
bool isValid() const
Checks if this expression is valid.
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 setFocusProxy(QWidget *w)
bool isEmpty() const
QString trimmed() const
bool isValidExpression(QString *expressionError=nullptr) const
Return true if the current expression is valid.
void setLayer(QgsVectorLayer *layer)
set the layer of whch fields are displayed
int row() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
void setAllowExpression(bool allowExpression)
returns the currently used layer
bool isExpressionValid(const QString &expressionStr)
void setGeomCalculator(const QgsDistanceArea &da)
set the geometry calculator used in the expression dialog
void setSizePolicy(QSizePolicy)
T * data() const
QString expression() const
Returns the currently selected field or expression.
void fieldChanged(const QString &fieldName)
the signal is emitted when the currently selected field changes
void setItalic(bool enable)
General purpose distance and area calculator.
QLineEdit * lineEdit() const
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 currently used layer.
void setModel(QAbstractItemModel *model)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const
void setWindowTitle(const QString &)
void setField(const QString &fieldName)
sets the current field or expression in the widget
void setCurrentIndex(int index)
void updateLineEditStyle(const QString &expression=QString())
updateLineEditStyle will re-style (color/font) the line edit depending on content and status ...
void insertWidget(int index, QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
void setToolTip(const QString &)
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool isNull() const
QModelIndex indexFromName(const QString &fieldName)
return the index corresponding to a given fieldName
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
virtual bool event(QEvent *event)
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.
QgsExpressionContext(* ExpressionContextCallback)(const void *context)
Callback function for retrieving the expression context for the expression.
QString currentField(bool *isExpression=nullptr, bool *isValid=nullptr) const
currentField returns the currently selected field or expression if allowed