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