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