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