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