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