QGIS API Documentation  2.4.0-Chugiak
 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 #include <QSettings>
27 
29  : QWidget( parent )
30 {
31  setupUi( this );
32 
33  mValueGroupBox->hide();
34  mLoadGroupBox->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  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
108 
109  QSettings settings;
110  splitter->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter" ).toByteArray() );
111  splitter_2->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter2" ).toByteArray() );
112 }
113 
114 
116 {
117  QSettings settings;
118  settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter", splitter->saveState() );
119  settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter2", splitter_2->saveState() );
120 }
121 
123 {
124  mLayer = layer;
125 }
126 
127 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
128 {
129  // Get the item
130  QModelIndex idx = mProxyModel->mapToSource( index );
131  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
132  if ( !item )
133  return;
134 
135  if ( item->getItemType() != QgsExpressionItem::Field )
136  {
137  mValueListWidget->clear();
138  }
139 
140  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
141  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
142 
143  // Show the help for the current item.
144  QString help = loadFunctionHelp( item );
145  txtHelpText->setText( help );
146  txtHelpText->setToolTip( txtHelpText->toPlainText() );
147 }
148 
150 {
151  QModelIndex idx = mProxyModel->mapToSource( index );
152  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
153  if ( item == 0 )
154  return;
155 
156  // Don't handle the double click it we are on a header node.
157  if ( item->getItemType() == QgsExpressionItem::Header )
158  return;
159 
160  // Insert the expression text.
161  txtExpressionString->insertPlainText( item->getExpressionText() );
162  txtExpressionString->setFocus();
163 }
164 
166 {
167  // TODO We should really return a error the user of the widget that
168  // the there is no layer set.
169  if ( !mLayer )
170  return;
171 
173 }
174 
176 {
177  if ( fields.isEmpty() )
178  return;
179 
180  QStringList fieldNames;
181  //foreach ( const QgsField& field, fields )
182  for ( int i = 0; i < fields.count(); ++i )
183  {
184  QString fieldName = fields[i].name();
185  fieldNames << fieldName;
186  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
187  }
188  highlighter->addFields( fieldNames );
189 }
190 
191 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
192 {
193  // TODO We should really return a error the user of the widget that
194  // the there is no layer set.
195  if ( !mLayer )
196  return;
197 
198  // TODO We should thread this so that we don't hold the user up if the layer is massive.
199  mValueListWidget->clear();
200  mValueListWidget->setUpdatesEnabled( false );
201  mValueListWidget->blockSignals( true );
202 
203  QList<QVariant> values;
204  mLayer->uniqueValues( fieldIndex, values, countLimit );
205  foreach ( QVariant value, values )
206  {
207  if ( value.isNull() )
208  mValueListWidget->addItem( "NULL" );
209  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
210  mValueListWidget->addItem( value.toString() );
211  else
212  mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
213  }
214 
215  mValueListWidget->setUpdatesEnabled( true );
216  mValueListWidget->blockSignals( false );
217 }
218 
220  QString label,
221  QString expressionText,
222  QString helpText,
224 {
225  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
226  item->setData( label, Qt::UserRole );
227  // Look up the group and insert the new function.
228  if ( mExpressionGroups.contains( group ) )
229  {
230  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
231  groupNode->appendRow( item );
232  }
233  else
234  {
235  // If the group doesn't exist yet we make it first.
237  newgroupNode->setData( group, Qt::UserRole );
238  newgroupNode->appendRow( item );
239  mModel->appendRow( newgroupNode );
240  mExpressionGroups.insert( group, newgroupNode );
241  }
242 }
243 
245 {
246  return mExpressionValid;
247 }
248 
250 {
251  QSettings settings;
252  QString location = QString( "/expressions/recent/%1" ).arg( key );
253  QStringList expressions = settings.value( location ).toStringList();
254  expressions.removeAll( this->expressionText() );
255 
256  expressions.prepend( this->expressionText() );
257 
258  while ( expressions.count() > 20 )
259  {
260  expressions.pop_back();
261  }
262 
263  settings.setValue( location, expressions );
264  this->loadRecent( key );
265 }
266 
268 {
269  QString name = tr( "Recent (%1)" ).arg( key );
270  if ( mExpressionGroups.contains( name ) )
271  {
272  QgsExpressionItem* node = mExpressionGroups.value( name );
273  node->removeRows( 0, node->rowCount() );
274  }
275 
276  QSettings settings;
277  QString location = QString( "/expressions/recent/%1" ).arg( key );
278  QStringList expressions = settings.value( location ).toStringList();
279  foreach ( QString expression, expressions )
280  {
281  this->registerItem( name, expression, expression, expression );
282  }
283 }
284 
286 {
287  mDa = da;
288 }
289 
291 {
292  return txtExpressionString->toPlainText();
293 }
294 
295 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
296 {
297  txtExpressionString->setPlainText( expression );
298 }
299 
301 {
302  QString text = txtExpressionString->toPlainText();
303 
304  // If the string is empty the expression will still "fail" although
305  // we don't show the user an error as it will be confusing.
306  if ( text.isEmpty() )
307  {
308  lblPreview->setText( "" );
309  lblPreview->setStyleSheet( "" );
310  txtExpressionString->setToolTip( "" );
311  lblPreview->setToolTip( "" );
312  emit expressionParsed( false );
313  return;
314  }
315 
316 
317 
318  QgsExpression exp( text );
319 
320  if ( mLayer )
321  {
322  // Only set calculator if we have layer, else use default.
323  exp.setGeomCalculator( mDa );
324 
325  if ( !mFeature.isValid() )
326  {
328  }
329 
330  if ( mFeature.isValid() )
331  {
332  QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
333  if ( !exp.hasEvalError() )
334  lblPreview->setText( value.toString() );
335  }
336  else
337  {
338  // The feature is invalid because we don't have one but that doesn't mean user can't
339  // build a expression string. They just get no preview.
340  lblPreview->setText( "" );
341  }
342  }
343  else
344  {
345  // No layer defined
346  QVariant value = exp.evaluate();
347  if ( !exp.hasEvalError() )
348  {
349  lblPreview->setText( value.toString() );
350  }
351  }
352 
353  if ( exp.hasParserError() || exp.hasEvalError() )
354  {
355  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
356  if ( exp.hasEvalError() )
357  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
358 
359  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
360  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
361  txtExpressionString->setToolTip( tooltip );
362  lblPreview->setToolTip( tooltip );
363  emit expressionParsed( false );
364  return;
365  }
366  else
367  {
368  lblPreview->setStyleSheet( "" );
369  txtExpressionString->setToolTip( "" );
370  lblPreview->setToolTip( "" );
371  emit expressionParsed( true );
372  }
373 }
374 
376 {
377  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
378  if ( txtSearchEdit->text().isEmpty() )
379  expressionTree->collapseAll();
380  else
381  expressionTree->expandAll();
382 }
383 
385 {
386  Q_UNUSED( link );
387  QgsMessageViewer * mv = new QgsMessageViewer( this );
388  mv->setWindowTitle( tr( "More info on expression error" ) );
389  mv->setMessageAsHtml( txtExpressionString->toolTip() );
390  mv->exec();
391 }
392 
394 {
395  txtExpressionString->insertPlainText( " " + item->text() + " " );
396  txtExpressionString->setFocus();
397 }
398 
400 {
401  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
402  txtExpressionString->insertPlainText( " " + button->text() + " " );
403  txtExpressionString->setFocus();
404 }
405 
407 {
408  QModelIndex idx = expressionTree->indexAt( pt );
409  idx = mProxyModel->mapToSource( idx );
410  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
411  if ( !item )
412  return;
413 
414  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
415  {
416  QMenu* menu = new QMenu( this );
417  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
418  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
419  menu->popup( expressionTree->mapToGlobal( pt ) );
420  }
421 }
422 
424 {
425  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
426  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
427  // TODO We should really return a error the user of the widget that
428  // the there is no layer set.
429  if ( !mLayer )
430  return;
431 
432  mValueGroupBox->show();
433  int fieldIndex = mLayer->fieldNameIndex( item->text() );
434  fillFieldValues( fieldIndex, 10 );
435 }
436 
438 {
439  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
440  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
441  // TODO We should really return a error the user of the widget that
442  // the there is no layer set.
443  if ( !mLayer )
444  return;
445 
446  mValueGroupBox->show();
447  int fieldIndex = mLayer->fieldNameIndex( item->text() );
448  fillFieldValues( fieldIndex, -1 );
449 }
450 
452 {
453  mExpressionValid = state;
454 }
455 
457 {
458  if ( !expressionItem )
459  return "";
460 
461  QString helpContents = expressionItem->getHelpText();
462 
463  // Return the function help that is set for the function if there is one.
464  if ( helpContents.isEmpty() )
465  {
466  QString name = expressionItem->data( Qt::UserRole ).toString();
467 
468  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
469  helpContents = QgsExpression::helptext( "Field" );
470  else
471  helpContents = QgsExpression::helptext( name );
472  }
473 
474  QString myStyle = QgsApplication::reportStyleSheet();
475  return "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
476 }
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:161
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:195
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:193
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)