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