QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
16 
17 #include <QFile>
18 #include <QTextStream>
19 #include <QDir>
20 #include <QInputDialog>
21 #include <QComboBox>
22 #include <QGraphicsOpacityEffect>
23 #include <QPropertyAnimation>
24 #include <QMessageBox>
25 #include <QVersionNumber>
26 #include <QDateTime>
27 #include <QJsonDocument>
28 #include <QJsonObject>
29 #include <QJsonArray>
30 #include <QFileDialog>
31 #include <QMenu>
32 
34 #include "qgslogger.h"
35 #include "qgsexpression.h"
36 #include "qgsexpressionfunction.h"
37 #include "qgsexpressionnodeimpl.h"
38 #include "qgsapplication.h"
39 #include "qgspythonrunner.h"
40 #include "qgsgeometry.h"
41 #include "qgsfeature.h"
42 #include "qgsfeatureiterator.h"
43 #include "qgsvectorlayer.h"
44 #include "qgssettings.h"
45 #include "qgsproject.h"
46 #include "qgsrelation.h"
49 #include "qgsfieldformatter.h"
51 #include "qgsexpressiontreeview.h"
52 
53 
54 
55 bool formatterCanProvideAvailableValues( QgsVectorLayer *layer, const QString &fieldName )
56 {
57  if ( layer )
58  {
59  const QgsFields fields = layer->fields();
60  int fieldIndex = fields.lookupField( fieldName );
61  if ( fieldIndex != -1 )
62  {
63  const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
65 
67  }
68  }
69  return false;
70 }
71 
72 
74  : QWidget( parent )
75  , mProject( QgsProject::instance() )
76 {
77  setupUi( this );
78 
79  connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
80  connect( btnNewFile, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
81  connect( btnRemoveFile, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::btnRemoveFile_pressed );
82  connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
83  connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
84  connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
85  connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
86  connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
87  connect( btnSaveExpression, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::storeCurrentUserExpression );
88  connect( btnEditExpression, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::editSelectedUserExpression );
89  connect( btnRemoveExpression, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::removeSelectedUserExpression );
90  connect( btnImportExpressions, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::importUserExpressions_pressed );
91  connect( btnExportExpressions, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::exportUserExpressions_pressed );
92  connect( btnClearEditor, &QPushButton::pressed, txtExpressionString, &QgsCodeEditorExpression::clear );
93  connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, mExpressionTreeView, &QgsExpressionTreeView::setSearchText );
94 
95  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::toolTipChanged, txtExpressionString, &QgsCodeEditorExpression::setToolTip );
96  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::onExpressionParsed );
97  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, btnSaveExpression, &QToolButton::setEnabled );
98  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::expressionParsed ); // signal-to-signal
99  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::parserErrorChanged, this, &QgsExpressionBuilderWidget::parserErrorChanged ); // signal-to-signal
100  connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::evalErrorChanged, this, &QgsExpressionBuilderWidget::evalErrorChanged ); // signal-to-signal
101 
102  connect( mExpressionTreeView, &QgsExpressionTreeView::expressionItemDoubleClicked, this, &QgsExpressionBuilderWidget::insertExpressionText );
103  connect( mExpressionTreeView, &QgsExpressionTreeView::currentExpressionItemChanged, this, &QgsExpressionBuilderWidget::expressionTreeItemChanged );
104 
105  mExpressionTreeMenuProvider = new ExpressionTreeMenuProvider( this );
106  mExpressionTreeView->setMenuProvider( mExpressionTreeMenuProvider );
107 
108  txtHelpText->setOpenExternalLinks( true );
109  mValueGroupBox->hide();
110  // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
111 
112  // Note: must be in sync with the json help file for UserGroup
113  mUserExpressionsGroupName = QgsExpression::group( QStringLiteral( "UserGroup" ) );
114 
115  // Set icons for tool buttons
116  btnSaveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileSave.svg" ) ) );
117  btnEditExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "symbologyEdit.svg" ) ) );
118  btnRemoveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionDeleteSelected.svg" ) ) );
119  btnExportExpressions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionSharingExport.svg" ) ) );
120  btnImportExpressions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionSharingImport.svg" ) ) );
121  btnClearEditor->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileNew.svg" ) ) );
122 
123  connect( btnLoadAll, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadAllValues );
124  connect( btnLoadSample, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadSampleValues );
125 
126  const auto pushButtons { mOperatorsGroupBox->findChildren<QPushButton *>() };
127  for ( QPushButton *button : pushButtons )
128  {
129  connect( button, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
130  }
131 
132  txtSearchEdit->setShowSearchIcon( true );
133  txtSearchEdit->setPlaceholderText( tr( "Search…" ) );
134 
135  mValuesModel = std::make_unique<QStandardItemModel>();
136  mProxyValues = std::make_unique<QSortFilterProxyModel>();
137  mProxyValues->setSourceModel( mValuesModel.get() );
138  mValuesListView->setModel( mProxyValues.get() );
139  txtSearchEditValues->setShowSearchIcon( true );
140  txtSearchEditValues->setPlaceholderText( tr( "Search…" ) );
141 
142  editorSplit->setSizes( QList<int>( {175, 300} ) );
143 
144  functionsplit->setCollapsible( 0, false );
145  connect( mShowHelpButton, &QPushButton::clicked, this, [ = ]()
146  {
147  functionsplit->setSizes( QList<int>( {mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(),
148  mHelpAndValuesWidget->minimumWidth()
149  } ) );
150  mShowHelpButton->setEnabled( false );
151  } );
152  connect( functionsplit, &QSplitter::splitterMoved, this, [ = ]( int, int )
153  {
154  mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
155  } );
156 
157  QgsSettings settings;
158  splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
159  editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
160  functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
161  mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
162 
163  if ( QgsPythonRunner::isValid() )
164  {
165  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
166  updateFunctionFileList( mFunctionsPath );
167  btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
168  }
169  else
170  {
171  tab_2->hide();
172  }
173 
174  txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
175  lblAutoSave->clear();
176 
177  // Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
178  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
179  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
180  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionInvalidParams );
181  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionNamedArgsError );
182 #if defined(QSCINTILLA_VERSION) && QSCINTILLA_VERSION >= 0x20a00
183  txtExpressionString->indicatorDefine( QgsCodeEditor::TriangleIndicator, QgsExpression::ParserError::Unknown );
184 #else
185  txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::Unknown );
186 #endif
187 
188  // Set all the error markers as red. -1 is all.
189  txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
190  txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
191  txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
192 
193  // Hidden function markers.
194  txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
195  txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
196  txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
197  txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
198 
199  connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
200  txtExpressionString->setAutoCompletionCaseSensitivity( false );
201  txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
202  txtExpressionString->setCallTipsVisible( 0 );
203 
204  setExpectedOutputFormat( QString() );
205  mFunctionBuilderHelp->setLineNumbersVisible( false );
206  mFunctionBuilderHelp->setFoldingVisible( false );
207  mFunctionBuilderHelp->setEdgeMode( QsciScintilla::EdgeNone );
208  mFunctionBuilderHelp->setEdgeColumn( 0 );
209  mFunctionBuilderHelp->setReadOnly( true );
210  mFunctionBuilderHelp->setText( tr( "\"\"\"Define a new function using the @qgsfunction decorator.\n\
211 \n\
212  The function accepts the following parameters\n\
213 \n\
214  : param [any]: Define any parameters you want to pass to your function before\n\
215  the following arguments.\n\
216  : param feature: The current feature\n\
217  : param parent: The QgsExpression object\n\
218  : param context: If there is an argument called ``context`` found at the last\n\
219  position, this variable will contain a ``QgsExpressionContext``\n\
220  object, that gives access to various additional information like\n\
221  expression variables. E.g. ``context.variable( 'layer_id' )``\n\
222  : returns: The result of the expression.\n\
223 \n\
224 \n\
225 \n\
226  The @qgsfunction decorator accepts the following arguments:\n\
227 \n\
228 \n\
229  : param args: Defines the number of arguments. With ``args = 'auto'`` the number of\n\
230  arguments will automatically be extracted from the signature.\n\
231  With ``args = -1``, any number of arguments are accepted.\n\
232  : param group: The name of the group under which this expression function will\n\
233  be listed.\n\
234  : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
235  If False, the result will always be NULL as soon as any parameter is NULL.\n\
236  Defaults to False.\n\
237  : param usesgeometry : Set this to True if your function requires access to\n\
238  feature.geometry(). Defaults to False.\n\
239  : param referenced_columns: An array of attribute names that are required to run\n\
240  this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
241  \"\"\"" ) );
242 }
243 
244 
246 {
247  QgsSettings settings;
248  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
249  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
250  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
251  delete mExpressionTreeMenuProvider;
252 }
253 
254 void QgsExpressionBuilderWidget::init( const QgsExpressionContext &context, const QString &recentCollection, const Flags &flags )
255 {
256  setExpressionContext( context );
257 
258  if ( flags.testFlag( LoadRecent ) )
259  mExpressionTreeView->loadRecent( recentCollection );
260 
261  if ( flags.testFlag( LoadUserExpressions ) )
262  mExpressionTreeView->loadUserExpressions();
263 }
264 
265 void QgsExpressionBuilderWidget::initWithLayer( QgsVectorLayer *layer, const QgsExpressionContext &context, const QString &recentCollection, const Flags &flags )
266 {
267  init( context, recentCollection, flags );
268  setLayer( layer );
269 }
270 
271 void QgsExpressionBuilderWidget::initWithFields( const QgsFields &fields, const QgsExpressionContext &context, const QString &recentCollection, const Flags &flags )
272 {
273  init( context, recentCollection, flags );
274  mExpressionTreeView->loadFieldNames( fields );
275 }
276 
277 
279 {
280  mLayer = layer;
281  mExpressionTreeView->setLayer( mLayer );
282  mExpressionPreviewWidget->setLayer( mLayer );
283 
284  //TODO - remove existing layer scope from context
285 
286  if ( mLayer )
287  {
288  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
289  expressionContextUpdated();
290  txtExpressionString->setFields( mLayer->fields() );
291  }
292 }
293 
294 void QgsExpressionBuilderWidget::expressionContextUpdated()
295 {
296  txtExpressionString->setExpressionContext( mExpressionContext );
297  mExpressionTreeView->setExpressionContext( mExpressionContext );
298  mExpressionPreviewWidget->setExpressionContext( mExpressionContext );
299 }
300 
302 {
303  return mLayer;
304 }
305 
306 void QgsExpressionBuilderWidget::expressionTreeItemChanged( QgsExpressionItem *item )
307 {
308  txtSearchEditValues->clear();
309 
310  if ( !item )
311  return;
312 
313  bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
314  if ( isField )
315  {
316  mValuesModel->clear();
317 
318  cbxValuesInUse->setVisible( formatterCanProvideAvailableValues( mLayer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) );
319  cbxValuesInUse->setChecked( false );
320  }
321  mValueGroupBox->setVisible( isField );
322 
323  mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
324 
325  // Show the help for the current item.
326  QString help = loadFunctionHelp( item );
327  txtHelpText->setText( help );
328 
329  bool isUserExpression = item->parent() && item->parent()->text() == mUserExpressionsGroupName;
330 
331  btnRemoveExpression->setEnabled( isUserExpression );
332  btnEditExpression->setEnabled( isUserExpression );
333 }
334 
335 void QgsExpressionBuilderWidget::btnRun_pressed()
336 {
337  if ( !cmbFileNames->currentItem() )
338  return;
339 
340  QString file = cmbFileNames->currentItem()->text();
341  saveFunctionFile( file );
342  runPythonCode( txtPython->text() );
343 }
344 
345 void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
346 {
347  if ( QgsPythonRunner::isValid() )
348  {
349  QString pythontext = code;
350  QgsPythonRunner::run( pythontext );
351  }
352  mExpressionTreeView->refresh();
353 }
354 
356 {
357  QDir myDir( mFunctionsPath );
358  if ( !myDir.exists() )
359  {
360  myDir.mkpath( mFunctionsPath );
361  }
362 
363  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
364  {
365  fileName.append( ".py" );
366  }
367 
368  fileName = mFunctionsPath + QDir::separator() + fileName;
369  QFile myFile( fileName );
370  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
371  {
372  QTextStream myFileStream( &myFile );
373  myFileStream << txtPython->text() << endl;
374  myFile.close();
375  }
376 }
377 
379 {
380  mFunctionsPath = path;
381  QDir dir( path );
382  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
383  QStringList files = dir.entryList( QDir::Files );
384  cmbFileNames->clear();
385  const auto constFiles = files;
386  for ( const QString &name : constFiles )
387  {
388  QFileInfo info( mFunctionsPath + QDir::separator() + name );
389  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
390  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), info.baseName() );
391  cmbFileNames->addItem( item );
392  }
393  if ( !cmbFileNames->currentItem() )
394  {
395  cmbFileNames->setCurrentRow( 0 );
396  }
397 
398  if ( cmbFileNames->count() == 0 )
399  {
400  // Create default sample entry.
401  newFunctionFile( "default" );
402  txtPython->setText( QStringLiteral( "'''\n#Sample custom function file\n"
403  "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''" ).arg( txtPython->text() ) );
404  saveFunctionFile( "default" );
405  }
406 }
407 
408 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
409 {
410  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
411  if ( !items.isEmpty() )
412  return;
413 
414  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
415  cmbFileNames->insertItem( 0, item );
416  cmbFileNames->setCurrentRow( 0 );
417 
418  QString templatetxt;
419  QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templatetxt );
420  txtPython->setText( templatetxt );
421  saveFunctionFile( fileName );
422 }
423 
424 void QgsExpressionBuilderWidget::btnNewFile_pressed()
425 {
426  bool ok;
427  QString text = QInputDialog::getText( this, tr( "New File" ),
428  tr( "New file name:" ), QLineEdit::Normal,
429  QString(), &ok );
430  if ( ok && !text.isEmpty() )
431  {
432  newFunctionFile( text );
433  }
434 }
435 
436 void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
437 {
438  if ( QMessageBox::question( this, tr( "Remove File" ),
439  tr( "Are you sure you want to remove current functions file?" ),
440  QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
441  return;
442 
443  int currentRow = cmbFileNames->currentRow();
444  QString fileName = cmbFileNames->currentItem()->text();
445  if ( QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
446  {
447  {
448  QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
449  delete itemToRemove;
450  }
451 
452  if ( cmbFileNames->count() > 0 )
453  {
454  cmbFileNames->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
455  loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
456  }
457  else
458  {
459  btnRemoveFile->setEnabled( false );
460  txtPython->clear();
461  }
462  }
463  else
464  {
465  QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
466  }
467 }
468 
469 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
470 {
471  if ( lastitem )
472  {
473  QString filename = lastitem->text();
474  saveFunctionFile( filename );
475  }
476  QString path = mFunctionsPath + QDir::separator() + item->text();
477  loadCodeFromFile( path );
478 }
479 
481 {
482  if ( !path.endsWith( QLatin1String( ".py" ) ) )
483  path.append( ".py" );
484 
485  txtPython->loadScript( path );
486 }
487 
489 {
490  txtPython->setText( code );
491 }
492 
493 void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
494 {
495  // Insert the expression text or replace selected text
496  txtExpressionString->insertText( text );
497  txtExpressionString->setFocus();
498 }
499 
500 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
501 {
502  Q_UNUSED( fieldValues )
503  // This is not maintained and setLayer() should be used instead.
504 }
505 
506 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit, bool forceUsedValues )
507 {
508  // TODO We should really return a error the user of the widget that
509  // the there is no layer set.
510  if ( !mLayer )
511  return;
512 
513  // TODO We should thread this so that we don't hold the user up if the layer is massive.
514 
515  const QgsFields fields = mLayer->fields();
516  int fieldIndex = fields.lookupField( fieldName );
517 
518  if ( fieldIndex < 0 )
519  return;
520 
521  const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
523 
524  QVariantList values;
525  if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
526  {
527  QgsFieldFormatterContext fieldFormatterContext;
528  fieldFormatterContext.setProject( mProject );
529  values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
530  }
531  else
532  {
533  values = qgis::setToList( mLayer->uniqueValues( fieldIndex, countLimit ) );
534  }
535  std::sort( values.begin(), values.end() );
536 
537  mValuesModel->clear();
538  for ( const QVariant &value : std::as_const( values ) )
539  {
540  QString strValue;
541  if ( value.isNull() )
542  strValue = QStringLiteral( "NULL" );
543  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
544  strValue = value.toString();
545  else
546  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
547 
548  QString representedValue = formatter->representValue( mLayer, fieldIndex, setup.config(), QVariant(), value );
549  if ( representedValue != value.toString() )
550  representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
551 
552  QStandardItem *item = new QStandardItem( representedValue );
553  item->setData( strValue );
554  mValuesModel->appendRow( item );
555  }
556 }
557 
558 QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
559 {
560  if ( !function )
561  return QString();
562 
563  QString helpContents = QgsExpression::helpText( function->name() );
564 
565  return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
566 
567 }
568 
569 
570 
572 {
573  return mExpressionValid;
574 }
575 
576 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
577 {
578  mExpressionTreeView->saveToRecent( expressionText(), collection );
579 }
580 
581 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
582 {
583  mExpressionTreeView->loadRecent( collection );
584 }
585 
587 {
588  return mExpressionTreeView;
589 }
590 
591 // this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
593 {
594  mExpressionTreeView->loadUserExpressions();
595 }
596 
597 void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString expression, const QString &helpText )
598 {
599  mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
600 }
601 
603 {
604  mExpressionTreeView->removeFromUserExpressions( label );
605 }
606 
607 
609 {
610  mExpressionPreviewWidget->setGeomCalculator( da );
611 }
612 
614 {
615  return txtExpressionString->text();
616 }
617 
618 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
619 {
620  txtExpressionString->setText( expression );
621 }
622 
624 {
625  return lblExpected->text();
626 }
627 
629 {
630  lblExpected->setText( expected );
631  mExpectedOutputFrame->setVisible( !expected.isNull() );
632 }
633 
635 {
636  mExpressionContext = context;
637  expressionContextUpdated();
638 }
639 
640 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
641 {
642  QString text = expressionText();
643 
644  btnClearEditor->setEnabled( ! txtExpressionString->text().isEmpty() );
645  btnSaveExpression->setEnabled( false );
646 
647  mExpressionPreviewWidget->setExpressionText( text );
648 }
649 
651 {
652  return mExpressionPreviewWidget->parserError();
653 }
654 
656 {
657  return mExpressionPreviewWidget->evalError();
658 }
659 
661 {
663  return mExpressionTreeView->model();
665 }
666 
668 {
669  return mProject;
670 }
671 
673 {
674  mProject = project;
675  mExpressionTreeView->setProject( project );
676 }
677 
679 {
680  QWidget::showEvent( e );
681  txtExpressionString->setFocus();
682 }
683 
684 void QgsExpressionBuilderWidget::createErrorMarkers( QList<QgsExpression::ParserError> errors )
685 {
686  clearErrors();
687  for ( const QgsExpression::ParserError &error : errors )
688  {
689  int errorFirstLine = error.firstLine - 1 ;
690  int errorFirstColumn = error.firstColumn - 1;
691  int errorLastColumn = error.lastColumn - 1;
692  int errorLastLine = error.lastLine - 1;
693 
694  // If we have a unknown error we just mark the point that hit the error for now
695  // until we can handle others more.
696  if ( error.errorType == QgsExpression::ParserError::Unknown )
697  {
698  errorFirstLine = errorLastLine;
699  errorFirstColumn = errorLastColumn - 1;
700  }
701  txtExpressionString->fillIndicatorRange( errorFirstLine,
702  errorFirstColumn,
703  errorLastLine,
704  errorLastColumn, error.errorType );
705  }
706 }
707 
708 void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
709 {
710  switch ( inNode->nodeType() )
711  {
712  case QgsExpressionNode::NodeType::ntFunction:
713  {
714  const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
715  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
716  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
717  int start = inNode->parserFirstColumn - 1;
718  int end = inNode->parserLastColumn - 1;
719  int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
720  txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
721  if ( node->args() )
722  {
723  const QList< QgsExpressionNode * > nodeList = node->args()->list();
724  for ( QgsExpressionNode *n : nodeList )
725  {
726  createMarkers( n );
727  }
728  }
729  break;
730  }
731  case QgsExpressionNode::NodeType::ntLiteral:
732  {
733  break;
734  }
735  case QgsExpressionNode::NodeType::ntUnaryOperator:
736  {
737  const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
738  createMarkers( node->operand() );
739  break;
740  }
741  case QgsExpressionNode::NodeType::ntBinaryOperator:
742  {
743  const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
744  createMarkers( node->opLeft() );
745  createMarkers( node->opRight() );
746  break;
747  }
748  case QgsExpressionNode::NodeType::ntColumnRef:
749  {
750  break;
751  }
752  case QgsExpressionNode::NodeType::ntInOperator:
753  {
754  const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
755  if ( node->list() )
756  {
757  const QList< QgsExpressionNode * > nodeList = node->list()->list();
758  for ( QgsExpressionNode *n : nodeList )
759  {
760  createMarkers( n );
761  }
762  }
763  break;
764  }
765  case QgsExpressionNode::NodeType::ntCondition:
766  {
767  const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
768  for ( QgsExpressionNodeCondition::WhenThen *cond : node->conditions() )
769  {
770  createMarkers( cond->whenExp() );
771  createMarkers( cond->thenExp() );
772  }
773  if ( node->elseExp() )
774  {
775  createMarkers( node->elseExp() );
776  }
777  break;
778  }
779  case QgsExpressionNode::NodeType::ntIndexOperator:
780  {
781  break;
782  }
783  }
784 }
785 
786 void QgsExpressionBuilderWidget::clearFunctionMarkers()
787 {
788  int lastLine = txtExpressionString->lines() - 1;
789  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
790 }
791 
792 void QgsExpressionBuilderWidget::clearErrors()
793 {
794  int lastLine = txtExpressionString->lines() - 1;
795  // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
796  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
797  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
798  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
799  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
800  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
801 }
802 
803 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
804 {
805  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
806  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
807 }
808 
809 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
810 {
811  // Insert the item text or replace selected text
812  txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
813  txtExpressionString->setFocus();
814 }
815 
816 void QgsExpressionBuilderWidget::operatorButtonClicked()
817 {
818  QPushButton *button = qobject_cast<QPushButton *>( sender() );
819 
820  // Insert the button text or replace selected text
821  txtExpressionString->insertText( ' ' + button->text() + ' ' );
822  txtExpressionString->setFocus();
823 }
824 
826 {
827  QgsExpressionItem *item = mExpressionTreeView->currentItem();
828  // TODO We should really return a error the user of the widget that
829  // the there is no layer set.
830  if ( !mLayer || !item )
831  return;
832 
833  mValueGroupBox->show();
834  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), 10 );
835 }
836 
838 {
839  QgsExpressionItem *item = mExpressionTreeView->currentItem();
840  // TODO We should really return a error the user of the widget that
841  // the there is no layer set.
842  if ( !mLayer || !item )
843  return;
844 
845  mValueGroupBox->show();
846  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), -1 );
847 }
848 
850 {
851  QgsExpressionItem *item = mExpressionTreeView->currentItem();
852  // TODO We should really return a error the user of the widget that
853  // the there is no layer set.
854  if ( !mLayer || !item )
855  return;
856 
857  mValueGroupBox->show();
858  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), 10, true );
859 }
860 
862 {
863  QgsExpressionItem *item = mExpressionTreeView->currentItem();
864  // TODO We should really return a error the user of the widget that
865  // the there is no layer set.
866  if ( !mLayer || !item )
867  return;
868 
869  mValueGroupBox->show();
870  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), -1, true );
871 }
872 
873 void QgsExpressionBuilderWidget::txtPython_textChanged()
874 {
875  lblAutoSave->setText( tr( "Saving…" ) );
876  if ( mAutoSave )
877  {
878  autosave();
879  }
880 }
881 
883 {
884  // Don't auto save if not on function editor that would be silly.
885  if ( tabWidget->currentIndex() != 1 )
886  return;
887 
888  QListWidgetItem *item = cmbFileNames->currentItem();
889  if ( !item )
890  return;
891 
892  QString file = item->text();
893  saveFunctionFile( file );
894  lblAutoSave->setText( QStringLiteral( "Saved" ) );
895  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
896  lblAutoSave->setGraphicsEffect( effect );
897  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
898  anim->setDuration( 2000 );
899  anim->setStartValue( 1.0 );
900  anim->setEndValue( 0.0 );
901  anim->setEasingCurve( QEasingCurve::OutQuad );
902  anim->start( QAbstractAnimation::DeleteWhenStopped );
903 }
904 
906 {
907  const QString expression { this->expressionText() };
908  QgsExpressionStoreDialog dlg { expression, expression, QString( ), mExpressionTreeView->userExpressionLabels() };
909  if ( dlg.exec() == QDialog::DialogCode::Accepted )
910  {
911  mExpressionTreeView->saveToUserExpressions( dlg.label(), dlg.expression(), dlg.helpText() );
912  }
913 }
914 
916 {
917  // Get the item
918  QgsExpressionItem *item = mExpressionTreeView->currentItem();
919  if ( !item )
920  return;
921 
922  // Don't handle remove if we are on a header node or the parent
923  // is not the user group
924  if ( item->getItemType() == QgsExpressionItem::Header ||
925  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
926  return;
927 
928  QgsSettings settings;
929  QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
930  QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText };
931 
932  if ( dlg.exec() == QDialog::DialogCode::Accepted )
933  {
934  // label has changed removed the old one before adding the new one
935  if ( dlg.label() != item->text() )
936  {
937  mExpressionTreeView->removeFromUserExpressions( item->text() );
938  }
939 
940  mExpressionTreeView->saveToUserExpressions( dlg.label(), dlg.expression(), dlg.helpText() );
941  }
942 }
943 
945 {
946  // Get the item
947  QgsExpressionItem *item = mExpressionTreeView->currentItem();
948 
949  if ( !item )
950  return;
951 
952  // Don't handle remove if we are on a header node or the parent
953  // is not the user group
954  if ( item->getItemType() == QgsExpressionItem::Header ||
955  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
956  return;
957 
958  if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ),
959  tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ),
960  QMessageBox::Yes | QMessageBox::No ) )
961  {
962  mExpressionTreeView->removeFromUserExpressions( item->text() );
963  }
964 
965 }
966 
967 void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
968 {
969  QgsSettings settings;
970  QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
971  QString saveFileName = QFileDialog::getSaveFileName(
972  this,
973  tr( "Export User Expressions" ),
974  lastSaveDir,
975  tr( "User expressions" ) + " (*.json)" );
976 
977  if ( saveFileName.isEmpty() )
978  return;
979 
980  QFileInfo saveFileInfo( saveFileName );
981 
982  if ( saveFileInfo.suffix().isEmpty() )
983  {
984  QString saveFileNameWithSuffix = saveFileName.append( ".json" );
985  saveFileInfo = QFileInfo( saveFileNameWithSuffix );
986  }
987 
988  settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
989 
990  QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
991  QFile jsonFile( saveFileName );
992 
993  if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
994  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
995 
996  if ( ! jsonFile.write( exportJson.toJson() ) )
997  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
998  else
999  jsonFile.close();
1000 }
1001 
1002 void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1003 {
1004  QgsSettings settings;
1005  QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1006  QString loadFileName = QFileDialog::getOpenFileName(
1007  this,
1008  tr( "Import User Expressions" ),
1009  lastImportDir,
1010  tr( "User expressions" ) + " (*.json)" );
1011 
1012  if ( loadFileName.isEmpty() )
1013  return;
1014 
1015  QFileInfo loadFileInfo( loadFileName );
1016 
1017  settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), loadFileInfo.absolutePath(), QgsSettings::App );
1018 
1019  QFile jsonFile( loadFileName );
1020 
1021  if ( !jsonFile.open( QFile::ReadOnly ) )
1022  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1023 
1024  QTextStream jsonStream( &jsonFile );
1025  QString jsonString = jsonFile.readAll();
1026  jsonFile.close();
1027 
1028  QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1029 
1030  if ( importJson.isNull() )
1031  {
1032  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1033  return;
1034  }
1035 
1036  mExpressionTreeView->loadExpressionsFromJson( importJson );
1037 }
1038 
1039 
1040 const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1041 {
1042  return mExpressionTreeView->findExpressions( label );
1043 }
1044 
1045 void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1046 {
1047  if ( state & Qt::ControlModifier )
1048  {
1049  int position = txtExpressionString->positionFromLineIndex( line, index );
1050  long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1051  QgsExpressionFunction *func = QgsExpression::Functions()[fncIndex];
1052  QString help = getFunctionHelp( func );
1053  txtHelpText->setText( help );
1054  }
1055 }
1056 
1057 void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1058 {
1059  clearErrors();
1060 
1061  mExpressionValid = state;
1062  if ( state )
1063  {
1064  createMarkers( mExpressionPreviewWidget->rootNode() );
1065  }
1066  else
1067  {
1068  createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1069  }
1070 }
1071 
1072 QString QgsExpressionBuilderWidget::helpStylesheet() const
1073 {
1074  //start with default QGIS report style
1075  QString style = QgsApplication::reportStyleSheet();
1076 
1077  //add some tweaks
1078  style += " .functionname {color: #0a6099; font-weight: bold;} "
1079  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1080  " td.argument { padding-right: 10px; }";
1081 
1082  return style;
1083 }
1084 
1085 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1086 {
1087  if ( !expressionItem )
1088  return QString();
1089 
1090  QString helpContents = expressionItem->getHelpText();
1091 
1092  // Return the function help that is set for the function if there is one.
1093  if ( helpContents.isEmpty() )
1094  {
1095  QString name = expressionItem->data( Qt::UserRole ).toString();
1096 
1097  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1098  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1099  else
1100  helpContents = QgsExpression::helpText( name );
1101  }
1102 
1103  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1104 }
1105 
1106 
1107 // *************
1108 // Menu provider
1109 
1110 QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1111 {
1112  QMenu *menu = nullptr;
1113  QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1114  if ( item->getItemType() == QgsExpressionItem::Field && layer )
1115  {
1116  menu = new QMenu( mExpressionBuilderWidget );
1117  menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1118  menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1119 
1120  if ( formatterCanProvideAvailableValues( layer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) )
1121  {
1122  menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, SLOT( loadSampleUsedValues() ) );
1123  menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1124  }
1125  }
1126  return menu;
1127 }
static QString reportStyleSheet(QgsApplication::StyleSheetType styleSheetType=QgsApplication::StyleSheetType::Qt)
Returns a css style sheet for reports, the styleSheetType argument determines what type of stylesheet...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Q_DECL_DEPRECATED void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
Q_DECL_DEPRECATED void saveToRecent(const QString &collection="generic")
Adds the current expression to the given collection.
@ LoadRecent
Load recent expressions given the collection key.
@ LoadUserExpressions
Load user expressions.
void initWithFields(const QgsFields &fields, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), const Flags &flags=LoadAll)
Initialize with given fields without any layer.
void loadSampleValues()
Load sample values into the sample value area.
Q_DECL_DEPRECATED void loadUserExpressions()
Loads the user expressions.
QString expressionText()
Gets the expression string that has been set in the expression area.
QString expectedOutputFormat()
The set expected format string.
void parserErrorChanged()
Will be set to true if the current expression text reported a parser error with the context.
Q_DECL_DEPRECATED void removeFromUserExpressions(const QString &label)
Removes the expression label from the user stored expressions.
void loadFunctionCode(const QString &code)
Loads code into the function editor.
Q_DECL_DEPRECATED QStandardItemModel * model()
Returns a pointer to the dialog's function item model.
QgsExpressionTreeView * expressionTree() const
Returns the expression tree.
Q_DECL_DEPRECATED void saveToUserExpressions(const QString &label, const QString expression, const QString &helpText)
Stores the user expression with given label and helpText.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context.
bool isExpressionValid()
Returns if the expression is valid.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
void loadCodeFromFile(QString path)
Loads code from the given file into the function editor.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
bool parserError() const
Will be set to true if the current expression text reports a parser error with the context.
QgsProject * project()
Returns the project currently associated with the widget.
Q_DECL_DEPRECATED void loadRecent(const QString &collection=QStringLiteral("generic"))
Loads the recent expressions from the given collection.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
bool evalError() const
Will be set to true if the current expression text reported an eval error with the context.
void storeCurrentUserExpression()
Adds the current expressions to the stored user expressions.
void updateFunctionFileList(const QString &path)
Updates the list of function files found at the given path.
void showEvent(QShowEvent *e) override
void setExpectedOutputFormat(const QString &expected)
The set expected format string.
void init(const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), const Flags &flags=LoadAll)
Initialize without any layer.
void removeSelectedUserExpression()
Removes the selected expression from the stored user expressions, the selected expression must be a u...
void newFunctionFile(const QString &fileName="scratch")
Creates a new file in the function editor.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the widget.
void loadAllUsedValues()
Load all unique values from the set layer into the sample area.
const QList< QgsExpressionItem * > findExpressions(const QString &label)
Returns the list of expression items matching a label.
void editSelectedUserExpression()
Edits the selected expression from the stored user expressions, the selected expression must be a use...
void initWithLayer(QgsVectorLayer *layer, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), const Flags &flags=LoadAll)
Initialize with a layer.
QgsVectorLayer * layer() const
Returns the current layer or a nullptr.
QgsExpressionBuilderWidget(QWidget *parent=nullptr)
Create a new expression builder widget with an optional parent.
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
void loadAllValues()
Load all unique values from the set layer into the sample area.
void autosave()
Auto save the current Python function code.
void loadSampleUsedValues()
Load used sample values into the sample value area.
void saveFunctionFile(QString fileName)
Saves the current function editor text to the given file.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
A abstract base class for defining QgsExpression functions.
An expression item that can be used in the QgsExpressionBuilderWidget tree.
QString getExpressionText() const
QgsExpressionItem::ItemType getItemType() const
Gets the type of expression item, e.g., header, field, ExpressionNode.
QString getHelpText() const
Gets the help text that is associated with this expression item.
static const int ITEM_NAME_ROLE
Item name role.
A binary expression operator, which operates on two values.
QgsExpressionNode * opRight() const
Returns the node to the right of the operator.
QgsExpressionNode * opLeft() const
Returns the node to the left of the operator.
Represents a "WHEN... THEN..." portation of a CASE WHEN clause in an expression.
An expression node for CASE WHEN clauses.
QgsExpressionNode * elseExp() const
The ELSE expression used for the condition.
WhenThenList conditions() const
The list of WHEN THEN expression parts of the expression.
An expression node for expression functions.
int fnIndex() const
Returns the index of the node's function.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
An expression node for value IN or NOT IN clauses.
QgsExpressionNode::NodeList * list() const
Returns the list of nodes to search for matching values within.
A unary node is either negative as in boolean (not) or as in numbers (minus).
QgsExpressionNode * operand() const
Returns the node the operator will operate upon.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
Abstract base class for all nodes that can appear in an expression.
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
int parserFirstLine
First line in the parser this node was found.
int parserLastColumn
Last column in the parser this node was found.
int parserFirstColumn
First column in the parser this node was found.
void parserErrorChanged()
Will be set to true if the current expression text reported a parser error with the context.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context.
void toolTipChanged(const QString &toolTip)
Emitted whenever the tool tip changed.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
A generic dialog for editing expression text, label and help text.
QgsExpressionTreeView is a tree view to list all expressions functions, variables and fields that can...
void expressionItemDoubleClicked(const QString &text)
Emitted when a expression item is double clicked.
void currentExpressionItemChanged(QgsExpressionItem *item)
Emitter when the current expression item changed.
void setSearchText(const QString &text)
Sets the text to filter the expression tree.
static const QList< QgsExpressionFunction * > & Functions()
static QString helpText(QString name)
Returns the help text for a specified function.
static QString group(const QString &group)
Returns the translated name for a function group.
A context for field formatter containing information like the project.
void setProject(QgsProject *project)
Sets the project used in field formatter.
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
@ CanProvideAvailableValues
Can provide possible values.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:559
Container of fields for a vector layer.
Definition: qgsfields.h:45
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands)
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:1080
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1079
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:537
bool formatterCanProvideAvailableValues(QgsVectorLayer *layer, const QString &fieldName)
Details about any parser errors that were found when parsing the expression.
@ FunctionInvalidParams
Function was called with invalid args.
@ Unknown
Unknown error type.
@ FunctionUnknown
Function was unknown.
@ FunctionNamedArgsError
Non named function arg used after named arg.
@ FunctionWrongArgs
Function was called with the wrong number of args.