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