QGIS API Documentation  3.0.2-Girona (307d082)
qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgisexpressionbuilderwidget.cpp - A generic 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 "qgsexpressionfunction.h"
20 #include "qgsmessageviewer.h"
21 #include "qgsapplication.h"
22 #include "qgspythonrunner.h"
23 #include "qgsgeometry.h"
24 #include "qgsfeature.h"
25 #include "qgsfeatureiterator.h"
26 #include "qgsvectorlayer.h"
27 #include "qgssettings.h"
28 #include "qgsproject.h"
29 #include "qgsrelationmanager.h"
30 #include "qgsrelation.h"
31 
32 #include <QMenu>
33 #include <QFile>
34 #include <QTextStream>
35 #include <QDir>
36 #include <QInputDialog>
37 #include <QComboBox>
38 #include <QGraphicsOpacityEffect>
39 #include <QPropertyAnimation>
40 
41 
43  : QWidget( parent )
44  , mProject( QgsProject::instance() )
45 {
46  setupUi( this );
47  connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
48  connect( btnNewFile, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
49  connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
50  connect( expressionTree, &QTreeView::doubleClicked, this, &QgsExpressionBuilderWidget::expressionTree_doubleClicked );
51  connect( txtExpressionString, &QgsCodeEditorSQL::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
52  connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
53  connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
54  connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEdit_textChanged );
55  connect( lblPreview, &QLabel::linkActivated, this, &QgsExpressionBuilderWidget::lblPreview_linkActivated );
56  connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
57 
58  mValueGroupBox->hide();
59  mLoadGroupBox->hide();
60 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
61 
62  mModel = new QStandardItemModel();
63  mProxyModel = new QgsExpressionItemSearchProxy();
64  mProxyModel->setDynamicSortFilter( true );
65  mProxyModel->setSourceModel( mModel );
66  expressionTree->setModel( mProxyModel );
67  expressionTree->setSortingEnabled( true );
68  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
69 
70  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
71  connect( this, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsExpressionBuilderWidget::setExpressionState );
72  connect( expressionTree, &QWidget::customContextMenuRequested, this, &QgsExpressionBuilderWidget::showContextMenu );
73  connect( expressionTree->selectionModel(), &QItemSelectionModel::currentChanged,
74  this, &QgsExpressionBuilderWidget::currentChanged );
75 
76  connect( btnLoadAll, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadAllValues );
77  connect( btnLoadSample, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadSampleValues );
78 
79  Q_FOREACH ( QPushButton *button, mOperatorsGroupBox->findChildren<QPushButton *>() )
80  {
81  connect( button, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
82  }
83 
84  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
85 
86  mValuesModel = new QStringListModel();
87  mProxyValues = new QSortFilterProxyModel();
88  mProxyValues->setSourceModel( mValuesModel );
89  mValuesListView->setModel( mProxyValues );
90  txtSearchEditValues->setPlaceholderText( tr( "Search" ) );
91 
92  QgsSettings settings;
93  splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
94  editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
95  functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
96 
97  txtExpressionString->setFoldingVisible( false );
98 
99  updateFunctionTree();
100 
101  if ( QgsPythonRunner::isValid() )
102  {
103  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
104  updateFunctionFileList( mFunctionsPath );
105  }
106  else
107  {
108  tab_2->hide();
109  }
110 
111  // select the first item in the function list
112  // in order to avoid a blank help widget
113  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
114  expressionTree->setCurrentIndex( firstItem );
115 
116  lblAutoSave->clear();
117  txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
118 }
119 
120 
122 {
123  QgsSettings settings;
124  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
125  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
126  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
127 
128  delete mModel;
129  delete mProxyModel;
130  delete mValuesModel;
131  delete mProxyValues;
132 }
133 
135 {
136  mLayer = layer;
137 
138  //TODO - remove existing layer scope from context
139 
140  if ( mLayer )
141  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
142 }
143 
144 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
145 {
146  txtSearchEditValues->clear();
147 
148  // Get the item
149  QModelIndex idx = mProxyModel->mapToSource( index );
150  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
151  if ( !item )
152  return;
153 
154  if ( item->getItemType() == QgsExpressionItem::Field && mFieldValues.contains( item->text() ) )
155  {
156  const QStringList &values = mFieldValues[item->text()];
157  mValuesModel->setStringList( values );
158  }
159 
160  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
161  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
162 
163  // Show the help for the current item.
164  QString help = loadFunctionHelp( item );
165  txtHelpText->setText( help );
166 }
167 
168 void QgsExpressionBuilderWidget::btnRun_pressed()
169 {
170  if ( !cmbFileNames->currentItem() )
171  return;
172 
173  QString file = cmbFileNames->currentItem()->text();
174  saveFunctionFile( file );
175  runPythonCode( txtPython->text() );
176 }
177 
178 void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
179 {
180  if ( QgsPythonRunner::isValid() )
181  {
182  QString pythontext = code;
183  QgsPythonRunner::run( pythontext );
184  }
185  updateFunctionTree();
186  loadFieldNames();
187  loadRecent( mRecentKey );
188 }
189 
191 {
192  QDir myDir( mFunctionsPath );
193  if ( !myDir.exists() )
194  {
195  myDir.mkpath( mFunctionsPath );
196  }
197 
198  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
199  {
200  fileName.append( ".py" );
201  }
202 
203  fileName = mFunctionsPath + QDir::separator() + fileName;
204  QFile myFile( fileName );
205  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
206  {
207  QTextStream myFileStream( &myFile );
208  myFileStream << txtPython->text() << endl;
209  myFile.close();
210  }
211 }
212 
214 {
215  mFunctionsPath = path;
216  QDir dir( path );
217  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
218  QStringList files = dir.entryList( QDir::Files );
219  cmbFileNames->clear();
220  Q_FOREACH ( const QString &name, files )
221  {
222  QFileInfo info( mFunctionsPath + QDir::separator() + name );
223  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
224  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), info.baseName() );
225  cmbFileNames->addItem( item );
226  }
227  if ( !cmbFileNames->currentItem() )
228  cmbFileNames->setCurrentRow( 0 );
229 }
230 
231 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
232 {
233  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
234  if ( !items.isEmpty() )
235  return;
236 
237  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), fileName );
238  cmbFileNames->insertItem( 0, item );
239  cmbFileNames->setCurrentRow( 0 );
240 
241  QString templatetxt;
242  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressions.template" ), templatetxt );
243  txtPython->setText( templatetxt );
244  saveFunctionFile( fileName );
245 }
246 
247 void QgsExpressionBuilderWidget::btnNewFile_pressed()
248 {
249  bool ok;
250  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
251  tr( "File name:" ), QLineEdit::Normal,
252  QLatin1String( "" ), &ok );
253  if ( ok && !text.isEmpty() )
254  {
255  newFunctionFile( text );
256  }
257 }
258 
259 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
260 {
261  if ( lastitem )
262  {
263  QString filename = lastitem->text();
264  saveFunctionFile( filename );
265  }
266  QString path = mFunctionsPath + QDir::separator() + item->text();
267  loadCodeFromFile( path );
268 }
269 
271 {
272  if ( !path.endsWith( QLatin1String( ".py" ) ) )
273  path.append( ".py" );
274 
275  txtPython->loadScript( path );
276 }
277 
279 {
280  txtPython->setText( code );
281 }
282 
283 void QgsExpressionBuilderWidget::expressionTree_doubleClicked( const QModelIndex &index )
284 {
285  QModelIndex idx = mProxyModel->mapToSource( index );
286  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
287  if ( !item )
288  return;
289 
290  // Don't handle the double-click if we are on a header node.
291  if ( item->getItemType() == QgsExpressionItem::Header )
292  return;
293 
294  // Insert the expression text or replace selected text
295  txtExpressionString->insertText( item->getExpressionText() );
296  txtExpressionString->setFocus();
297 }
298 
300 {
301  // TODO We should really return a error the user of the widget that
302  // the there is no layer set.
303  if ( !mLayer )
304  return;
305 
306  loadFieldNames( mLayer->fields() );
307 }
308 
310 {
311  if ( fields.isEmpty() )
312  return;
313 
314  QStringList fieldNames;
315  //Q_FOREACH ( const QgsField& field, fields )
316  fieldNames.reserve( fields.count() );
317  for ( int i = 0; i < fields.count(); ++i )
318  {
319  QString fieldName = fields.at( i ).name();
320  fieldNames << fieldName;
321  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QLatin1String( "" ), QgsExpressionItem::Field, false, i );
322  }
323 // highlighter->addFields( fieldNames );
324 }
325 
326 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
327 {
328  QgsFields fields;
329  for ( auto it = fieldValues.constBegin(); it != fieldValues.constEnd(); ++it )
330  {
331  fields.append( QgsField( it.key() ) );
332  }
333  loadFieldNames( fields );
334  mFieldValues = fieldValues;
335 }
336 
337 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit )
338 {
339  // TODO We should really return a error the user of the widget that
340  // the there is no layer set.
341  if ( !mLayer )
342  return;
343 
344  // TODO We should thread this so that we don't hold the user up if the layer is massive.
345 
346  int fieldIndex = mLayer->fields().lookupField( fieldName );
347 
348  if ( fieldIndex < 0 )
349  return;
350 
351  QStringList strValues;
352  QList<QVariant> values = mLayer->uniqueValues( fieldIndex, countLimit ).toList();
353  std::sort( values.begin(), values.end() );
354  Q_FOREACH ( const QVariant &value, values )
355  {
356  QString strValue;
357  if ( value.isNull() )
358  strValue = QStringLiteral( "NULL" );
359  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
360  strValue = value.toString();
361  else
362  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
363  strValues.append( strValue );
364  }
365  mValuesModel->setStringList( strValues );
366  mFieldValues[fieldName] = strValues;
367 }
368 
369 void QgsExpressionBuilderWidget::registerItem( const QString &group,
370  const QString &label,
371  const QString &expressionText,
372  const QString &helpText,
373  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
374 {
375  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
376  item->setData( label, Qt::UserRole );
377  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
378 
379  // Look up the group and insert the new function.
380  if ( mExpressionGroups.contains( group ) )
381  {
382  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
383  groupNode->appendRow( item );
384  }
385  else
386  {
387  // If the group doesn't exist yet we make it first.
388  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
389  newgroupNode->setData( group, Qt::UserRole );
390  //Recent group should always be last group
391  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
392  newgroupNode->appendRow( item );
393  newgroupNode->setBackground( QBrush( QColor( 238, 238, 238 ) ) );
394  mModel->appendRow( newgroupNode );
395  mExpressionGroups.insert( group, newgroupNode );
396  }
397 
398  if ( highlightedItem )
399  {
400  //insert a copy as a top level item
401  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
402  topLevelItem->setData( label, Qt::UserRole );
403  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
404  QFont font = topLevelItem->font();
405  font.setBold( true );
406  topLevelItem->setFont( font );
407  mModel->appendRow( topLevelItem );
408  }
409 
410 }
411 
413 {
414  return mExpressionValid;
415 }
416 
417 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
418 {
419  QgsSettings settings;
420  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
421  QStringList expressions = settings.value( location ).toStringList();
422  expressions.removeAll( this->expressionText() );
423 
424  expressions.prepend( this->expressionText() );
425 
426  while ( expressions.count() > 20 )
427  {
428  expressions.pop_back();
429  }
430 
431  settings.setValue( location, expressions );
432  this->loadRecent( collection );
433 }
434 
435 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
436 {
437  mRecentKey = collection;
438  QString name = tr( "Recent (%1)" ).arg( collection );
439  if ( mExpressionGroups.contains( name ) )
440  {
441  QgsExpressionItem *node = mExpressionGroups.value( name );
442  node->removeRows( 0, node->rowCount() );
443  }
444 
445  QgsSettings settings;
446  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
447  QStringList expressions = settings.value( location ).toStringList();
448  int i = 0;
449  Q_FOREACH ( const QString &expression, expressions )
450  {
451  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
452  i++;
453  }
454 }
455 
456 void QgsExpressionBuilderWidget::loadLayers()
457 {
458  if ( !mProject )
459  return;
460 
461  QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
462  QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
463  for ( ; layerIt != layers.constEnd(); ++layerIt )
464  {
465  registerItemForAllGroups( QStringList() << tr( "Map Layers" ), layerIt.value()->name(), QStringLiteral( "'%1'" ).arg( layerIt.key() ), formatLayerHelp( layerIt.value() ) );
466  }
467 }
468 
469 void QgsExpressionBuilderWidget::loadRelations()
470 {
471  if ( !mProject )
472  return;
473 
474  QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
475  QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
476  for ( ; relIt != relations.constEnd(); ++relIt )
477  {
478  registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), QStringLiteral( "'%1'" ).arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
479  }
480 }
481 
482 void QgsExpressionBuilderWidget::updateFunctionTree()
483 {
484  mModel->clear();
485  mExpressionGroups.clear();
486  // TODO Can we move this stuff to QgsExpression, like the functions?
487  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
488  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
489  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "*" ), QStringLiteral( " * " ) );
490  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "/" ), QStringLiteral( " / " ) );
491  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "%" ), QStringLiteral( " % " ) );
492  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "^" ), QStringLiteral( " ^ " ) );
493  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "=" ), QStringLiteral( " = " ) );
494  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "~" ), QStringLiteral( " ~ " ) );
495  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">" ), QStringLiteral( " > " ) );
496  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<" ), QStringLiteral( " < " ) );
497  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
498  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
499  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
500  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
501  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
502  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
503  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
504  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
505  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
506  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
507  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
508 
509  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
510  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
511 
512  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
513 
514  // Load the functions from the QgsExpression class
515  int count = QgsExpression::functionCount();
516  for ( int i = 0; i < count; i++ )
517  {
518  QgsExpressionFunction *func = QgsExpression::Functions()[i];
519  QString name = func->name();
520  if ( name.startsWith( '_' ) ) // do not display private functions
521  continue;
522  if ( func->isDeprecated() ) // don't show deprecated functions
523  continue;
524  if ( func->isContextual() )
525  {
526  //don't show contextual functions by default - it's up the the QgsExpressionContext
527  //object to provide them if supported
528  continue;
529  }
530  if ( func->params() != 0 )
531  name += '(';
532  else if ( !name.startsWith( '$' ) )
533  name += QLatin1String( "()" );
534  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
535  }
536 
537  // load relation names
538  loadRelations();
539 
540  // load layer IDs
541  loadLayers();
542 
543  loadExpressionContext();
544 }
545 
547 {
548  mDa = da;
549 }
550 
552 {
553  return txtExpressionString->text();
554 }
555 
556 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
557 {
558  txtExpressionString->setText( expression );
559 }
560 
562 {
563  mExpressionContext = context;
564  updateFunctionTree();
565  loadFieldNames();
566  loadRecent( mRecentKey );
567 }
568 
569 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
570 {
571  QString text = expressionText();
572 
573  // If the string is empty the expression will still "fail" although
574  // we don't show the user an error as it will be confusing.
575  if ( text.isEmpty() )
576  {
577  lblPreview->clear();
578  lblPreview->setStyleSheet( QLatin1String( "" ) );
579  txtExpressionString->setToolTip( QLatin1String( "" ) );
580  lblPreview->setToolTip( QLatin1String( "" ) );
581  emit expressionParsed( false );
582  setParserError( true );
583  setEvalError( true );
584  return;
585  }
586 
587  QgsExpression exp( text );
588 
589  if ( mLayer )
590  {
591  // Only set calculator if we have layer, else use default.
592  exp.setGeomCalculator( &mDa );
593 
594  if ( !mExpressionContext.feature().isValid() )
595  {
596  // no feature passed yet, try to get from layer
597  QgsFeature f;
598  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
599  mExpressionContext.setFeature( f );
600  }
601  }
602 
603  QVariant value = exp.evaluate( &mExpressionContext );
604  if ( !exp.hasEvalError() )
605  {
606  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
607  }
608 
609  if ( exp.hasParserError() || exp.hasEvalError() )
610  {
611  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
612  if ( exp.hasEvalError() )
613  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
614 
615  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
616  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
617  txtExpressionString->setToolTip( tooltip );
618  lblPreview->setToolTip( tooltip );
619  emit expressionParsed( false );
620  setParserError( exp.hasParserError() );
621  setEvalError( exp.hasEvalError() );
622  return;
623  }
624  else
625  {
626  lblPreview->setStyleSheet( QString() );
627  txtExpressionString->setToolTip( QString() );
628  lblPreview->setToolTip( QString() );
629  emit expressionParsed( true );
630  setParserError( false );
631  setEvalError( false );
632  }
633 }
634 
635 void QgsExpressionBuilderWidget::loadExpressionContext()
636 {
637  QStringList variableNames = mExpressionContext.filteredVariableNames();
638  Q_FOREACH ( const QString &variable, variableNames )
639  {
640  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
641  QgsExpression::formatVariableHelp( mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
643  mExpressionContext.isHighlightedVariable( variable ) );
644  }
645 
646  // Load the functions from the expression context
647  QStringList contextFunctions = mExpressionContext.functionNames();
648  Q_FOREACH ( const QString &functionName, contextFunctions )
649  {
650  QgsExpressionFunction *func = mExpressionContext.function( functionName );
651  QString name = func->name();
652  if ( name.startsWith( '_' ) ) // do not display private functions
653  continue;
654  if ( func->params() != 0 )
655  name += '(';
656  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
657  }
658 }
659 
660 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
661 {
662  Q_FOREACH ( const QString &group, groups )
663  {
664  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
665  }
666 }
667 
668 QString QgsExpressionBuilderWidget::formatRelationHelp( const QgsRelation &relation ) const
669 {
670  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the relation ID for the relation named '%1'." ).arg( relation.name() ) );
671  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( relation.id() ) ) );
672  return text;
673 }
674 
675 QString QgsExpressionBuilderWidget::formatLayerHelp( const QgsMapLayer *layer ) const
676 {
677  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the layer ID for the layer named '%1'." ).arg( layer->name() ) );
678  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( layer->id() ) ) );
679  return text;
680 }
681 
683 {
684  return mParserError;
685 }
686 
687 void QgsExpressionBuilderWidget::setParserError( bool parserError )
688 {
689  if ( parserError == mParserError )
690  return;
691 
692  mParserError = parserError;
693  emit parserErrorChanged();
694 }
695 
697 {
698  return mEvalError;
699 }
700 
701 void QgsExpressionBuilderWidget::setEvalError( bool evalError )
702 {
703  if ( evalError == mEvalError )
704  return;
705 
706  mEvalError = evalError;
707  emit evalErrorChanged();
708 }
709 
711 {
712  return mModel;
713 }
714 
716 {
717  return mProject;
718 }
719 
721 {
722  mProject = project;
723  updateFunctionTree();
724 }
725 
727 {
728  QWidget::showEvent( e );
729  txtExpressionString->setFocus();
730 }
731 
732 void QgsExpressionBuilderWidget::txtSearchEdit_textChanged()
733 {
734  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
735  if ( txtSearchEdit->text().isEmpty() )
736  {
737  expressionTree->collapseAll();
738  }
739  else
740  {
741  expressionTree->expandAll();
742  QModelIndex index = mProxyModel->index( 0, 0 );
743  if ( mProxyModel->hasChildren( index ) )
744  {
745  QModelIndex child = mProxyModel->index( 0, 0, index );
746  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
747  }
748  }
749 }
750 
751 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
752 {
753  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
754  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
755 }
756 
757 void QgsExpressionBuilderWidget::lblPreview_linkActivated( const QString &link )
758 {
759  Q_UNUSED( link );
760  QgsMessageViewer *mv = new QgsMessageViewer( this );
761  mv->setWindowTitle( tr( "More Info on Expression Error" ) );
762  mv->setMessageAsHtml( txtExpressionString->toolTip() );
763  mv->exec();
764 }
765 
766 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
767 {
768  // Insert the item text or replace selected text
769  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
770  txtExpressionString->setFocus();
771 }
772 
773 void QgsExpressionBuilderWidget::operatorButtonClicked()
774 {
775  QPushButton *button = dynamic_cast<QPushButton *>( sender() );
776 
777  // Insert the button text or replace selected text
778  txtExpressionString->insertText( ' ' + button->text() + ' ' );
779  txtExpressionString->setFocus();
780 }
781 
782 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
783 {
784  QModelIndex idx = expressionTree->indexAt( pt );
785  idx = mProxyModel->mapToSource( idx );
786  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
787  if ( !item )
788  return;
789 
790  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
791  {
792  QMenu *menu = new QMenu( this );
793  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
794  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
795  menu->popup( expressionTree->mapToGlobal( pt ) );
796  }
797 }
798 
800 {
801  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
802  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
803  // TODO We should really return a error the user of the widget that
804  // the there is no layer set.
805  if ( !mLayer || !item )
806  return;
807 
808  mValueGroupBox->show();
809  fillFieldValues( item->text(), 10 );
810 }
811 
813 {
814  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
815  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
816  // TODO We should really return a error the user of the widget that
817  // the there is no layer set.
818  if ( !mLayer || !item )
819  return;
820 
821  mValueGroupBox->show();
822  fillFieldValues( item->text(), -1 );
823 }
824 
825 void QgsExpressionBuilderWidget::txtPython_textChanged()
826 {
827  lblAutoSave->setText( tr( "Saving…" ) );
828  if ( mAutoSave )
829  {
830  autosave();
831  }
832 }
833 
835 {
836  // Don't auto save if not on function editor that would be silly.
837  if ( tabWidget->currentIndex() != 1 )
838  return;
839 
840  QListWidgetItem *item = cmbFileNames->currentItem();
841  if ( !item )
842  return;
843 
844  QString file = item->text();
845  saveFunctionFile( file );
846  lblAutoSave->setText( QStringLiteral( "Saved" ) );
847  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
848  lblAutoSave->setGraphicsEffect( effect );
849  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
850  anim->setDuration( 2000 );
851  anim->setStartValue( 1.0 );
852  anim->setEndValue( 0.0 );
853  anim->setEasingCurve( QEasingCurve::OutQuad );
854  anim->start( QAbstractAnimation::DeleteWhenStopped );
855 }
856 
857 void QgsExpressionBuilderWidget::setExpressionState( bool state )
858 {
859  mExpressionValid = state;
860 }
861 
862 QString QgsExpressionBuilderWidget::helpStylesheet() const
863 {
864  //start with default QGIS report style
865  QString style = QgsApplication::reportStyleSheet();
866 
867  //add some tweaks
868  style += " .functionname {color: #0a6099; font-weight: bold;} "
869  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
870  " td.argument { padding-right: 10px; }";
871 
872  return style;
873 }
874 
875 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
876 {
877  if ( !expressionItem )
878  return QLatin1String( "" );
879 
880  QString helpContents = expressionItem->getHelpText();
881 
882  // Return the function help that is set for the function if there is one.
883  if ( helpContents.isEmpty() )
884  {
885  QString name = expressionItem->data( Qt::UserRole ).toString();
886 
887  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
888  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
889  else
890  helpContents = QgsExpression::helpText( name );
891  }
892 
893  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
894 }
895 
896 
897 
898 
899 
901 {
902  setFilterCaseSensitivity( Qt::CaseInsensitive );
903 }
904 
905 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
906 {
907  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
908  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
909 
910  int count = sourceModel()->rowCount( index );
911  bool matchchild = false;
912  for ( int i = 0; i < count; ++i )
913  {
914  if ( filterAcceptsRow( i, index ) )
915  {
916  matchchild = true;
917  break;
918  }
919  }
920 
921  if ( itemType == QgsExpressionItem::Header && matchchild )
922  return true;
923 
924  if ( itemType == QgsExpressionItem::Header )
925  return false;
926 
927  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
928 }
929 
930 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
931 {
932  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
933  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
934  if ( leftSort != rightSort )
935  return leftSort < rightSort;
936 
937  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
938  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
939 
940  //ignore $ prefixes when sorting
941  if ( leftString.startsWith( '$' ) )
942  leftString = leftString.mid( 1 );
943  if ( rightString.startsWith( '$' ) )
944  rightString = rightString.mid( 1 );
945 
946  return QString::localeAwareCompare( leftString, rightString ) < 0;
947 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:176
QgsProject * project()
Returns the project currently associated with the widget.
QString name
Definition: qgsrelation.h:45
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
Base class for all map layer types.
Definition: qgsmaplayer.h:56
QStandardItemModel * model()
Returns a pointer to the dialog&#39;s function item model.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
QString name
Definition: qgsfield.h:57
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
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.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context...
void loadSampleValues()
Load sample values into the sample value area.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
QString id
Definition: qgsrelation.h:42
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
Container of fields for a vector layer.
Definition: qgsfields.h:42
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const override
Calculates a list of unique values contained within an attribute in the layer.
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
static QString reportStyleSheet()
get a standard css style sheet for reports.
int count() const
Return number of items.
Definition: qgsfields.cpp:115
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.
QString description(const QString &name) const
Returns a translated description string for the variable with specified name.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:145
void updateFunctionFileList(const QString &path)
Update the list of function files found at the given path.
void setMessageAsHtml(const QString &msg)
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Search proxy used to filter the QgsExpressionBuilderWidget tree.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsFields fields() const override
Returns the list of fields of this layer.
bool parserError() const
Will be set to true if the current expression text reports a parser error with the context...
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...
This class wraps a request for features to a vector layer (or directly its vector data provider)...
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:59
Reads and writes project states.
Definition: qgsproject.h:82
void loadFieldNames()
Loads all the field names from the layer.
static const int ITEM_TYPE_ROLE
Item type role.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
void showEvent(QShowEvent *e) override
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.
bool evalError() const
Will be set to true if the current expression text reported an eval error with the context...
QgsExpressionItem::ItemType getItemType() const
Get the type of expression item, e.g., header, field, ExpressionNode.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
static const int CUSTOM_SORT_ROLE
Custom sort order role.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations...
An expression item that can be used in the QgsExpressionBuilderWidget tree.
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
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
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:110
QString expressionText()
Gets the expression string that has been set in the expression area.
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
void parserErrorChanged()
Will be set to true if the current expression text reported a parser error with the context...
QString getHelpText() const
Get the help text that is associated with this expression item.
void registerItem(const QString &group, const QString &label, const QString &expressionText, const QString &helpText=QString(), QgsExpressionItem::ItemType type=QgsExpressionItem::ExpressionNode, bool highlightedItem=false, int sortOrder=1)
Registers a node item for the expression builder.
QString name
Definition: qgsmaplayer.h:60
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)
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.
QgsExpressionFunction * function(const QString &name) const
Fetches a matching function from the context.