QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 "qgsexpressionnodeimpl.h"
21 #include "qgsmessageviewer.h"
22 #include "qgsapplication.h"
23 #include "qgspythonrunner.h"
24 #include "qgsgeometry.h"
25 #include "qgsfeature.h"
26 #include "qgsfeatureiterator.h"
27 #include "qgsvectorlayer.h"
28 #include "qgssettings.h"
29 #include "qgsproject.h"
30 #include "qgsrelationmanager.h"
31 #include "qgsrelation.h"
34 #include "qgsfieldformatter.h"
35 
36 #include <QMenu>
37 #include <QFile>
38 #include <QTextStream>
39 #include <QDir>
40 #include <QInputDialog>
41 #include <QComboBox>
42 #include <QGraphicsOpacityEffect>
43 #include <QPropertyAnimation>
44 
45 
47  : QWidget( parent )
48  , mProject( QgsProject::instance() )
49 {
50  setupUi( this );
51  connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
52  connect( btnNewFile, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
53  connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
54  connect( expressionTree, &QTreeView::doubleClicked, this, &QgsExpressionBuilderWidget::expressionTree_doubleClicked );
55  connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
56  connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
57  connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
58  connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEdit_textChanged );
59  connect( lblPreview, &QLabel::linkActivated, this, &QgsExpressionBuilderWidget::lblPreview_linkActivated );
60  connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
61 
62  mValueGroupBox->hide();
63 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
64 
65  mModel = qgis::make_unique<QStandardItemModel>();
66  mProxyModel = qgis::make_unique<QgsExpressionItemSearchProxy>();
67  mProxyModel->setDynamicSortFilter( true );
68  mProxyModel->setSourceModel( mModel.get() );
69  expressionTree->setModel( mProxyModel.get() );
70  expressionTree->setSortingEnabled( true );
71  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
72 
73  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
74  connect( this, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsExpressionBuilderWidget::setExpressionState );
75  connect( expressionTree, &QWidget::customContextMenuRequested, this, &QgsExpressionBuilderWidget::showContextMenu );
76  connect( expressionTree->selectionModel(), &QItemSelectionModel::currentChanged,
77  this, &QgsExpressionBuilderWidget::currentChanged );
78 
79  connect( btnLoadAll, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadAllValues );
80  connect( btnLoadSample, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadSampleValues );
81 
82  const auto pushButtons { mOperatorsGroupBox->findChildren<QPushButton *>() };
83  for ( QPushButton *button : pushButtons )
84  {
85  connect( button, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
86  }
87 
88  txtSearchEdit->setShowSearchIcon( true );
89  txtSearchEdit->setPlaceholderText( tr( "Search…" ) );
90 
91  mValuesModel = qgis::make_unique<QStandardItemModel>();
92  mProxyValues = qgis::make_unique<QSortFilterProxyModel>();
93  mProxyValues->setSourceModel( mValuesModel.get() );
94  mValuesListView->setModel( mProxyValues.get() );
95  txtSearchEditValues->setShowSearchIcon( true );
96  txtSearchEditValues->setPlaceholderText( tr( "Search…" ) );
97 
98  editorSplit->setSizes( QList<int>( {175, 300} ) );
99 
100  functionsplit->setCollapsible( 0, false );
101  connect( mShowHelpButton, &QPushButton::clicked, this, [ = ]()
102  {
103  functionsplit->setSizes( QList<int>( {mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(),
104  mHelpAndValuesWidget->minimumWidth()
105  } ) );
106  mShowHelpButton->setEnabled( false );
107  } );
108  connect( functionsplit, &QSplitter::splitterMoved, this, [ = ]( int, int )
109  {
110  mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
111  } );
112 
113 
114  QgsSettings settings;
115  splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
116  editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
117  functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
118 
119  txtExpressionString->setFoldingVisible( false );
120 
121  updateFunctionTree();
122 
123  if ( QgsPythonRunner::isValid() )
124  {
125  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
126  updateFunctionFileList( mFunctionsPath );
127  }
128  else
129  {
130  tab_2->hide();
131  }
132 
133  // select the first item in the function list
134  // in order to avoid a blank help widget
135  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
136  expressionTree->setCurrentIndex( firstItem );
137 
138  txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
139  lblAutoSave->clear();
140 
141 
142  // Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
143  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
144  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
145  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionInvalidParams );
146  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionNamedArgsError );
147 #if defined(QSCINTILLA_VERSION) && QSCINTILLA_VERSION >= 0x20a00
148  txtExpressionString->indicatorDefine( QgsCodeEditor::TriangleIndicator, QgsExpression::ParserError::Unknown );
149 #else
150  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::Unknown );
151 #endif
152 
153  // Set all the error markers as red. -1 is all.
154  txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
155  txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
156  txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
157 
158  // Hidden function markers.
159  txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
160  txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
161  txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
162  txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
163 
164  connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
165  txtExpressionString->setAutoCompletionCaseSensitivity( true );
166  txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
167  txtExpressionString->setCallTipsVisible( 0 );
168 
169  setExpectedOutputFormat( QString() );
170  mFunctionBuilderHelp->setMarginVisible( false );
171  mFunctionBuilderHelp->setEdgeMode( QsciScintilla::EdgeNone );
172  mFunctionBuilderHelp->setEdgeColumn( 0 );
173  mFunctionBuilderHelp->setReadOnly( true );
174  mFunctionBuilderHelp->setText( tr( "\"\"\"Define a new function using the @qgsfunction decorator.\n\
175 \n\
176  The function accepts the following parameters\n\
177 \n\
178  : param [any]: Define any parameters you want to pass to your function before\n\
179  the following arguments.\n\
180  : param feature: The current feature\n\
181  : param parent: The QgsExpression object\n\
182  : param context: If there is an argument called ``context`` found at the last\n\
183  position, this variable will contain a ``QgsExpressionContext``\n\
184  object, that gives access to various additional information like\n\
185  expression variables. E.g. ``context.variable( 'layer_id' )``\n\
186  : returns: The result of the expression.\n\
187 \n\
188 \n\
189 \n\
190  The @qgsfunction decorator accepts the following arguments:\n\
191 \n\
192 \n\
193  : param args: Defines the number of arguments. With ``args = 'auto'`` the number of\n\
194  arguments will automatically be extracted from the signature.\n\
195  With ``args = -1``, any number of arguments are accepted.\n\
196  : param group: The name of the group under which this expression function will\n\
197  be listed.\n\
198  : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
199  If False, the result will always be NULL as soon as any parameter is NULL.\n\
200  Defaults to False.\n\
201  : param usesgeometry : Set this to True if your function requires access to\n\
202  feature.geometry(). Defaults to False.\n\
203  : param referenced_columns: An array of attribute names that are required to run\n\
204  this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
205  \"\"\"" ) );
206 }
207 
208 
210 {
211  QgsSettings settings;
212  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
213  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
214  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
215 }
216 
218 {
219  mLayer = layer;
220 
221  //TODO - remove existing layer scope from context
222 
223  if ( mLayer )
224  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
225 }
226 
227 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
228 {
229  txtSearchEditValues->clear();
230 
231  // Get the item
232  QModelIndex idx = mProxyModel->mapToSource( index );
233  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
234  if ( !item )
235  return;
236 
237  bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
238  if ( isField )
239  {
240  loadFieldValues( mFieldValues.value( item->text() ) );
241  }
242  mValueGroupBox->setVisible( isField );
243  mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
244 
245  // Show the help for the current item.
246  QString help = loadFunctionHelp( item );
247  txtHelpText->setText( help );
248 }
249 
250 void QgsExpressionBuilderWidget::btnRun_pressed()
251 {
252  if ( !cmbFileNames->currentItem() )
253  return;
254 
255  QString file = cmbFileNames->currentItem()->text();
256  saveFunctionFile( file );
257  runPythonCode( txtPython->text() );
258 }
259 
260 void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
261 {
262  if ( QgsPythonRunner::isValid() )
263  {
264  QString pythontext = code;
265  QgsPythonRunner::run( pythontext );
266  }
267  updateFunctionTree();
268  loadFieldNames();
269  loadRecent( mRecentKey );
270 }
271 
273 {
274  QDir myDir( mFunctionsPath );
275  if ( !myDir.exists() )
276  {
277  myDir.mkpath( mFunctionsPath );
278  }
279 
280  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
281  {
282  fileName.append( ".py" );
283  }
284 
285  fileName = mFunctionsPath + QDir::separator() + fileName;
286  QFile myFile( fileName );
287  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
288  {
289  QTextStream myFileStream( &myFile );
290  myFileStream << txtPython->text() << endl;
291  myFile.close();
292  }
293 }
294 
296 {
297  mFunctionsPath = path;
298  QDir dir( path );
299  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
300  QStringList files = dir.entryList( QDir::Files );
301  cmbFileNames->clear();
302  const auto constFiles = files;
303  for ( const QString &name : constFiles )
304  {
305  QFileInfo info( mFunctionsPath + QDir::separator() + name );
306  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
307  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), info.baseName() );
308  cmbFileNames->addItem( item );
309  }
310  if ( !cmbFileNames->currentItem() )
311  {
312  cmbFileNames->setCurrentRow( 0 );
313  }
314 
315  if ( cmbFileNames->count() == 0 )
316  {
317  // Create default sample entry.
318  newFunctionFile( "default" );
319  txtPython->setText( QString( "'''\n#Sample custom function file\n "
320  "(uncomment to use and customize or Add button to create a new file) \n%1 \n '''" ).arg( txtPython->text() ) );
321  saveFunctionFile( "default" );
322  }
323 }
324 
325 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
326 {
327  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
328  if ( !items.isEmpty() )
329  return;
330 
331  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
332  cmbFileNames->insertItem( 0, item );
333  cmbFileNames->setCurrentRow( 0 );
334 
335  QString templatetxt;
336  QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templatetxt );
337  txtPython->setText( templatetxt );
338  saveFunctionFile( fileName );
339 }
340 
341 void QgsExpressionBuilderWidget::btnNewFile_pressed()
342 {
343  bool ok;
344  QString text = QInputDialog::getText( this, tr( "New File" ),
345  tr( "New file name:" ), QLineEdit::Normal,
346  QString(), &ok );
347  if ( ok && !text.isEmpty() )
348  {
349  newFunctionFile( text );
350  }
351 }
352 
353 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
354 {
355  if ( lastitem )
356  {
357  QString filename = lastitem->text();
358  saveFunctionFile( filename );
359  }
360  QString path = mFunctionsPath + QDir::separator() + item->text();
361  loadCodeFromFile( path );
362 }
363 
365 {
366  if ( !path.endsWith( QLatin1String( ".py" ) ) )
367  path.append( ".py" );
368 
369  txtPython->loadScript( path );
370 }
371 
373 {
374  txtPython->setText( code );
375 }
376 
377 void QgsExpressionBuilderWidget::expressionTree_doubleClicked( const QModelIndex &index )
378 {
379  QModelIndex idx = mProxyModel->mapToSource( index );
380  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
381  if ( !item )
382  return;
383 
384  // Don't handle the double-click if we are on a header node.
385  if ( item->getItemType() == QgsExpressionItem::Header )
386  return;
387 
388  // Insert the expression text or replace selected text
389  txtExpressionString->insertText( item->getExpressionText() );
390  txtExpressionString->setFocus();
391 }
392 
394 {
395  // TODO We should really return a error the user of the widget that
396  // the there is no layer set.
397  if ( !mLayer )
398  return;
399 
400  loadFieldNames( mLayer->fields() );
401 }
402 
403 
405 {
406  if ( fields.isEmpty() )
407  return;
408 
409  txtExpressionString->setFields( fields );
410 
411  QStringList fieldNames;
412  fieldNames.reserve( fields.count() );
413  for ( int i = 0; i < fields.count(); ++i )
414  {
415  QgsField field = fields.at( i );
416  QString fieldName = field.name();
417  fieldNames << fieldName;
418  QIcon icon = fields.iconForField( i );
419  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QString(), QgsExpressionItem::Field, false, i, icon );
420  }
421  // highlighter->addFields( fieldNames );
422 }
423 
424 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
425 {
426  mFieldValues.clear();
427  QgsFields fields;
428  for ( auto it = fieldValues.constBegin(); it != fieldValues.constEnd(); ++it )
429  {
430  fields.append( QgsField( it.key() ) );
431  const QStringList values = it.value();
432  QVariantMap map;
433  for ( const QString &value : values )
434  {
435  map.insert( value, value );
436  }
437  mFieldValues.insert( it.key(), map );
438  }
439  loadFieldNames( fields );
440 }
441 
442 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QVariantMap> &fieldValues )
443 {
444  QgsFields fields;
445  for ( auto it = fieldValues.constBegin(); it != fieldValues.constEnd(); ++it )
446  {
447  fields.append( QgsField( it.key() ) );
448  }
449  loadFieldNames( fields );
450  mFieldValues = fieldValues;
451 }
452 
453 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit )
454 {
455  // TODO We should really return a error the user of the widget that
456  // the there is no layer set.
457  if ( !mLayer )
458  return;
459 
460  // TODO We should thread this so that we don't hold the user up if the layer is massive.
461 
462  const QgsFields fields = mLayer->fields();
463  int fieldIndex = fields.lookupField( fieldName );
464 
465  if ( fieldIndex < 0 )
466  return;
467 
468  const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
470 
471  QList<QVariant> values = mLayer->uniqueValues( fieldIndex, countLimit ).toList();
472  std::sort( values.begin(), values.end() );
473 
474  for ( const QVariant &value : qgis::as_const( values ) )
475  {
476  QString strValue;
477  if ( value.isNull() )
478  strValue = QStringLiteral( "NULL" );
479  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
480  strValue = value.toString();
481  else
482  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
483 
484  QString representedValue = formatter->representValue( mLayer, fieldIndex, setup.config(), QVariant(), value );
485  if ( representedValue != value.toString() )
486  representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
487 
488  QStandardItem *item = new QStandardItem( representedValue );
489  item->setData( strValue );
490  mValuesModel->appendRow( item );
491  }
492 }
493 
494 QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
495 {
496  if ( !function )
497  return QString();
498 
499  QString helpContents = QgsExpression::helpText( function->name() );
500 
501  return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
502 
503 }
504 
505 void QgsExpressionBuilderWidget::registerItem( const QString &group,
506  const QString &label,
507  const QString &expressionText,
508  const QString &helpText,
509  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder, QIcon icon )
510 {
511  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
512  item->setData( label, Qt::UserRole );
513  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
514  item->setIcon( icon );
515 
516  // Look up the group and insert the new function.
517  if ( mExpressionGroups.contains( group ) )
518  {
519  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
520  groupNode->appendRow( item );
521  }
522  else
523  {
524  // If the group doesn't exist yet we make it first.
525  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QString(), QgsExpressionItem::Header );
526  newgroupNode->setData( group, Qt::UserRole );
527  //Recent group should always be last group
528  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
529  newgroupNode->appendRow( item );
530  newgroupNode->setBackground( QBrush( QColor( 238, 238, 238 ) ) );
531  mModel->appendRow( newgroupNode );
532  mExpressionGroups.insert( group, newgroupNode );
533  }
534 
535  if ( highlightedItem )
536  {
537  //insert a copy as a top level item
538  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
539  topLevelItem->setData( label, Qt::UserRole );
540  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
541  QFont font = topLevelItem->font();
542  font.setBold( true );
543  topLevelItem->setFont( font );
544  mModel->appendRow( topLevelItem );
545  }
546 
547 }
548 
550 {
551  return mExpressionValid;
552 }
553 
554 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
555 {
556  QgsSettings settings;
557  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
558  QStringList expressions = settings.value( location ).toStringList();
559  expressions.removeAll( this->expressionText() );
560 
561  expressions.prepend( this->expressionText() );
562 
563  while ( expressions.count() > 20 )
564  {
565  expressions.pop_back();
566  }
567 
568  settings.setValue( location, expressions );
569  this->loadRecent( collection );
570 }
571 
572 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
573 {
574  mRecentKey = collection;
575  QString name = tr( "Recent (%1)" ).arg( collection );
576  if ( mExpressionGroups.contains( name ) )
577  {
578  QgsExpressionItem *node = mExpressionGroups.value( name );
579  node->removeRows( 0, node->rowCount() );
580  }
581 
582  QgsSettings settings;
583  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
584  QStringList expressions = settings.value( location ).toStringList();
585  int i = 0;
586  const auto constExpressions = expressions;
587  for ( const QString &expression : constExpressions )
588  {
589  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
590  i++;
591  }
592 }
593 
594 void QgsExpressionBuilderWidget::loadLayers()
595 {
596  if ( !mProject )
597  return;
598 
599  QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
600  QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
601  for ( ; layerIt != layers.constEnd(); ++layerIt )
602  {
603  registerItemForAllGroups( QStringList() << tr( "Map Layers" ), layerIt.value()->name(), QStringLiteral( "'%1'" ).arg( layerIt.key() ), formatLayerHelp( layerIt.value() ) );
604  }
605 }
606 
607 void QgsExpressionBuilderWidget::loadRelations()
608 {
609  if ( !mProject )
610  return;
611 
612  QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
613  QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
614  for ( ; relIt != relations.constEnd(); ++relIt )
615  {
616  registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), QStringLiteral( "'%1'" ).arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
617  }
618 }
619 
620 void QgsExpressionBuilderWidget::updateFunctionTree()
621 {
622  mModel->clear();
623  mExpressionGroups.clear();
624  // TODO Can we move this stuff to QgsExpression, like the functions?
625  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
626  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
627  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "*" ), QStringLiteral( " * " ) );
628  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "/" ), QStringLiteral( " / " ) );
629  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "%" ), QStringLiteral( " % " ) );
630  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "^" ), QStringLiteral( " ^ " ) );
631  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "=" ), QStringLiteral( " = " ) );
632  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "~" ), QStringLiteral( " ~ " ) );
633  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">" ), QStringLiteral( " > " ) );
634  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<" ), QStringLiteral( " < " ) );
635  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
636  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
637  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
638  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "[]" ), QStringLiteral( "[ ]" ) );
639  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
640  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
641  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
642  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
643  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
644  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
645  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
646  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
647 
648  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
649  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
650 
651  // use -1 as sort order here -- NULL should always show before the field list
652  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ), QString(), QgsExpressionItem::ExpressionNode, false, -1 );
653 
654  // Load the functions from the QgsExpression class
655  int count = QgsExpression::functionCount();
656  for ( int i = 0; i < count; i++ )
657  {
659  QString name = func->name();
660  if ( name.startsWith( '_' ) ) // do not display private functions
661  continue;
662  if ( func->isDeprecated() ) // don't show deprecated functions
663  continue;
664  if ( func->isContextual() )
665  {
666  //don't show contextual functions by default - it's up the the QgsExpressionContext
667  //object to provide them if supported
668  continue;
669  }
670  if ( func->params() != 0 )
671  name += '(';
672  else if ( !name.startsWith( '$' ) )
673  name += QLatin1String( "()" );
674  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ) );
675  }
676 
677  // load relation names
678  loadRelations();
679 
680  // load layer IDs
681  loadLayers();
682 
683  loadExpressionContext();
684 }
685 
687 {
688  mDa = da;
689 }
690 
692 {
693  return txtExpressionString->text();
694 }
695 
696 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
697 {
698  txtExpressionString->setText( expression );
699 }
700 
702 {
703  return lblExpected->text();
704 }
705 
707 {
708  lblExpected->setText( expected );
709  mExpectedOutputFrame->setVisible( !expected.isNull() );
710 }
711 
713 {
714  mExpressionContext = context;
715  updateFunctionTree();
716  loadFieldNames();
717  loadRecent( mRecentKey );
718 }
719 
720 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
721 {
722  QString text = expressionText();
723  clearErrors();
724 
725  // If the string is empty the expression will still "fail" although
726  // we don't show the user an error as it will be confusing.
727  if ( text.isEmpty() )
728  {
729  lblPreview->clear();
730  lblPreview->setStyleSheet( QString() );
731  txtExpressionString->setToolTip( QString() );
732  lblPreview->setToolTip( QString() );
733  emit expressionParsed( false );
734  setParserError( true );
735  setEvalError( true );
736  return;
737  }
738 
739 
740  QgsExpression exp( text );
741 
742  if ( mLayer )
743  {
744  // Only set calculator if we have layer, else use default.
745  exp.setGeomCalculator( &mDa );
746 
747  if ( !mExpressionContext.feature().isValid() )
748  {
749  // no feature passed yet, try to get from layer
750  QgsFeature f;
751  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
752  mExpressionContext.setFeature( f );
753  }
754  }
755 
756  QVariant value = exp.evaluate( &mExpressionContext );
757  if ( !exp.hasEvalError() )
758  {
759  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
760  }
761 
762  if ( exp.hasParserError() || exp.hasEvalError() )
763  {
764  QString errorString = exp.parserErrorString().replace( "\n", "<br>" );
765  QString tooltip;
766  if ( exp.hasParserError() )
767  tooltip = QStringLiteral( "<b>%1:</b>"
768  "%2" ).arg( tr( "Parser Errors" ), errorString );
769  // Only show the eval error if there is no parser error.
770  if ( !exp.hasParserError() && exp.hasEvalError() )
771  tooltip += QStringLiteral( "<b>%1:</b> %2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
772 
773  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
774  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
775  txtExpressionString->setToolTip( tooltip );
776  lblPreview->setToolTip( tooltip );
777  emit expressionParsed( false );
778  setParserError( exp.hasParserError() );
779  setEvalError( exp.hasEvalError() );
780  createErrorMarkers( exp.parserErrors() );
781  return;
782  }
783  else
784  {
785  lblPreview->setStyleSheet( QString() );
786  txtExpressionString->setToolTip( QString() );
787  lblPreview->setToolTip( QString() );
788  emit expressionParsed( true );
789  setParserError( false );
790  setEvalError( false );
791  createMarkers( exp.rootNode() );
792  }
793 
794 }
795 
796 void QgsExpressionBuilderWidget::loadExpressionContext()
797 {
798  txtExpressionString->setExpressionContext( mExpressionContext );
799  QStringList variableNames = mExpressionContext.filteredVariableNames();
800  const auto constVariableNames = variableNames;
801  for ( const QString &variable : constVariableNames )
802  {
803  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
804  QgsExpression::formatVariableHelp( mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
806  mExpressionContext.isHighlightedVariable( variable ) );
807  }
808 
809  // Load the functions from the expression context
810  QStringList contextFunctions = mExpressionContext.functionNames();
811  const auto constContextFunctions = contextFunctions;
812  for ( const QString &functionName : constContextFunctions )
813  {
814  QgsExpressionFunction *func = mExpressionContext.function( functionName );
815  QString name = func->name();
816  if ( name.startsWith( '_' ) ) // do not display private functions
817  continue;
818  if ( func->params() != 0 )
819  name += '(';
820  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ) );
821  }
822 }
823 
824 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
825 {
826  const auto constGroups = groups;
827  for ( const QString &group : constGroups )
828  {
829  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
830  }
831 }
832 
833 QString QgsExpressionBuilderWidget::formatRelationHelp( const QgsRelation &relation ) const
834 {
835  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the relation ID for the relation named '%1'." ).arg( relation.name() ) );
836  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( relation.id() ) ) );
837  return text;
838 }
839 
840 QString QgsExpressionBuilderWidget::formatLayerHelp( const QgsMapLayer *layer ) const
841 {
842  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the layer ID for the layer named '%1'." ).arg( layer->name() ) );
843  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( layer->id() ) ) );
844  return text;
845 }
846 
848 {
849  return mParserError;
850 }
851 
852 void QgsExpressionBuilderWidget::setParserError( bool parserError )
853 {
854  if ( parserError == mParserError )
855  return;
856 
857  mParserError = parserError;
858  emit parserErrorChanged();
859 }
860 
861 void QgsExpressionBuilderWidget::loadFieldValues( const QVariantMap &values )
862 {
863  mValuesModel->clear();
864  for ( QVariantMap::ConstIterator it = values.constBegin(); it != values.constEnd(); ++ it )
865  {
866  QStandardItem *item = new QStandardItem( it.key() );
867  item->setData( it.value() );
868  mValuesModel->appendRow( item );
869  }
870 }
871 
873 {
874  return mEvalError;
875 }
876 
877 void QgsExpressionBuilderWidget::setEvalError( bool evalError )
878 {
879  if ( evalError == mEvalError )
880  return;
881 
882  mEvalError = evalError;
883  emit evalErrorChanged();
884 }
885 
887 {
888  return mModel.get();
889 }
890 
892 {
893  return mProject;
894 }
895 
897 {
898  mProject = project;
899  updateFunctionTree();
900 }
901 
903 {
904  QWidget::showEvent( e );
905  txtExpressionString->setFocus();
906 }
907 
908 void QgsExpressionBuilderWidget::createErrorMarkers( QList<QgsExpression::ParserError> errors )
909 {
910  clearErrors();
911  for ( const QgsExpression::ParserError &error : errors )
912  {
913  int errorFirstLine = error.firstLine - 1 ;
914  int errorFirstColumn = error.firstColumn - 1;
915  int errorLastColumn = error.lastColumn - 1;
916  int errorLastLine = error.lastLine - 1;
917 
918  // If we have a unknown error we just mark the point that hit the error for now
919  // until we can handle others more.
920  if ( error.errorType == QgsExpression::ParserError::Unknown )
921  {
922  errorFirstLine = errorLastLine;
923  errorFirstColumn = errorLastColumn - 1;
924  }
925  txtExpressionString->fillIndicatorRange( errorFirstLine,
926  errorFirstColumn,
927  errorLastLine,
928  errorLastColumn, error.errorType );
929  }
930 }
931 
932 void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
933 {
934  switch ( inNode->nodeType() )
935  {
936  case QgsExpressionNode::NodeType::ntFunction:
937  {
938  const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
939  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
940  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
941  int start = inNode->parserFirstColumn - 1;
942  int end = inNode->parserLastColumn - 1;
943  int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
944  txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
945  if ( node->args() )
946  {
947  const QList< QgsExpressionNode * > nodeList = node->args()->list();
948  for ( QgsExpressionNode *n : nodeList )
949  {
950  createMarkers( n );
951  }
952  }
953  break;
954  }
955  case QgsExpressionNode::NodeType::ntLiteral:
956  {
957  break;
958  }
959  case QgsExpressionNode::NodeType::ntUnaryOperator:
960  {
961  const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
962  createMarkers( node->operand() );
963  break;
964  }
965  case QgsExpressionNode::NodeType::ntBinaryOperator:
966  {
967  const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
968  createMarkers( node->opLeft() );
969  createMarkers( node->opRight() );
970  break;
971  }
972  case QgsExpressionNode::NodeType::ntColumnRef:
973  {
974  break;
975  }
976  case QgsExpressionNode::NodeType::ntInOperator:
977  {
978  const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
979  if ( node->list() )
980  {
981  const QList< QgsExpressionNode * > nodeList = node->list()->list();
982  for ( QgsExpressionNode *n : nodeList )
983  {
984  createMarkers( n );
985  }
986  }
987  break;
988  }
989  case QgsExpressionNode::NodeType::ntCondition:
990  {
991  const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
992  for ( QgsExpressionNodeCondition::WhenThen *cond : node->conditions() )
993  {
994  createMarkers( cond->whenExp() );
995  createMarkers( cond->thenExp() );
996  }
997  if ( node->elseExp() )
998  {
999  createMarkers( node->elseExp() );
1000  }
1001  break;
1002  }
1003  case QgsExpressionNode::NodeType::ntIndexOperator:
1004  {
1005  break;
1006  }
1007  }
1008 }
1009 
1010 void QgsExpressionBuilderWidget::clearFunctionMarkers()
1011 {
1012  int lastLine = txtExpressionString->lines() - 1;
1013  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
1014 }
1015 
1016 void QgsExpressionBuilderWidget::clearErrors()
1017 {
1018  int lastLine = txtExpressionString->lines() - 1;
1019  // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
1020  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
1021  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
1022  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
1023  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
1024  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
1025 }
1026 
1027 void QgsExpressionBuilderWidget::txtSearchEdit_textChanged()
1028 {
1029  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
1030  if ( txtSearchEdit->text().isEmpty() )
1031  {
1032  expressionTree->collapseAll();
1033  }
1034  else
1035  {
1036  expressionTree->expandAll();
1037  QModelIndex index = mProxyModel->index( 0, 0 );
1038  if ( mProxyModel->hasChildren( index ) )
1039  {
1040  QModelIndex child = mProxyModel->index( 0, 0, index );
1041  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
1042  }
1043  }
1044 }
1045 
1046 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
1047 {
1048  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
1049  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
1050 }
1051 
1052 void QgsExpressionBuilderWidget::lblPreview_linkActivated( const QString &link )
1053 {
1054  Q_UNUSED( link )
1055  QgsMessageViewer *mv = new QgsMessageViewer( this );
1056  mv->setWindowTitle( tr( "More Info on Expression Error" ) );
1057  mv->setMessageAsHtml( txtExpressionString->toolTip() );
1058  mv->exec();
1059 }
1060 
1061 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
1062 {
1063  // Insert the item text or replace selected text
1064  txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
1065  txtExpressionString->setFocus();
1066 }
1067 
1068 void QgsExpressionBuilderWidget::operatorButtonClicked()
1069 {
1070  QPushButton *button = qobject_cast<QPushButton *>( sender() );
1071 
1072  // Insert the button text or replace selected text
1073  txtExpressionString->insertText( ' ' + button->text() + ' ' );
1074  txtExpressionString->setFocus();
1075 }
1076 
1077 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
1078 {
1079  QModelIndex idx = expressionTree->indexAt( pt );
1080  idx = mProxyModel->mapToSource( idx );
1081  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
1082  if ( !item )
1083  return;
1084 
1085  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
1086  {
1087  QMenu *menu = new QMenu( this );
1088  menu->addAction( tr( "Load First 10 Unique Values" ), this, SLOT( loadSampleValues() ) );
1089  menu->addAction( tr( "Load All Unique Values" ), this, SLOT( loadAllValues() ) );
1090  menu->popup( expressionTree->mapToGlobal( pt ) );
1091  }
1092 }
1093 
1095 {
1096  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
1097  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
1098  // TODO We should really return a error the user of the widget that
1099  // the there is no layer set.
1100  if ( !mLayer || !item )
1101  return;
1102 
1103  mValueGroupBox->show();
1104  fillFieldValues( item->text(), 10 );
1105 }
1106 
1108 {
1109  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
1110  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
1111  // TODO We should really return a error the user of the widget that
1112  // the there is no layer set.
1113  if ( !mLayer || !item )
1114  return;
1115 
1116  mValueGroupBox->show();
1117  fillFieldValues( item->text(), -1 );
1118 }
1119 
1120 void QgsExpressionBuilderWidget::txtPython_textChanged()
1121 {
1122  lblAutoSave->setText( tr( "Saving…" ) );
1123  if ( mAutoSave )
1124  {
1125  autosave();
1126  }
1127 }
1128 
1130 {
1131  // Don't auto save if not on function editor that would be silly.
1132  if ( tabWidget->currentIndex() != 1 )
1133  return;
1134 
1135  QListWidgetItem *item = cmbFileNames->currentItem();
1136  if ( !item )
1137  return;
1138 
1139  QString file = item->text();
1140  saveFunctionFile( file );
1141  lblAutoSave->setText( QStringLiteral( "Saved" ) );
1142  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
1143  lblAutoSave->setGraphicsEffect( effect );
1144  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
1145  anim->setDuration( 2000 );
1146  anim->setStartValue( 1.0 );
1147  anim->setEndValue( 0.0 );
1148  anim->setEasingCurve( QEasingCurve::OutQuad );
1149  anim->start( QAbstractAnimation::DeleteWhenStopped );
1150 }
1151 
1152 void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1153 {
1154  if ( state & Qt::ControlModifier )
1155  {
1156  int position = txtExpressionString->positionFromLineIndex( line, index );
1157  long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1158  QgsExpressionFunction *func = QgsExpression::Functions()[fncIndex];
1159  QString help = getFunctionHelp( func );
1160  txtHelpText->setText( help );
1161  }
1162 }
1163 
1164 void QgsExpressionBuilderWidget::setExpressionState( bool state )
1165 {
1166  mExpressionValid = state;
1167 }
1168 
1169 QString QgsExpressionBuilderWidget::helpStylesheet() const
1170 {
1171  //start with default QGIS report style
1172  QString style = QgsApplication::reportStyleSheet();
1173 
1174  //add some tweaks
1175  style += " .functionname {color: #0a6099; font-weight: bold;} "
1176  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1177  " td.argument { padding-right: 10px; }";
1178 
1179  return style;
1180 }
1181 
1182 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1183 {
1184  if ( !expressionItem )
1185  return QString();
1186 
1187  QString helpContents = expressionItem->getHelpText();
1188 
1189  // Return the function help that is set for the function if there is one.
1190  if ( helpContents.isEmpty() )
1191  {
1192  QString name = expressionItem->data( Qt::UserRole ).toString();
1193 
1194  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1195  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1196  else
1197  helpContents = QgsExpression::helpText( name );
1198  }
1199 
1200  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1201 }
1202 
1203 
1204 
1205 
1206 
1208 {
1209  setFilterCaseSensitivity( Qt::CaseInsensitive );
1210 }
1211 
1212 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
1213 {
1214  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
1215  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
1216 
1217  int count = sourceModel()->rowCount( index );
1218  bool matchchild = false;
1219  for ( int i = 0; i < count; ++i )
1220  {
1221  if ( filterAcceptsRow( i, index ) )
1222  {
1223  matchchild = true;
1224  break;
1225  }
1226  }
1227 
1228  if ( itemType == QgsExpressionItem::Header && matchchild )
1229  return true;
1230 
1231  if ( itemType == QgsExpressionItem::Header )
1232  return false;
1233 
1234  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
1235 }
1236 
1237 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
1238 {
1239  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
1240  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
1241  if ( leftSort != rightSort )
1242  return leftSort < rightSort;
1243 
1244  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
1245  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
1246 
1247  //ignore $ prefixes when sorting
1248  if ( leftString.startsWith( '$' ) )
1249  leftString = leftString.mid( 1 );
1250  if ( rightString.startsWith( '$' ) )
1251  rightString = rightString.mid( 1 );
1252 
1253  return QString::localeAwareCompare( leftString, rightString ) < 0;
1254 }
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:183
QgsProject * project()
Returns the project currently associated with the widget.
Class for parsing and evaluation of expressions (formerly called "search strings").
QString name
Definition: qgsrelation.h:48
int parserFirstColumn
First column in the parser this node was found.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true)
Formats an expression result for friendly display to the user.
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:78
bool isHighlightedFunction(const QString &name) const
Returns true if the specified function name is intended to be highlighted to the user.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
QVariantMap config() const
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
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:58
int params() const
The number of parameters this function takes.
QStringList groups() const
Returns a list of the groups the function belongs to.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString group(const QString &group)
Returns the translated name for a function group.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QIcon iconForField(int fieldIdx) const
Returns an icon corresponding to a field index, based on the field&#39;s type and source.
Definition: qgsfields.cpp:275
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QgsExpressionNode * opRight() const
Returns the node to the right of the operator.
WhenThenList conditions() const
The list of WHEN THEN expression parts of the expression.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
Details about any parser errors that were found when parsing the expression.
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:45
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
QVariant evaluate()
Evaluate the feature and return the result.
An expression node for CASE WHEN clauses.
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, QIcon icon=QIcon())
Registers a node item for the expression builder.
QString evalErrorString() const
Returns evaluation error.
Container of fields for a vector layer.
Definition: qgsfields.h:42
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsExpressionNode::NodeList * list() const
Returns the list of nodes to search for matching values within.
QList< QgsExpression::ParserError > parserErrors() const
Returns parser error details including location of error.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:410
QgsExpressionNode * opLeft() const
Returns the node to the left of the operator.
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:55
static QString reportStyleSheet()
Returns a standard css style sheet for reports.
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
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.
Function was called with invalid args.
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.
Non named function arg used after named arg.
QString description(const QString &name) const
Returns a translated description string for the variable with specified name.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
void setExpectedOutputFormat(const QString &expected)
The set expected format string.
void updateFunctionFileList(const QString &path)
Update the list of function files found at the given path.
void setMessageAsHtml(const QString &msg)
static int functionCount()
Returns the number of functions defined in the parser.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsFields fields() const FINAL
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.
QString expectedOutputFormat()
The set expected format string.
Function was called with the wrong number of args.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An expression node for value IN or NOT IN clauses.
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)
Appends 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:89
void loadFieldNames()
Loads all the field names from the layer.
Abstract base class for all nodes that can appear in an expression.
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
An expression node for expression functions.
void autosave()
Auto save the current Python function code.
QgsExpressionNode * elseExp() const
The ELSE expression used for the condition.
static const QList< QgsExpressionFunction * > & Functions()
static QString formatVariableHelp(const QString &description, bool showValue=true, const QVariant &value=QVariant())
Returns formatted help text for a variable.
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
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...
A field formatter helps to handle and display values for a field.
QgsExpressionItem::ItemType getItemType() const
Gets the type of expression item, e.g., header, field, ExpressionNode.
static const int CUSTOM_SORT_ROLE
Custom sort order role.
QString name() const
The name of the function.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations...
A abstract base class for defining QgsExpression functions.
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.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
int parserFirstLine
First line in the parser this node was found.
QStringList functionNames() const
Retrieves a list of function names contained in the context.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
const QString helpText() const
The help text for the function.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QString getExpressionText() const
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
Holder for the widget type and its configuration for a field.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
A unary node is either negative as in boolean (not) or as in numbers (minus).
void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
A binary expression operator, which operates on two values.
A generic message view for displaying QGIS messages.
bool isEmpty() const
Checks whether the container is empty.
Definition: qgsfields.cpp:128
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.
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
Gets the help text that is associated with this expression item.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:82
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)
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.
virtual bool isDeprecated() const
Returns true if the function is deprecated and should not be presented as a valid option to users in ...
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
QgsExpressionNode * operand() const
Returns the node the operator will operate upon.
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)
Represents a "WHEN... THEN..." portation of a CASE WHEN clause in an expression.
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.
int parserLastColumn
Last column in the parser this node was found.
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object...
int fnIndex() const
Returns the index of the node&#39;s function.