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 #include <QDir>
29 #include <QComboBox>
30 
32  : QWidget( parent )
33 {
34  setupUi( this );
35 
36  mValueGroupBox->hide();
37  mLoadGroupBox->hide();
38 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
39 
40  mModel = new QStandardItemModel();
41  mProxyModel = new QgsExpressionItemSearchProxy();
42  mProxyModel->setSourceModel( mModel );
43  expressionTree->setModel( mProxyModel );
44 
45  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
46  connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
47  connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
48  connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
49  this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
50 
51  connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
52  connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
53 
54  foreach ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
55  {
56  connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
57  }
58 
59  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
60 
61  QSettings settings;
62  splitter->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter" ).toByteArray() );
63 
64  txtExpressionString->setFoldingVisible( false );
65 
66  updateFunctionTree();
67 
69  {
70  QgsPythonRunner::eval( "qgis.user.expressionspath", mFunctionsPath );
72  // The scratch file gets written each time the widget opens.
73  saveFunctionFile( "scratch" );
74  updateFunctionFileList( mFunctionsPath );
75  }
76  else
77  {
78  tab_2->setEnabled( false );
79  }
80 }
81 
82 
84 {
85  QSettings settings;
86  settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter", splitter->saveState() );
87 // settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter2", splitter_2->saveState() );
88 }
89 
91 {
92  mLayer = layer;
93 }
94 
95 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
96 {
97  // Get the item
98  QModelIndex idx = mProxyModel->mapToSource( index );
99  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
100  if ( !item )
101  return;
102 
103  if ( item->getItemType() != QgsExpressionItem::Field )
104  {
105  mValueListWidget->clear();
106  }
107 
108  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
109  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
110 
111  // Show the help for the current item.
112  QString help = loadFunctionHelp( item );
113  txtHelpText->setText( help );
114  txtHelpText->setToolTip( txtHelpText->toPlainText() );
115 }
116 
118 {
119  saveFunctionFile( cmbFileNames->currentText() );
120  runPythonCode( txtPython->text() );
121 }
122 
123 void QgsExpressionBuilderWidget::runPythonCode( QString code )
124 {
125  if ( QgsPythonRunner::isValid() )
126  {
127  QString pythontext = code;
128  QgsPythonRunner::run( pythontext );
129  }
130  updateFunctionTree();
131 }
132 
134 {
135  QDir myDir( mFunctionsPath );
136  if ( !myDir.exists() )
137  {
138  myDir.mkpath( mFunctionsPath );
139  }
140 
141  if ( !fileName.endsWith( ".py" ) )
142  {
143  fileName.append( ".py" );
144  }
145 
146  fileName = mFunctionsPath + QDir::separator() + fileName;
147  QFile myFile( fileName );
148  if ( myFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
149  {
150  QTextStream myFileStream( &myFile );
151  myFileStream << txtPython->text() << endl;
152  myFile.close();
153  }
154 }
155 
157 {
158  mFunctionsPath = path;
159  QDir dir( path );
160  dir.setNameFilters( QStringList() << "*.py" );
161  QStringList files = dir.entryList( QDir::Files );
162  cmbFileNames->clear();
163  foreach ( QString name, files )
164  {
165  QFileInfo info( mFunctionsPath + QDir::separator() + name );
166  if ( info.baseName() == "__init__" ) continue;
167  cmbFileNames->addItem( info.baseName() );
168  }
169 }
170 
172 {
173  QString templatetxt;
174  QgsPythonRunner::eval( "qgis.user.expressions.template", templatetxt );
175  txtPython->setText( templatetxt );
176  int index = cmbFileNames->findText( fileName );
177  if ( index == -1 )
178  cmbFileNames->setEditText( fileName );
179  else
180  cmbFileNames->setCurrentIndex( index );
181 }
182 
184 {
185  newFunctionFile();
186 }
187 
189 {
190  if ( index == -1 )
191  return;
192 
193  QString path = mFunctionsPath + QDir::separator() + cmbFileNames->currentText();
194  loadCodeFromFile( path );
195 }
196 
198 {
199  if ( !path.endsWith( ".py" ) )
200  path.append( ".py" );
201 
202  txtPython->loadScript( path );
203 }
204 
206 {
207  txtPython->setText( code );
208 }
209 
211 {
212  QString name = cmbFileNames->currentText();
213  saveFunctionFile( name );
214  int index = cmbFileNames->findText( name );
215  if ( index == -1 )
216  {
217  cmbFileNames->addItem( name );
218  cmbFileNames->setCurrentIndex( cmbFileNames->count() - 1 );
219  }
220 }
221 
223 {
224  QModelIndex idx = mProxyModel->mapToSource( index );
225  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
226  if ( item == 0 )
227  return;
228 
229  // Don't handle the double click it we are on a header node.
230  if ( item->getItemType() == QgsExpressionItem::Header )
231  return;
232 
233  // Insert the expression text or replace selected text
234  txtExpressionString->insertText( item->getExpressionText() );
235  txtExpressionString->setFocus();
236 }
237 
239 {
240  // TODO We should really return a error the user of the widget that
241  // the there is no layer set.
242  if ( !mLayer )
243  return;
244 
245  loadFieldNames( mLayer->pendingFields() );
246 }
247 
249 {
250  if ( fields.isEmpty() )
251  return;
252 
253  QStringList fieldNames;
254  //foreach ( const QgsField& field, fields )
255  for ( int i = 0; i < fields.count(); ++i )
256  {
257  QString fieldName = fields[i].name();
258  fieldNames << fieldName;
259  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
260  }
261 // highlighter->addFields( fieldNames );
262 }
263 
264 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
265 {
266  // TODO We should really return a error the user of the widget that
267  // the there is no layer set.
268  if ( !mLayer )
269  return;
270 
271  // TODO We should thread this so that we don't hold the user up if the layer is massive.
272  mValueListWidget->clear();
273  mValueListWidget->setUpdatesEnabled( false );
274  mValueListWidget->blockSignals( true );
275 
276  QList<QVariant> values;
277  mLayer->uniqueValues( fieldIndex, values, countLimit );
278  foreach ( QVariant value, values )
279  {
280  if ( value.isNull() )
281  mValueListWidget->addItem( "NULL" );
282  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
283  mValueListWidget->addItem( value.toString() );
284  else
285  mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
286  }
287 
288  mValueListWidget->setUpdatesEnabled( true );
289  mValueListWidget->blockSignals( false );
290 }
291 
293  QString label,
294  QString expressionText,
295  QString helpText,
297 {
298  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
299  item->setData( label, Qt::UserRole );
300  // Look up the group and insert the new function.
301  if ( mExpressionGroups.contains( group ) )
302  {
303  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
304  groupNode->appendRow( item );
305  }
306  else
307  {
308  // If the group doesn't exist yet we make it first.
310  newgroupNode->setData( group, Qt::UserRole );
311  newgroupNode->appendRow( item );
312  mModel->appendRow( newgroupNode );
313  mExpressionGroups.insert( group, newgroupNode );
314  }
315 }
316 
318 {
319  return mExpressionValid;
320 }
321 
323 {
324  QSettings settings;
325  QString location = QString( "/expressions/recent/%1" ).arg( key );
326  QStringList expressions = settings.value( location ).toStringList();
327  expressions.removeAll( this->expressionText() );
328 
329  expressions.prepend( this->expressionText() );
330 
331  while ( expressions.count() > 20 )
332  {
333  expressions.pop_back();
334  }
335 
336  settings.setValue( location, expressions );
337  this->loadRecent( key );
338 }
339 
341 {
342  QString name = tr( "Recent (%1)" ).arg( key );
343  if ( mExpressionGroups.contains( name ) )
344  {
345  QgsExpressionItem* node = mExpressionGroups.value( name );
346  node->removeRows( 0, node->rowCount() );
347  }
348 
349  QSettings settings;
350  QString location = QString( "/expressions/recent/%1" ).arg( key );
351  QStringList expressions = settings.value( location ).toStringList();
352  foreach ( QString expression, expressions )
353  {
354  this->registerItem( name, expression, expression, expression );
355  }
356 }
357 
358 void QgsExpressionBuilderWidget::updateFunctionTree()
359 {
360  mModel->clear();
361  mExpressionGroups.clear();
362  // TODO Can we move this stuff to QgsExpression, like the functions?
363  registerItem( "Operators", "+", " + ", tr( "Addition operator" ) );
364  registerItem( "Operators", "-", " - ", tr( "Subtraction operator" ) );
365  registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) );
366  registerItem( "Operators", "/", " / ", tr( "Division operator" ) );
367  registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) );
368  registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) );
369  registerItem( "Operators", "=", " = ", tr( "Equal operator" ) );
370  registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) );
371  registerItem( "Operators", "<", " < ", tr( "Less than operator" ) );
372  registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) );
373  registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) );
374  registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) );
375  registerItem( "Operators", "||", " || ",
376  QString( "<b>|| %1</b><br><i>%2</i><br><i>%3:</i>%4" )
377  .arg( tr( "(String Concatenation)" ) )
378  .arg( tr( "Joins two values together into a string" ) )
379  .arg( tr( "Usage" ) )
380  .arg( tr( "'Dia' || Diameter" ) ) );
381  registerItem( "Operators", "IN", " IN " );
382  registerItem( "Operators", "LIKE", " LIKE " );
383  registerItem( "Operators", "ILIKE", " ILIKE " );
384  registerItem( "Operators", "IS", " IS " );
385  registerItem( "Operators", "OR", " OR " );
386  registerItem( "Operators", "AND", " AND " );
387  registerItem( "Operators", "NOT", " NOT " );
388 
389  QString casestring = "CASE WHEN condition THEN result END";
390  QString caseelsestring = "CASE WHEN condition THEN result ELSE result END";
391  registerItem( "Conditionals", "CASE", casestring );
392  registerItem( "Conditionals", "CASE ELSE", caseelsestring );
393 
394  // Load the functions from the QgsExpression class
395  int count = QgsExpression::functionCount();
396  for ( int i = 0; i < count; i++ )
397  {
399  QString name = func->name();
400  if ( name.startsWith( "_" ) ) // do not display private functions
401  continue;
402  if ( func->params() != 0 )
403  name += "(";
404  registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
405  }
406 
407  QList<QgsExpression::Function*> specials = QgsExpression::specialColumns();
408  for ( int i = 0; i < specials.size(); ++i )
409  {
410  QString name = specials[i]->name();
411  registerItem( specials[i]->group(), name, " " + name + " " );
412  }
413 }
414 
416 {
417  mDa = da;
418 }
419 
421 {
422  return txtExpressionString->text();
423 }
424 
425 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
426 {
427  txtExpressionString->setText( expression );
428 }
429 
431 {
432  QString text = expressionText();
433 
434  // If the string is empty the expression will still "fail" although
435  // we don't show the user an error as it will be confusing.
436  if ( text.isEmpty() )
437  {
438  lblPreview->setText( "" );
439  lblPreview->setStyleSheet( "" );
440  txtExpressionString->setToolTip( "" );
441  lblPreview->setToolTip( "" );
442  emit expressionParsed( false );
443  return;
444  }
445 
446  QgsExpression exp( text );
447 
448  if ( mLayer )
449  {
450  // Only set calculator if we have layer, else use default.
451  exp.setGeomCalculator( mDa );
452 
453  if ( !mFeature.isValid() )
454  {
455  mLayer->getFeatures().nextFeature( mFeature );
456  }
457 
458  if ( mFeature.isValid() )
459  {
460  QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
461  if ( !exp.hasEvalError() )
462  lblPreview->setText( value.toString() );
463  }
464  else
465  {
466  // The feature is invalid because we don't have one but that doesn't mean user can't
467  // build a expression string. They just get no preview.
468  lblPreview->setText( "" );
469  }
470  }
471  else
472  {
473  // No layer defined
474  QVariant value = exp.evaluate();
475  if ( !exp.hasEvalError() )
476  {
477  lblPreview->setText( value.toString() );
478  }
479  }
480 
481  if ( exp.hasParserError() || exp.hasEvalError() )
482  {
483  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
484  if ( exp.hasEvalError() )
485  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
486 
487  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
488  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
489  txtExpressionString->setToolTip( tooltip );
490  lblPreview->setToolTip( tooltip );
491  emit expressionParsed( false );
492  return;
493  }
494  else
495  {
496  lblPreview->setStyleSheet( "" );
497  txtExpressionString->setToolTip( "" );
498  lblPreview->setToolTip( "" );
499  emit expressionParsed( true );
500  }
501 }
502 
504 {
505  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
506  if ( txtSearchEdit->text().isEmpty() )
507  expressionTree->collapseAll();
508  else
509  expressionTree->expandAll();
510 }
511 
513 {
514  Q_UNUSED( link );
515  QgsMessageViewer * mv = new QgsMessageViewer( this );
516  mv->setWindowTitle( tr( "More info on expression error" ) );
517  mv->setMessageAsHtml( txtExpressionString->toolTip() );
518  mv->exec();
519 }
520 
522 {
523  // Insert the item text or replace selected text
524  txtExpressionString->insertText( " " + item->text() + " " );
525  txtExpressionString->setFocus();
526 }
527 
529 {
530  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
531 
532  // Insert the button text or replace selected text
533  txtExpressionString->insertText( " " + button->text() + " " );
534  txtExpressionString->setFocus();
535 }
536 
538 {
539  QModelIndex idx = expressionTree->indexAt( pt );
540  idx = mProxyModel->mapToSource( idx );
541  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
542  if ( !item )
543  return;
544 
545  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
546  {
547  QMenu* menu = new QMenu( this );
548  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
549  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
550  menu->popup( expressionTree->mapToGlobal( pt ) );
551  }
552 }
553 
555 {
556  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
557  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
558  // TODO We should really return a error the user of the widget that
559  // the there is no layer set.
560  if ( !mLayer )
561  return;
562 
563  mValueGroupBox->show();
564  int fieldIndex = mLayer->fieldNameIndex( item->text() );
565  fillFieldValues( fieldIndex, 10 );
566 }
567 
569 {
570  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
571  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
572  // TODO We should really return a error the user of the widget that
573  // the there is no layer set.
574  if ( !mLayer )
575  return;
576 
577  mValueGroupBox->show();
578  int fieldIndex = mLayer->fieldNameIndex( item->text() );
579  fillFieldValues( fieldIndex, -1 );
580 }
581 
582 void QgsExpressionBuilderWidget::setExpressionState( bool state )
583 {
584  mExpressionValid = state;
585 }
586 
587 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* expressionItem )
588 {
589  if ( !expressionItem )
590  return "";
591 
592  QString helpContents = expressionItem->getHelpText();
593 
594  // Return the function help that is set for the function if there is one.
595  if ( helpContents.isEmpty() )
596  {
597  QString name = expressionItem->data( Qt::UserRole ).toString();
598 
599  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
600  helpContents = QgsExpression::helptext( "Field" );
601  else
602  helpContents = QgsExpression::helptext( name );
603  }
604 
605  QString myStyle = QgsApplication::reportStyleSheet();
606  return "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
607 }
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
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
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)
static bool eval(QString command, QString &result)
Eval a python statement.
void updateFunctionFileList(QString path)
Update the list of function files found at the given path.
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.
void loadCodeFromFile(QString path)
Load code from the given file into the function editor.
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)
void loadFunctionCode(QString code)
Load code into the function editor.
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.
void newFunctionFile(QString fileName="scratch")
Create a new file in the function editor.
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)