QGIS API Documentation  2.99.0-Master (37c43df)
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 #include "qgsgeometry.h"
23 #include "qgsfeature.h"
24 #include "qgsfeatureiterator.h"
25 #include "qgsvectorlayer.h"
26 
27 #include <QSettings>
28 #include <QMenu>
29 #include <QFile>
30 #include <QTextStream>
31 #include <QDir>
32 #include <QInputDialog>
33 #include <QComboBox>
34 #include <QGraphicsOpacityEffect>
35 #include <QPropertyAnimation>
36 
37 
38 
40  : QWidget( parent )
41  , mAutoSave( true )
42  , mLayer( nullptr )
43  , highlighter( nullptr )
44  , mExpressionValid( false )
45 {
46  setupUi( this );
47 
48  mValueGroupBox->hide();
49  mLoadGroupBox->hide();
50 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
51 
52  mModel = new QStandardItemModel();
53  mProxyModel = new QgsExpressionItemSearchProxy();
54  mProxyModel->setDynamicSortFilter( true );
55  mProxyModel->setSourceModel( mModel );
56  expressionTree->setModel( mProxyModel );
57  expressionTree->setSortingEnabled( true );
58  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
59 
60  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
61  connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
62  connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
63  connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
64  this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
65 
66  connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
67  connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
68 
69  Q_FOREACH ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
70  {
71  connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
72  }
73 
74  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
75 
76  mValuesModel = new QStringListModel();
77  mProxyValues = new QSortFilterProxyModel();
78  mProxyValues->setSourceModel( mValuesModel );
79  mValuesListView->setModel( mProxyValues );
80  txtSearchEditValues->setPlaceholderText( tr( "Search" ) );
81 
82  QSettings settings;
83  splitter->restoreState( settings.value( QStringLiteral( "/windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
84  editorSplit->restoreState( settings.value( QStringLiteral( "/windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
85  functionsplit->restoreState( settings.value( QStringLiteral( "/windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
86 
87  txtExpressionString->setFoldingVisible( false );
88 
89  updateFunctionTree();
90 
92  {
93  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
94  updateFunctionFileList( mFunctionsPath );
95  }
96  else
97  {
98  tab_2->hide();
99  }
100 
101  // select the first item in the function list
102  // in order to avoid a blank help widget
103  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
104  expressionTree->setCurrentIndex( firstItem );
105 
106  lblAutoSave->setText( QLatin1String( "" ) );
107 }
108 
109 
111 {
112  QSettings settings;
113  settings.setValue( QStringLiteral( "/windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
114  settings.setValue( QStringLiteral( "/windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
115  settings.setValue( QStringLiteral( "/windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
116 
117  delete mModel;
118  delete mProxyModel;
119  delete mValuesModel;
120  delete mProxyValues;
121 }
122 
124 {
125  mLayer = layer;
126 
127  //TODO - remove existing layer scope from context
128 
129  if ( mLayer )
130  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
131 }
132 
133 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
134 {
135  txtSearchEditValues->setText( QLatin1String( "" ) );
136 
137  // Get the item
138  QModelIndex idx = mProxyModel->mapToSource( index );
139  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
140  if ( !item )
141  return;
142 
143  if ( item->getItemType() == QgsExpressionItem::Field && mFieldValues.contains( item->text() ) )
144  {
145  const QStringList& values = mFieldValues[item->text()];
146  mValuesModel->setStringList( values );
147  }
148 
149  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
150  mValueGroupBox->setVisible(( item->getItemType() == QgsExpressionItem::Field && mLayer ) || mValuesListView->model()->rowCount() > 0 );
151 
152  // Show the help for the current item.
153  QString help = loadFunctionHelp( item );
154  txtHelpText->setText( help );
155 }
156 
157 void QgsExpressionBuilderWidget::on_btnRun_pressed()
158 {
159  if ( !cmbFileNames->currentItem() )
160  return;
161 
162  QString file = cmbFileNames->currentItem()->text();
163  saveFunctionFile( file );
164  runPythonCode( txtPython->text() );
165 }
166 
167 void QgsExpressionBuilderWidget::runPythonCode( const QString& code )
168 {
169  if ( QgsPythonRunner::isValid() )
170  {
171  QString pythontext = code;
172  QgsPythonRunner::run( pythontext );
173  }
174  updateFunctionTree();
175  loadFieldNames();
176  loadRecent( mRecentKey );
177 }
178 
180 {
181  QDir myDir( mFunctionsPath );
182  if ( !myDir.exists() )
183  {
184  myDir.mkpath( mFunctionsPath );
185  }
186 
187  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
188  {
189  fileName.append( ".py" );
190  }
191 
192  fileName = mFunctionsPath + QDir::separator() + fileName;
193  QFile myFile( fileName );
194  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
195  {
196  QTextStream myFileStream( &myFile );
197  myFileStream << txtPython->text() << endl;
198  myFile.close();
199  }
200 }
201 
203 {
204  mFunctionsPath = path;
205  QDir dir( path );
206  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
207  QStringList files = dir.entryList( QDir::Files );
208  cmbFileNames->clear();
209  Q_FOREACH ( const QString& name, files )
210  {
211  QFileInfo info( mFunctionsPath + QDir::separator() + name );
212  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
213  QListWidgetItem* item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), info.baseName() );
214  cmbFileNames->addItem( item );
215  }
216  if ( !cmbFileNames->currentItem() )
217  cmbFileNames->setCurrentRow( 0 );
218 }
219 
220 void QgsExpressionBuilderWidget::newFunctionFile( const QString& fileName )
221 {
222  QList<QListWidgetItem*> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
223  if ( !items.isEmpty() )
224  return;
225 
226  QString templatetxt;
227  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressions.template" ), templatetxt );
228  txtPython->setText( templatetxt );
229  cmbFileNames->insertItem( 0, fileName );
230  cmbFileNames->setCurrentRow( 0 );
231  saveFunctionFile( fileName );
232 }
233 
234 void QgsExpressionBuilderWidget::on_btnNewFile_pressed()
235 {
236  bool ok;
237  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
238  tr( "File name:" ), QLineEdit::Normal,
239  QLatin1String( "" ), &ok );
240  if ( ok && !text.isEmpty() )
241  {
242  newFunctionFile( text );
243  }
244 }
245 
246 void QgsExpressionBuilderWidget::on_cmbFileNames_currentItemChanged( QListWidgetItem* item, QListWidgetItem* lastitem )
247 {
248  if ( lastitem )
249  {
250  QString filename = lastitem->text();
251  saveFunctionFile( filename );
252  }
253  QString path = mFunctionsPath + QDir::separator() + item->text();
254  loadCodeFromFile( path );
255 }
256 
258 {
259  if ( !path.endsWith( QLatin1String( ".py" ) ) )
260  path.append( ".py" );
261 
262  txtPython->loadScript( path );
263 }
264 
266 {
267  txtPython->setText( code );
268 }
269 
270 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
271 {
272  QModelIndex idx = mProxyModel->mapToSource( index );
273  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
274  if ( !item )
275  return;
276 
277  // Don't handle the double click it we are on a header node.
278  if ( item->getItemType() == QgsExpressionItem::Header )
279  return;
280 
281  // Insert the expression text or replace selected text
282  txtExpressionString->insertText( item->getExpressionText() );
283  txtExpressionString->setFocus();
284 }
285 
287 {
288  // TODO We should really return a error the user of the widget that
289  // the there is no layer set.
290  if ( !mLayer )
291  return;
292 
293  loadFieldNames( mLayer->fields() );
294 }
295 
297 {
298  if ( fields.isEmpty() )
299  return;
300 
301  QStringList fieldNames;
302  //Q_FOREACH ( const QgsField& field, fields )
303  fieldNames.reserve( fields.count() );
304  for ( int i = 0; i < fields.count(); ++i )
305  {
306  QString fieldName = fields.at( i ).name();
307  fieldNames << fieldName;
308  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QLatin1String( "" ), QgsExpressionItem::Field, false, i );
309  }
310 // highlighter->addFields( fieldNames );
311 }
312 
313 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
314 {
315  QgsFields fields;
316  Q_FOREACH ( const QString& fieldName, fieldValues.keys() )
317  {
318  fields.append( QgsField( fieldName ) );
319  }
320  loadFieldNames( fields );
321  mFieldValues = fieldValues;
322 }
323 
324 void QgsExpressionBuilderWidget::fillFieldValues( const QString& fieldName, int countLimit )
325 {
326  // TODO We should really return a error the user of the widget that
327  // the there is no layer set.
328  if ( !mLayer )
329  return;
330 
331  // TODO We should thread this so that we don't hold the user up if the layer is massive.
332 
333  int fieldIndex = mLayer->fields().lookupField( fieldName );
334 
335  if ( fieldIndex < 0 )
336  return;
337 
338  QList<QVariant> values;
339  QStringList strValues;
340  mLayer->uniqueValues( fieldIndex, values, countLimit );
341  Q_FOREACH ( const QVariant& value, values )
342  {
343  QString strValue;
344  if ( value.isNull() )
345  strValue = QStringLiteral( "NULL" );
346  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
347  strValue = value.toString();
348  else
349  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
350  strValues.append( strValue );
351  }
352  mValuesModel->setStringList( strValues );
353  mFieldValues[fieldName] = strValues;
354 }
355 
356 void QgsExpressionBuilderWidget::registerItem( const QString& group,
357  const QString& label,
358  const QString& expressionText,
359  const QString& helpText,
360  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
361 {
362  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
363  item->setData( label, Qt::UserRole );
364  item->setData( sortOrder, QgsExpressionItem::CustomSortRole );
365 
366  // Look up the group and insert the new function.
367  if ( mExpressionGroups.contains( group ) )
368  {
369  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
370  groupNode->appendRow( item );
371  }
372  else
373  {
374  // If the group doesn't exist yet we make it first.
375  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
376  newgroupNode->setData( group, Qt::UserRole );
377  //Recent group should always be last group
378  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CustomSortRole );
379  newgroupNode->appendRow( item );
380  newgroupNode->setBackground( QBrush( QColor( "#eee" ) ) );
381  mModel->appendRow( newgroupNode );
382  mExpressionGroups.insert( group, newgroupNode );
383  }
384 
385  if ( highlightedItem )
386  {
387  //insert a copy as a top level item
388  QgsExpressionItem* topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
389  topLevelItem->setData( label, Qt::UserRole );
390  item->setData( 0, QgsExpressionItem::CustomSortRole );
391  QFont font = topLevelItem->font();
392  font.setBold( true );
393  topLevelItem->setFont( font );
394  mModel->appendRow( topLevelItem );
395  }
396 
397 }
398 
400 {
401  return mExpressionValid;
402 }
403 
404 void QgsExpressionBuilderWidget::saveToRecent( const QString& collection )
405 {
406  QSettings settings;
407  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
408  QStringList expressions = settings.value( location ).toStringList();
409  expressions.removeAll( this->expressionText() );
410 
411  expressions.prepend( this->expressionText() );
412 
413  while ( expressions.count() > 20 )
414  {
415  expressions.pop_back();
416  }
417 
418  settings.setValue( location, expressions );
419  this->loadRecent( collection );
420 }
421 
422 void QgsExpressionBuilderWidget::loadRecent( const QString& collection )
423 {
424  mRecentKey = collection;
425  QString name = tr( "Recent (%1)" ).arg( collection );
426  if ( mExpressionGroups.contains( name ) )
427  {
428  QgsExpressionItem* node = mExpressionGroups.value( name );
429  node->removeRows( 0, node->rowCount() );
430  }
431 
432  QSettings settings;
433  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
434  QStringList expressions = settings.value( location ).toStringList();
435  int i = 0;
436  Q_FOREACH ( const QString& expression, expressions )
437  {
438  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
439  i++;
440  }
441 }
442 
443 void QgsExpressionBuilderWidget::updateFunctionTree()
444 {
445  mModel->clear();
446  mExpressionGroups.clear();
447  // TODO Can we move this stuff to QgsExpression, like the functions?
448  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
449  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
450  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "*" ), QStringLiteral( " * " ) );
451  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "/" ), QStringLiteral( " / " ) );
452  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "%" ), QStringLiteral( " % " ) );
453  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "^" ), QStringLiteral( " ^ " ) );
454  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "=" ), QStringLiteral( " = " ) );
455  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">" ), QStringLiteral( " > " ) );
456  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<" ), QStringLiteral( " < " ) );
457  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
458  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
459  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
460  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
461  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
462  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
463  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
464  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
465  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
466  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
467  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
468 
469  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
470  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
471 
472  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
473 
474  // Load the functions from the QgsExpression class
475  int count = QgsExpression::functionCount();
476  for ( int i = 0; i < count; i++ )
477  {
479  QString name = func->name();
480  if ( name.startsWith( '_' ) ) // do not display private functions
481  continue;
482  if ( func->isDeprecated() ) // don't show deprecated functions
483  continue;
484  if ( func->isContextual() )
485  {
486  //don't show contextual functions by default - it's up the the QgsExpressionContext
487  //object to provide them if supported
488  continue;
489  }
490  if ( func->params() != 0 )
491  name += '(';
492  else if ( !name.startsWith( '$' ) )
493  name += QLatin1String( "()" );
494  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
495  }
496 
497  loadExpressionContext();
498 }
499 
501 {
502  mDa = da;
503 }
504 
506 {
507  return txtExpressionString->text();
508 }
509 
510 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
511 {
512  txtExpressionString->setText( expression );
513 }
514 
516 {
517  mExpressionContext = context;
518 
519  loadExpressionContext();
520 }
521 
522 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
523 {
524  QString text = expressionText();
525 
526  // If the string is empty the expression will still "fail" although
527  // we don't show the user an error as it will be confusing.
528  if ( text.isEmpty() )
529  {
530  lblPreview->setText( QLatin1String( "" ) );
531  lblPreview->setStyleSheet( QLatin1String( "" ) );
532  txtExpressionString->setToolTip( QLatin1String( "" ) );
533  lblPreview->setToolTip( QLatin1String( "" ) );
534  emit expressionParsed( false );
535  return;
536  }
537 
538  QgsExpression exp( text );
539 
540  if ( mLayer )
541  {
542  // Only set calculator if we have layer, else use default.
543  exp.setGeomCalculator( &mDa );
544 
545  if ( !mFeature.isValid() )
546  {
547  mLayer->getFeatures().nextFeature( mFeature );
548  }
549 
550  if ( mFeature.isValid() )
551  {
552  mExpressionContext.setFeature( mFeature );
553  QVariant value = exp.evaluate( &mExpressionContext );
554  if ( !exp.hasEvalError() )
555  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
556  }
557  else
558  {
559  // The feature is invalid because we don't have one but that doesn't mean user can't
560  // build a expression string. They just get no preview.
561  lblPreview->setText( QLatin1String( "" ) );
562  }
563  }
564  else
565  {
566  // No layer defined
567  QVariant value = exp.evaluate( &mExpressionContext );
568  if ( !exp.hasEvalError() )
569  {
570  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
571  }
572  }
573 
574  if ( exp.hasParserError() || exp.hasEvalError() )
575  {
576  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
577  if ( exp.hasEvalError() )
578  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
579 
580  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
581  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
582  txtExpressionString->setToolTip( tooltip );
583  lblPreview->setToolTip( tooltip );
584  emit expressionParsed( false );
585  return;
586  }
587  else
588  {
589  lblPreview->setStyleSheet( QLatin1String( "" ) );
590  txtExpressionString->setToolTip( QLatin1String( "" ) );
591  lblPreview->setToolTip( QLatin1String( "" ) );
592  emit expressionParsed( true );
593  }
594 }
595 
596 void QgsExpressionBuilderWidget::loadExpressionContext()
597 {
598  QStringList variableNames = mExpressionContext.filteredVariableNames();
599  Q_FOREACH ( const QString& variable, variableNames )
600  {
601  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
602  QgsExpression::variableHelpText( variable, true, mExpressionContext.variable( variable ) ),
604  mExpressionContext.isHighlightedVariable( variable ) );
605  }
606 
607  // Load the functions from the expression context
608  QStringList contextFunctions = mExpressionContext.functionNames();
609  Q_FOREACH ( const QString& functionName, contextFunctions )
610  {
611  QgsExpression::Function* func = mExpressionContext.function( functionName );
612  QString name = func->name();
613  if ( name.startsWith( '_' ) ) // do not display private functions
614  continue;
615  if ( func->params() != 0 )
616  name += '(';
617  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
618  }
619 }
620 
621 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList& groups, const QString& label, const QString& expressionText, const QString& helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
622 {
623  Q_FOREACH ( const QString& group, groups )
624  {
625  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
626  }
627 }
628 
630 {
631  QWidget::showEvent( e );
632  txtExpressionString->setFocus();
633 }
634 
635 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
636 {
637  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
638  if ( txtSearchEdit->text().isEmpty() )
639  {
640  expressionTree->collapseAll();
641  }
642  else
643  {
644  expressionTree->expandAll();
645  QModelIndex index = mProxyModel->index( 0, 0 );
646  if ( mProxyModel->hasChildren( index ) )
647  {
648  QModelIndex child = mProxyModel->index( 0, 0, index );
649  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
650  }
651  }
652 }
653 
654 void QgsExpressionBuilderWidget::on_txtSearchEditValues_textChanged()
655 {
656  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
657  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
658 }
659 
660 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString& link )
661 {
662  Q_UNUSED( link );
663  QgsMessageViewer * mv = new QgsMessageViewer( this );
664  mv->setWindowTitle( tr( "More info on expression error" ) );
665  mv->setMessageAsHtml( txtExpressionString->toolTip() );
666  mv->exec();
667 }
668 
669 void QgsExpressionBuilderWidget::on_mValuesListView_doubleClicked( const QModelIndex &index )
670 {
671  // Insert the item text or replace selected text
672  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
673  txtExpressionString->setFocus();
674 }
675 
676 void QgsExpressionBuilderWidget::operatorButtonClicked()
677 {
678  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
679 
680  // Insert the button text or replace selected text
681  txtExpressionString->insertText( ' ' + button->text() + ' ' );
682  txtExpressionString->setFocus();
683 }
684 
685 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
686 {
687  QModelIndex idx = expressionTree->indexAt( pt );
688  idx = mProxyModel->mapToSource( idx );
689  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
690  if ( !item )
691  return;
692 
693  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
694  {
695  QMenu* menu = new QMenu( this );
696  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
697  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
698  menu->popup( expressionTree->mapToGlobal( pt ) );
699  }
700 }
701 
703 {
704  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
705  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
706  // TODO We should really return a error the user of the widget that
707  // the there is no layer set.
708  if ( !mLayer || !item )
709  return;
710 
711  mValueGroupBox->show();
712  fillFieldValues( item->text(), 10 );
713 }
714 
716 {
717  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
718  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
719  // TODO We should really return a error the user of the widget that
720  // the there is no layer set.
721  if ( !mLayer || !item )
722  return;
723 
724  mValueGroupBox->show();
725  fillFieldValues( item->text(), -1 );
726 }
727 
728 void QgsExpressionBuilderWidget::on_txtPython_textChanged()
729 {
730  lblAutoSave->setText( QStringLiteral( "Saving..." ) );
731  if ( mAutoSave )
732  {
733  autosave();
734  }
735 }
736 
738 {
739  // Don't auto save if not on function editor that would be silly.
740  if ( tabWidget->currentIndex() != 1 )
741  return;
742 
743  QListWidgetItem* item = cmbFileNames->currentItem();
744  if ( !item )
745  return;
746 
747  QString file = item->text();
748  saveFunctionFile( file );
749  lblAutoSave->setText( QStringLiteral( "Saved" ) );
750  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
751  lblAutoSave->setGraphicsEffect( effect );
752  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
753  anim->setDuration( 2000 );
754  anim->setStartValue( 1.0 );
755  anim->setEndValue( 0.0 );
756  anim->setEasingCurve( QEasingCurve::OutQuad );
757  anim->start( QAbstractAnimation::DeleteWhenStopped );
758 }
759 
760 void QgsExpressionBuilderWidget::setExpressionState( bool state )
761 {
762  mExpressionValid = state;
763 }
764 
765 QString QgsExpressionBuilderWidget::helpStylesheet() const
766 {
767  //start with default QGIS report style
768  QString style = QgsApplication::reportStyleSheet();
769 
770  //add some tweaks
771  style += " .functionname {color: #0a6099; font-weight: bold;} "
772  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
773  " td.argument { padding-right: 10px; }";
774 
775  return style;
776 }
777 
778 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* expressionItem )
779 {
780  if ( !expressionItem )
781  return QLatin1String( "" );
782 
783  QString helpContents = expressionItem->getHelpText();
784 
785  // Return the function help that is set for the function if there is one.
786  if ( helpContents.isEmpty() )
787  {
788  QString name = expressionItem->data( Qt::UserRole ).toString();
789 
790  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
791  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
792  else
793  helpContents = QgsExpression::helpText( name );
794  }
795 
796  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
797 }
798 
799 
800 
801 
802 
804 {
805  setFilterCaseSensitivity( Qt::CaseInsensitive );
806 }
807 
808 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
809 {
810  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
811  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ItemTypeRole ).toInt() );
812 
813  int count = sourceModel()->rowCount( index );
814  bool matchchild = false;
815  for ( int i = 0; i < count; ++i )
816  {
817  if ( filterAcceptsRow( i, index ) )
818  {
819  matchchild = true;
820  break;
821  }
822  }
823 
824  if ( itemType == QgsExpressionItem::Header && matchchild )
825  return true;
826 
827  if ( itemType == QgsExpressionItem::Header )
828  return false;
829 
830  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
831 }
832 
833 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex& left, const QModelIndex& right ) const
834 {
835  int leftSort = sourceModel()->data( left, QgsExpressionItem::CustomSortRole ).toInt();
836  int rightSort = sourceModel()->data( right, QgsExpressionItem::CustomSortRole ).toInt();
837  if ( leftSort != rightSort )
838  return leftSort < rightSort;
839 
840  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
841  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
842 
843  //ignore $ prefixes when sorting
844  if ( leftString.startsWith( '$' ) )
845  leftString = leftString.mid( 1 );
846  if ( rightString.startsWith( '$' ) )
847  rightString = rightString.mid( 1 );
848 
849  return QString::localeAwareCompare( leftString, rightString ) < 0;
850 }
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1) const
Calculates a list of unique values contained within an attribute in the layer.
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:291
void registerItem(const QString &group, const QString &label, const QString &expressionText, const QString &helpText="", QgsExpressionItem::ItemType type=QgsExpressionItem::ExpressionNode, bool highlightedItem=false, int sortOrder=1)
Registers a node item for the expression builder.
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:200
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
static unsigned index
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
QString name
Definition: qgsfield.h:55
const QString helpText() const
The help text for the function.
static QString group(const QString &group)
Returns the translated name for a function group.
static const int CustomSortRole
Custom sort order role.
A abstract base class for defining QgsExpression functions.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
void loadSampleValues()
Load sample values into the sample value area.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
QVariant evaluate()
Evaluate the feature and return the result.
QString evalErrorString() const
Returns evaluation error.
Container of fields for a vector layer.
Definition: qgsfields.h:36
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
static QString formatPreviewString(const QVariant &value)
Formats an expression result for friendly display to the user.
static QString reportStyleSheet()
get a standard css style sheet for reports.
int count() const
Return number of items.
Definition: qgsfields.cpp:117
QString parserErrorString() const
Returns parser error.
void loadFunctionCode(const QString &code)
Load code into the function editor.
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
void loadCodeFromFile(QString path)
Load code from the given file into the function editor.
void newFunctionFile(const QString &fileName="scratch")
Create a new file in the function editor.
QgsFields fields() const
Returns the list of fields of this layer.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:137
void updateFunctionFileList(const QString &path)
Update the list of function files found at the given path.
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.
bool isHighlightedVariable(const QString &name) const
Returns true if the specified variable name is intended to be highlighted to the user.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
QString name() const
The name of the function.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Append a field. The field must have unique name, otherwise it is rejected (returns false) ...
Definition: qgsfields.cpp:61
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object...
void loadFieldNames()
Loads all the field names from the layer.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:47
void autosave()
Auto save the current Python function code.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the widget.
static bool eval(const QString &command, QString &result)
Eval a python statement.
QgsExpressionItem::ItemType getItemType() const
Get the type of expression item eg header, field, ExpressionNode.
General purpose distance and area calculator.
QgsExpression::Function * function(const QString &name) const
Fetches a matching function from the context.
An expression item that can be used in the QgsExpressionBuilderWidget tree.
void setGeomCalculator(const QgsDistanceArea *calc)
Sets the geometry calculator used for distance and area calculations in expressions.
QStringList functionNames() const
Retrieves a list of function names contained in the context.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
QString getExpressionText() const
static const int ItemTypeRole
Item type role.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
A generic message view for displaying QGIS messages.
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfields.cpp:112
QString expressionText()
Gets the expression string that has been set in the expression area.
static QString helpText(QString name)
Returns the help text for a specified function.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
QString getHelpText() const
Get the help text that is associated with this expression item.
static QString variableHelpText(const QString &variableName, bool showValue=true, const QVariant &value=QVariant())
Returns the help text for a specified variable.
int params() const
The number of parameters this function takes.
QgsExpressionBuilderWidget(QWidget *parent=nullptr)
Create a new expression builder widget with an optional parent.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
QStringList groups() const
Returns a list of the groups the function belongs to.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
Represents a vector layer which manages a vector based data sets.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
void saveToRecent(const QString &collection="generic")
Adds the current expression to the given collection.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands)
void loadAllValues()
Load all unique values from the set layer into the sample area.
virtual bool isDeprecated() const
Returns true if the function is deprecated and should not be presented as a valid option to users in ...