QGIS API Documentation  2.3.0-Master
 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  mLoadGroupBox->hide();
34  highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
35 
36  mModel = new QStandardItemModel( );
38  mProxyModel->setSourceModel( mModel );
39  expressionTree->setModel( mProxyModel );
40 
41  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
42  connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
43  connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
44  connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
45  this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
46 
47  connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
48  connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
49 
50  foreach ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
51  {
52  connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
53  }
54 
55  // TODO Can we move this stuff to QgsExpression, like the functions?
56  registerItem( "Operators", "+", " + ", tr( "Addition operator" ) );
57  registerItem( "Operators", "-", " -" , tr( "Subtraction operator" ) );
58  registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) );
59  registerItem( "Operators", "/", " / ", tr( "Division operator" ) );
60  registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) );
61  registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) );
62  registerItem( "Operators", "=", " = ", tr( "Equal operator" ) );
63  registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) );
64  registerItem( "Operators", "<", " < ", tr( "Less than operator" ) );
65  registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) );
66  registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) );
67  registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) );
68  registerItem( "Operators", "||", " || ",
69  QString( "<b>|| %1</b><br><i>%2</i><br><i>%3:</i>%4" )
70  .arg( tr( "(String Concatenation)" ) )
71  .arg( tr( "Joins two values together into a string" ) )
72  .arg( tr( "Usage" ) )
73  .arg( tr( "'Dia' || Diameter" ) ) );
74  registerItem( "Operators", "LIKE", " LIKE " );
75  registerItem( "Operators", "ILIKE", " ILIKE " );
76  registerItem( "Operators", "IS", " IS " );
77  registerItem( "Operators", "OR", " OR " );
78  registerItem( "Operators", "AND", " AND " );
79  registerItem( "Operators", "NOT", " NOT " );
80 
81  QString casestring = "CASE WHEN condition THEN result END";
82  QString caseelsestring = "CASE WHEN condition THEN result ELSE result END";
83  registerItem( "Conditionals", "CASE", casestring );
84  registerItem( "Conditionals", "CASE ELSE", caseelsestring );
85 
86  // Load the functions from the QgsExpression class
87  int count = QgsExpression::functionCount();
88  for ( int i = 0; i < count; i++ )
89  {
91  QString name = func->name();
92  if ( name.startsWith( "_" ) ) // do not display private functions
93  continue;
94  if ( func->params() >= 1 )
95  name += "(";
96  registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
97  }
98 
99  QList<QgsExpression::Function*> specials = QgsExpression::specialColumns();
100  for ( int i = 0; i < specials.size(); ++i )
101  {
102  QString name = specials[i]->name();
103  registerItem( specials[i]->group(), name, " " + name + " " );
104  }
105 
106  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
107 }
108 
109 
111 {
112 
113 }
114 
116 {
117  mLayer = layer;
118 }
119 
120 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
121 {
122  // Get the item
123  QModelIndex idx = mProxyModel->mapToSource( index );
124  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
125  if ( !item )
126  return;
127 
128  if ( item->getItemType() != QgsExpressionItem::Field )
129  {
130  mValueListWidget->clear();
131  }
132 
133  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
134  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
135 
136  // Show the help for the current item.
137  QString help = loadFunctionHelp( item );
138  txtHelpText->setText( help );
139  txtHelpText->setToolTip( txtHelpText->toPlainText() );
140 }
141 
143 {
144  QModelIndex idx = mProxyModel->mapToSource( index );
145  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
146  if ( item == 0 )
147  return;
148 
149  // Don't handle the double click it we are on a header node.
150  if ( item->getItemType() == QgsExpressionItem::Header )
151  return;
152 
153  // Insert the expression text.
154  txtExpressionString->insertPlainText( item->getExpressionText() );
155  txtExpressionString->setFocus();
156 }
157 
159 {
160  // TODO We should really return a error the user of the widget that
161  // the there is no layer set.
162  if ( !mLayer )
163  return;
164 
166 }
167 
169 {
170  if ( fields.isEmpty() )
171  return;
172 
173  QStringList fieldNames;
174  //foreach ( const QgsField& field, fields )
175  for ( int i = 0; i < fields.count(); ++i )
176  {
177  QString fieldName = fields[i].name();
178  fieldNames << fieldName;
179  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
180  }
181  highlighter->addFields( fieldNames );
182 }
183 
184 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
185 {
186  // TODO We should really return a error the user of the widget that
187  // the there is no layer set.
188  if ( !mLayer )
189  return;
190 
191  // TODO We should thread this so that we don't hold the user up if the layer is massive.
192  mValueListWidget->clear();
193  mValueListWidget->setUpdatesEnabled( false );
194  mValueListWidget->blockSignals( true );
195 
196  QList<QVariant> values;
197  mLayer->uniqueValues( fieldIndex, values, countLimit );
198  foreach ( QVariant value, values )
199  {
200  if ( value.isNull() )
201  mValueListWidget->addItem( "NULL" );
202  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
203  mValueListWidget->addItem( value.toString() );
204  else
205  mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
206  }
207 
208  mValueListWidget->setUpdatesEnabled( true );
209  mValueListWidget->blockSignals( false );
210 }
211 
213  QString label,
214  QString expressionText,
215  QString helpText,
217 {
218  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
219  item->setData( label, Qt::UserRole );
220  // Look up the group and insert the new function.
221  if ( mExpressionGroups.contains( group ) )
222  {
223  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
224  groupNode->appendRow( item );
225  }
226  else
227  {
228  // If the group doesn't exist yet we make it first.
230  newgroupNode->setData( group, Qt::UserRole );
231  newgroupNode->appendRow( item );
232  mModel->appendRow( newgroupNode );
233  mExpressionGroups.insert( group, newgroupNode );
234  }
235 }
236 
238 {
239  return mExpressionValid;
240 }
241 
243 {
244  QSettings settings;
245  QString location = QString( "/expressions/recent/%1" ).arg( key );
246  QStringList expressions = settings.value( location ).toStringList();
247  expressions.removeAll( this->expressionText() );
248 
249  expressions.prepend( this->expressionText() );
250 
251  while ( expressions.count() > 20 )
252  {
253  expressions.pop_back();
254  }
255 
256  settings.setValue( location, expressions );
257  this->loadRecent( key );
258 }
259 
261 {
262  QString name = tr( "Recent (%1)" ).arg( key );
263  if ( mExpressionGroups.contains( name ) )
264  {
265  QgsExpressionItem* node = mExpressionGroups.value( name );
266  node->removeRows( 0, node->rowCount() );
267  }
268 
269  QSettings settings;
270  QString location = QString( "/expressions/recent/%1" ).arg( key );
271  QStringList expressions = settings.value( location ).toStringList();
272  foreach ( QString expression, expressions )
273  {
274  this->registerItem( name, expression, expression, expression );
275  }
276 }
277 
279 {
280  mDa = da;
281 }
282 
284 {
285  return txtExpressionString->toPlainText();
286 }
287 
288 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
289 {
290  txtExpressionString->setPlainText( expression );
291 }
292 
294 {
295  QString text = txtExpressionString->toPlainText();
296 
297  // If the string is empty the expression will still "fail" although
298  // we don't show the user an error as it will be confusing.
299  if ( text.isEmpty() )
300  {
301  lblPreview->setText( "" );
302  lblPreview->setStyleSheet( "" );
303  txtExpressionString->setToolTip( "" );
304  lblPreview->setToolTip( "" );
305  emit expressionParsed( false );
306  return;
307  }
308 
309 
310 
311  QgsExpression exp( text );
312 
313  if ( mLayer )
314  {
315  // Only set calculator if we have layer, else use default.
316  exp.setGeomCalculator( mDa );
317 
318  if ( !mFeature.isValid() )
319  {
321  }
322 
323  if ( mFeature.isValid() )
324  {
325  QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
326  if ( !exp.hasEvalError() )
327  lblPreview->setText( value.toString() );
328  }
329  else
330  {
331  // The feature is invalid because we don't have one but that doesn't mean user can't
332  // build a expression string. They just get no preview.
333  lblPreview->setText( "" );
334  }
335  }
336  else
337  {
338  // No layer defined
339  QVariant value = exp.evaluate();
340  if ( !exp.hasEvalError() )
341  {
342  lblPreview->setText( value.toString() );
343  }
344  }
345 
346  if ( exp.hasParserError() || exp.hasEvalError() )
347  {
348  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
349  if ( exp.hasEvalError() )
350  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
351 
352  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
353  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
354  txtExpressionString->setToolTip( tooltip );
355  lblPreview->setToolTip( tooltip );
356  emit expressionParsed( false );
357  return;
358  }
359  else
360  {
361  lblPreview->setStyleSheet( "" );
362  txtExpressionString->setToolTip( "" );
363  lblPreview->setToolTip( "" );
364  emit expressionParsed( true );
365  }
366 }
367 
369 {
370  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
371  if ( txtSearchEdit->text().isEmpty() )
372  expressionTree->collapseAll();
373  else
374  expressionTree->expandAll();
375 }
376 
378 {
379  Q_UNUSED( link );
380  QgsMessageViewer * mv = new QgsMessageViewer( this );
381  mv->setWindowTitle( tr( "More info on expression error" ) );
382  mv->setMessageAsHtml( txtExpressionString->toolTip() );
383  mv->exec();
384 }
385 
387 {
388  txtExpressionString->insertPlainText( " " + item->text() + " " );
389  txtExpressionString->setFocus();
390 }
391 
393 {
394  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
395  txtExpressionString->insertPlainText( " " + button->text() + " " );
396  txtExpressionString->setFocus();
397 }
398 
400 {
401  QModelIndex idx = expressionTree->indexAt( pt );
402  idx = mProxyModel->mapToSource( idx );
403  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
404  if ( !item )
405  return;
406 
407  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
408  {
409  QMenu* menu = new QMenu( this );
410  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
411  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
412  menu->popup( expressionTree->mapToGlobal( pt ) );
413  }
414 }
415 
417 {
418  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
419  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
420  // TODO We should really return a error the user of the widget that
421  // the there is no layer set.
422  if ( !mLayer )
423  return;
424 
425  mValueGroupBox->show();
426  int fieldIndex = mLayer->fieldNameIndex( item->text() );
427  fillFieldValues( fieldIndex, 10 );
428 }
429 
431 {
432  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
433  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
434  // TODO We should really return a error the user of the widget that
435  // the there is no layer set.
436  if ( !mLayer )
437  return;
438 
439  mValueGroupBox->show();
440  int fieldIndex = mLayer->fieldNameIndex( item->text() );
441  fillFieldValues( fieldIndex, -1 );
442 }
443 
445 {
446  mExpressionValid = state;
447 }
448 
450 {
451  if ( !expressionItem )
452  return "";
453 
454  QString helpContents = expressionItem->getHelpText();
455 
456  // Return the function help that is set for the function if there is one.
457  if ( helpContents.isEmpty() )
458  {
459  QString name = expressionItem->data( Qt::UserRole ).toString();
460 
461  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
462  helpContents = QgsExpression::helptext( "Field" );
463  else
464  helpContents = QgsExpression::helptext( name );
465  }
466 
467  QString myStyle = QgsApplication::reportStyleSheet();
468  return "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
469 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:89
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
static unsigned index
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:96
bool isValid() const
Return the validity of this feature.
Definition: qgsfeature.cpp:171
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:164
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)
int count() const
Return number of items.
Definition: qgsfield.h:198
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.
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 setGeomCalculator(const QgsDistanceArea &calc)
Sets the geometry calculator used in evaluation of expressions,.
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
bool nextFeature(QgsFeature &f)
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:196
QgsExpressionHighlighter * highlighter
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:98
QString getHelpText()
Get the help text that is associated with this expression item.
QString evalErrorString() const
Returns evaluation error.
#define tr(sourceText)