QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgisexpressionbuilderwidget.cpp - A genric expression string builder widget.
3  --------------------------------------
4  Date : 29-May-2011
5  Copyright : (C) 2011 by Nathan Woodrow
6  Email : woodrow.nathan at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 #include "qgslogger.h"
18 #include "qgsexpression.h"
19 #include "qgsmessageviewer.h"
20 #include "qgsapplication.h"
21 
22 #include <QSettings>
23 #include <QMenu>
24 #include <QFile>
25 #include <QTextStream>
26 
28  : QWidget( parent )
29 {
30  setupUi( this );
31 
32  mValueGroupBox->hide();
33  btnLoadAll->hide();
34  btnLoadSample->hide();
35  highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
36 
37  mModel = new QStandardItemModel( );
39  mProxyModel->setSourceModel( mModel );
40  expressionTree->setModel( mProxyModel );
41 
42  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
43  connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
44  connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
45  connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
46  this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
47 
48  connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
49  connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
50 
51  foreach ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
52  {
53  connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
54  }
55 
56  // TODO Can we move this stuff to QgsExpression, like the functions?
57  registerItem( "Operators", "+", " + ", tr( "Addition operator" ) );
58  registerItem( "Operators", "-", " -" , tr( "Subtraction operator" ) );
59  registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) );
60  registerItem( "Operators", "/", " / ", tr( "Division operator" ) );
61  registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) );
62  registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) );
63  registerItem( "Operators", "=", " = ", tr( "Equal operator" ) );
64  registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) );
65  registerItem( "Operators", "<", " < ", tr( "Less than operator" ) );
66  registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) );
67  registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) );
68  registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) );
69  registerItem( "Operators", "||", " || ",
70  QString( "<b>|| %1</b><br><i>%2</i><br><i>%3:</i>%4" )
71  .arg( tr( "(String Concatenation)" ) )
72  .arg( tr( "Joins two values together into a string" ) )
73  .arg( tr( "Usage" ) )
74  .arg( tr( "'Dia' || Diameter" ) ) );
75  registerItem( "Operators", "LIKE", " LIKE " );
76  registerItem( "Operators", "ILIKE", " ILIKE " );
77  registerItem( "Operators", "IS", " IS " );
78  registerItem( "Operators", "OR", " OR " );
79  registerItem( "Operators", "AND", " AND " );
80  registerItem( "Operators", "NOT", " NOT " );
81 
82  QString casestring = "CASE WHEN condition THEN result END";
83  QString caseelsestring = "CASE WHEN condition THEN result ELSE result END";
84  registerItem( "Conditionals", "CASE", casestring );
85  registerItem( "Conditionals", "CASE ELSE", caseelsestring );
86 
87  // Load the functions from the QgsExpression class
88  int count = QgsExpression::functionCount();
89  for ( int i = 0; i < count; i++ )
90  {
92  QString name = func->name();
93  if ( name.startsWith( "_" ) ) // do not display private functions
94  continue;
95  if ( func->params() >= 1 )
96  name += "(";
97  registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
98  }
99 
100  QList<QgsExpression::Function*> specials = QgsExpression::specialColumns();
101  for ( int i = 0; i < specials.size(); ++i )
102  {
103  QString name = specials[i]->name();
104  registerItem( specials[i]->group(), name, " " + name + " " );
105  }
106 
107 #if QT_VERSION >= 0x040700
108  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
109 #endif
110 }
111 
112 
114 {
115 
116 }
117 
119 {
120  mLayer = layer;
121 }
122 
123 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
124 {
125  // Get the item
126  QModelIndex idx = mProxyModel->mapToSource( index );
127  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
128  if ( !item )
129  return;
130 
131  if ( item->getItemType() != QgsExpressionItem::Field )
132  {
133  mValueListWidget->clear();
134  }
135 
136  btnLoadAll->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
137  btnLoadSample->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
138  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
139 
140  // Show the help for the current item.
141  QString help = loadFunctionHelp( item );
142  txtHelpText->setText( help );
143  txtHelpText->setToolTip( txtHelpText->toPlainText() );
144 }
145 
147 {
148  QModelIndex idx = mProxyModel->mapToSource( index );
149  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
150  if ( item == 0 )
151  return;
152 
153  // Don't handle the double click it we are on a header node.
154  if ( item->getItemType() == QgsExpressionItem::Header )
155  return;
156 
157  // Insert the expression text.
158  txtExpressionString->insertPlainText( item->getExpressionText() );
159  txtExpressionString->setFocus();
160 }
161 
163 {
164  // TODO We should really return a error the user of the widget that
165  // the there is no layer set.
166  if ( !mLayer )
167  return;
168 
170 }
171 
173 {
174  if ( fields.isEmpty() )
175  return;
176 
177  QStringList fieldNames;
178  //foreach ( const QgsField& field, fields )
179  for ( int i = 0; i < fields.count(); ++i )
180  {
181  QString fieldName = fields[i].name();
182  fieldNames << fieldName;
183  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
184  }
185  highlighter->addFields( fieldNames );
186 }
187 
188 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
189 {
190  // TODO We should really return a error the user of the widget that
191  // the there is no layer set.
192  if ( !mLayer )
193  return;
194 
195  // TODO We should thread this so that we don't hold the user up if the layer is massive.
196  mValueListWidget->clear();
197  mValueListWidget->setUpdatesEnabled( false );
198  mValueListWidget->blockSignals( true );
199 
200  QList<QVariant> values;
201  mLayer->uniqueValues( fieldIndex, values, countLimit );
202  foreach ( QVariant value, values )
203  {
204  if ( value.isNull() )
205  mValueListWidget->addItem( "NULL" );
206  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
207  mValueListWidget->addItem( value.toString() );
208  else
209  mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
210  }
211 
212  mValueListWidget->setUpdatesEnabled( true );
213  mValueListWidget->blockSignals( false );
214 }
215 
217  QString label,
218  QString expressionText,
219  QString helpText,
221 {
222  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
223  item->setData( label, Qt::UserRole );
224  // Look up the group and insert the new function.
225  if ( mExpressionGroups.contains( group ) )
226  {
227  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
228  groupNode->appendRow( item );
229  }
230  else
231  {
232  // If the group doesn't exist yet we make it first.
234  newgroupNode->setData( group, Qt::UserRole );
235  newgroupNode->appendRow( item );
236  mModel->appendRow( newgroupNode );
237  mExpressionGroups.insert( group, newgroupNode );
238  }
239 }
240 
242 {
243  return mExpressionValid;
244 }
245 
247 {
248  mDa = da;
249 }
250 
252 {
253  return txtExpressionString->toPlainText();
254 }
255 
256 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
257 {
258  txtExpressionString->setPlainText( expression );
259 }
260 
262 {
263  QString text = txtExpressionString->toPlainText();
264 
265  // If the string is empty the expression will still "fail" although
266  // we don't show the user an error as it will be confusing.
267  if ( text.isEmpty() )
268  {
269  lblPreview->setText( "" );
270  lblPreview->setStyleSheet( "" );
271  txtExpressionString->setToolTip( "" );
272  lblPreview->setToolTip( "" );
273  emit expressionParsed( false );
274  return;
275  }
276 
277 
278 
279  QgsExpression exp( text );
280 
281  if ( mLayer )
282  {
283  // Only set calculator if we have layer, else use default.
284  exp.setGeomCalculator( mDa );
285 
286  if ( !mFeature.isValid() )
287  {
289  }
290 
291  if ( mFeature.isValid() )
292  {
293  QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
294  if ( !exp.hasEvalError() )
295  lblPreview->setText( value.toString() );
296  }
297  else
298  {
299  // The feature is invalid because we don't have one but that doesn't mean user can't
300  // build a expression string. They just get no preview.
301  lblPreview->setText( "" );
302  }
303  }
304  else
305  {
306  // No layer defined
307  QVariant value = exp.evaluate();
308  if ( !exp.hasEvalError() )
309  {
310  lblPreview->setText( value.toString() );
311  }
312  }
313 
314  if ( exp.hasParserError() || exp.hasEvalError() )
315  {
316  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
317  if ( exp.hasEvalError() )
318  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
319 
320  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
321  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
322  txtExpressionString->setToolTip( tooltip );
323  lblPreview->setToolTip( tooltip );
324  emit expressionParsed( false );
325  return;
326  }
327  else
328  {
329  lblPreview->setStyleSheet( "" );
330  txtExpressionString->setToolTip( "" );
331  lblPreview->setToolTip( "" );
332  emit expressionParsed( true );
333  }
334 }
335 
337 {
338  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
339  if ( txtSearchEdit->text().isEmpty() )
340  expressionTree->collapseAll();
341  else
342  expressionTree->expandAll();
343 }
344 
346 {
347  Q_UNUSED( link );
348  QgsMessageViewer * mv = new QgsMessageViewer( this );
349  mv->setWindowTitle( tr( "More info on expression error" ) );
350  mv->setMessageAsHtml( txtExpressionString->toolTip() );
351  mv->exec();
352 }
353 
355 {
356  txtExpressionString->insertPlainText( " " + item->text() + " " );
357  txtExpressionString->setFocus();
358 }
359 
361 {
362  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
363  txtExpressionString->insertPlainText( " " + button->text() + " " );
364  txtExpressionString->setFocus();
365 }
366 
368 {
369  QModelIndex idx = expressionTree->indexAt( pt );
370  idx = mProxyModel->mapToSource( idx );
371  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
372  if ( !item )
373  return;
374 
375  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
376  {
377  QMenu* menu = new QMenu( this );
378  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
379  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
380  menu->popup( expressionTree->mapToGlobal( pt ) );
381  }
382 }
383 
385 {
386  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
387  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
388  // TODO We should really return a error the user of the widget that
389  // the there is no layer set.
390  if ( !mLayer )
391  return;
392 
393  mValueGroupBox->show();
394  int fieldIndex = mLayer->fieldNameIndex( item->text() );
395  fillFieldValues( fieldIndex, 10 );
396 }
397 
399 {
400  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
401  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
402  // TODO We should really return a error the user of the widget that
403  // the there is no layer set.
404  if ( !mLayer )
405  return;
406 
407  mValueGroupBox->show();
408  int fieldIndex = mLayer->fieldNameIndex( item->text() );
409  fillFieldValues( fieldIndex, -1 );
410 }
411 
413 {
414  mExpressionValid = state;
415 }
416 
418 {
419  if ( !expressionItem )
420  return "";
421 
422  QString helpContents = expressionItem->getHelpText();
423 
424  // Return the function help that is set for the function if there is one.
425  if ( helpContents.isEmpty() )
426  {
427  QString name = expressionItem->data( Qt::UserRole ).toString();
428 
429  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
430  helpContents = QgsExpression::helptext( "Field" );
431  else
432  helpContents = QgsExpression::helptext( name );
433  }
434 
435  QString myStyle = QgsApplication::reportStyleSheet();
436  return "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
437 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:88
void setGeomCalculator(QgsDistanceArea &calc)
Sets the geometry calculator used in evaluation of expressions,.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:95
bool isValid() const
Return the validity of this feature.
Definition: qgsfeature.cpp:172
QgsExpressionItemSearchProxy * mProxyModel
QVariant evaluate(const QgsFeature *f=NULL)
Evaluate the feature and return the result.
A abstract base class for defining QgsExpression functions.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1)
Returns unique values for column.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
static QString helptext(QString name)
Container of fields for a vector layer.
Definition: qgsfield.h:162
static QString group(QString group)
static QString reportStyleSheet()
get a standard css style sheet for reports.
static const QList< Function * > & Functions()
void setMessageAsHtml(const QString &msg)
void addFields(QStringList fieldList)
static int functionCount()
Returns the number of functions defined in the parser.
Search proxy used to filter the QgsExpressionBuilderWidget tree.
QString loadFunctionHelp(QgsExpressionItem *functionName)
QMap< QString, QgsExpressionItem * > mExpressionGroups
void on_mValueListWidget_itemDoubleClicked(QListWidgetItem *item)
This class wraps a request for features to a vector layer (or directly its vector data provider)...
int count() const
Return number of items.
Definition: qgsfield.h:196
QGis::GeometryType geometryType() const
Returns point, line or polygon.
void on_expressionTree_doubleClicked(const QModelIndex &index)
void loadFieldNames()
Loads all the field names from the layer.
QString helptext()
The help text for the function.
void fillFieldValues(int fieldIndex, int countLimit)
QString name()
The name of the function.
QgsExpressionItem::ItemType getItemType()
Get the type of expression item eg header, field, ExpressionNode.
bool needsGeometry()
Returns true if the expression uses feature geometry for some computation.
General purpose distance and area calculator.
An expression item that can be used in the QgsExpressionBuilderWidget tree.
void currentChanged(const QModelIndex &index, const QModelIndex &)
QString group()
The group the function belongs to.
static QList< Function * > specialColumns()
Returns a list of special Column definitions.
int params()
The number of parameters this function takes.
A generic message view for displaying QGIS messages.
QString expressionText()
Gets the expression string that has been set in the expression area.
void registerItem(QString group, QString label, QString expressionText, QString helpText="", QgsExpressionItem::ItemType type=QgsExpressionItem::ExpressionNode)
Registers a node item for the expression builder.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
Do not fetch geometry.
Represents a vector layer which manages a vector based data sets.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfield.h:194
QgsExpressionHighlighter * highlighter
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:97
QString getHelpText()
Get the help text that is associated with this expression item.
QString evalErrorString() const
Returns evaluation error.
#define tr(sourceText)