QGIS API Documentation  3.21.0-Master (5b68dc587e)
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::clicked, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
81  connect( btnRemoveFile, &QPushButton::clicked, 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, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::storeCurrentUserExpression );
88  connect( btnEditExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::editSelectedUserExpression );
89  connect( btnRemoveExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::removeSelectedUserExpression );
90  connect( btnImportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::importUserExpressions_pressed );
91  connect( btnExportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::exportUserExpressions_pressed );
92  connect( btnClearEditor, &QToolButton::clicked, 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::clicked, this, &QgsExpressionBuilderWidget::loadAllValues );
124  connect( btnLoadSample, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::loadSampleValues );
125 
126  const auto pushButtons { mOperatorsGroupBox->findChildren<QPushButton *>() };
127  for ( QPushButton *button : pushButtons )
128  {
129  connect( button, &QAbstractButton::clicked, 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, QgsExpressionBuilderWidget::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, QgsExpressionBuilderWidget::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, QgsExpressionBuilderWidget::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 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
374  myFileStream << txtPython->text() << endl;
375 #else
376  myFileStream << txtPython->text() << Qt::endl;
377 #endif
378  myFile.close();
379  }
380 }
381 
383 {
384  mFunctionsPath = path;
385  QDir dir( path );
386  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
387  QStringList files = dir.entryList( QDir::Files );
388  cmbFileNames->clear();
389  const auto constFiles = files;
390  for ( const QString &name : constFiles )
391  {
392  QFileInfo info( mFunctionsPath + QDir::separator() + name );
393  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
394  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), info.baseName() );
395  cmbFileNames->addItem( item );
396  }
397  if ( !cmbFileNames->currentItem() )
398  {
399  cmbFileNames->setCurrentRow( 0 );
400  }
401 
402  if ( cmbFileNames->count() == 0 )
403  {
404  // Create default sample entry.
405  newFunctionFile( QStringLiteral( "default" ) );
406  txtPython->setText( QStringLiteral( "'''\n#Sample custom function file\n"
407  "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''" ).arg( txtPython->text() ) );
408  saveFunctionFile( QStringLiteral( "default" ) );
409  }
410 }
411 
412 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
413 {
414  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
415  if ( !items.isEmpty() )
416  return;
417 
418  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
419  cmbFileNames->insertItem( 0, item );
420  cmbFileNames->setCurrentRow( 0 );
421 
422  QString templatetxt;
423  QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templatetxt );
424  txtPython->setText( templatetxt );
425  saveFunctionFile( fileName );
426 }
427 
428 void QgsExpressionBuilderWidget::btnNewFile_pressed()
429 {
430  bool ok;
431  QString text = QInputDialog::getText( this, tr( "New File" ),
432  tr( "New file name:" ), QLineEdit::Normal,
433  QString(), &ok );
434  if ( ok && !text.isEmpty() )
435  {
436  newFunctionFile( text );
437  }
438 }
439 
440 void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
441 {
442  if ( QMessageBox::question( this, tr( "Remove File" ),
443  tr( "Are you sure you want to remove current functions file?" ),
444  QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
445  return;
446 
447  int currentRow = cmbFileNames->currentRow();
448  QString fileName = cmbFileNames->currentItem()->text();
449  if ( QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
450  {
451  {
452  QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
453  delete itemToRemove;
454  }
455 
456  if ( cmbFileNames->count() > 0 )
457  {
458  cmbFileNames->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
459  loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
460  }
461  else
462  {
463  btnRemoveFile->setEnabled( false );
464  txtPython->clear();
465  }
466  }
467  else
468  {
469  QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
470  }
471 }
472 
473 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
474 {
475  if ( lastitem )
476  {
477  QString filename = lastitem->text();
478  saveFunctionFile( filename );
479  }
480  QString path = mFunctionsPath + QDir::separator() + item->text();
481  loadCodeFromFile( path );
482 }
483 
485 {
486  if ( !path.endsWith( QLatin1String( ".py" ) ) )
487  path.append( ".py" );
488 
489  txtPython->loadScript( path );
490 }
491 
493 {
494  txtPython->setText( code );
495 }
496 
497 void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
498 {
499  // Insert the expression text or replace selected text
500  txtExpressionString->insertText( text );
501  txtExpressionString->setFocus();
502 }
503 
504 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
505 {
506  Q_UNUSED( fieldValues )
507  // This is not maintained and setLayer() should be used instead.
508 }
509 
510 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit, bool forceUsedValues )
511 {
512  // TODO We should really return a error the user of the widget that
513  // the there is no layer set.
514  if ( !mLayer )
515  return;
516 
517  // TODO We should thread this so that we don't hold the user up if the layer is massive.
518 
519  const QgsFields fields = mLayer->fields();
520  int fieldIndex = fields.lookupField( fieldName );
521 
522  if ( fieldIndex < 0 )
523  return;
524 
525  const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
527 
528  QVariantList values;
529  if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
530  {
531  QgsFieldFormatterContext fieldFormatterContext;
532  fieldFormatterContext.setProject( mProject );
533  values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
534  }
535  else
536  {
537  values = qgis::setToList( mLayer->uniqueValues( fieldIndex, countLimit ) );
538  }
539  std::sort( values.begin(), values.end() );
540 
541  mValuesModel->clear();
542  for ( const QVariant &value : std::as_const( values ) )
543  {
544  QString strValue;
545  if ( value.isNull() )
546  strValue = QStringLiteral( "NULL" );
547  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
548  strValue = value.toString();
549  else
550  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
551 
552  QString representedValue = formatter->representValue( mLayer, fieldIndex, setup.config(), QVariant(), value );
553  if ( representedValue != value.toString() )
554  representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
555 
556  QStandardItem *item = new QStandardItem( representedValue );
557  item->setData( strValue );
558  mValuesModel->appendRow( item );
559  }
560 }
561 
562 QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
563 {
564  if ( !function )
565  return QString();
566 
567  QString helpContents = QgsExpression::helpText( function->name() );
568 
569  return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
570 
571 }
572 
573 
574 
576 {
577  return mExpressionValid;
578 }
579 
580 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
581 {
582  mExpressionTreeView->saveToRecent( expressionText(), collection );
583 }
584 
585 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
586 {
587  mExpressionTreeView->loadRecent( collection );
588 }
589 
591 {
592  return mExpressionTreeView;
593 }
594 
595 // this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
597 {
598  mExpressionTreeView->loadUserExpressions();
599 }
600 
601 void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
602 {
603  mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
604 }
605 
607 {
608  mExpressionTreeView->removeFromUserExpressions( label );
609 }
610 
611 
613 {
614  mExpressionPreviewWidget->setGeomCalculator( da );
615 }
616 
618 {
619  return txtExpressionString->text();
620 }
621 
622 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
623 {
624  txtExpressionString->setText( expression );
625 }
626 
628 {
629  return lblExpected->text();
630 }
631 
633 {
634  lblExpected->setText( expected );
635  mExpectedOutputFrame->setVisible( !expected.isNull() );
636 }
637 
639 {
640  mExpressionContext = context;
641  expressionContextUpdated();
642 }
643 
644 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
645 {
646  QString text = expressionText();
647 
648  btnClearEditor->setEnabled( ! txtExpressionString->text().isEmpty() );
649  btnSaveExpression->setEnabled( false );
650 
651  mExpressionPreviewWidget->setExpressionText( text );
652 }
653 
655 {
656  return mExpressionPreviewWidget->parserError();
657 }
658 
660 {
661  mExpressionPreviewWidget->setVisible( isVisible );
662 }
663 
665 {
666  return mExpressionPreviewWidget->evalError();
667 }
668 
670 {
672  return mExpressionTreeView->model();
674 }
675 
677 {
678  return mProject;
679 }
680 
682 {
683  mProject = project;
684  mExpressionTreeView->setProject( project );
685 }
686 
688 {
689  QWidget::showEvent( e );
690  txtExpressionString->setFocus();
691 }
692 
693 void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
694 {
695  clearErrors();
696  for ( const QgsExpression::ParserError &error : errors )
697  {
698  int errorFirstLine = error.firstLine - 1 ;
699  int errorFirstColumn = error.firstColumn - 1;
700  int errorLastColumn = error.lastColumn - 1;
701  int errorLastLine = error.lastLine - 1;
702 
703  // If we have a unknown error we just mark the point that hit the error for now
704  // until we can handle others more.
705  if ( error.errorType == QgsExpression::ParserError::Unknown )
706  {
707  errorFirstLine = errorLastLine;
708  errorFirstColumn = errorLastColumn - 1;
709  }
710  txtExpressionString->fillIndicatorRange( errorFirstLine,
711  errorFirstColumn,
712  errorLastLine,
713  errorLastColumn, error.errorType );
714  }
715 }
716 
717 void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
718 {
719  switch ( inNode->nodeType() )
720  {
721  case QgsExpressionNode::NodeType::ntFunction:
722  {
723  const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
724  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
725  txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
726  int start = inNode->parserFirstColumn - 1;
727  int end = inNode->parserLastColumn - 1;
728  int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
729  txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
730  if ( node->args() )
731  {
732  const QList< QgsExpressionNode * > nodeList = node->args()->list();
733  for ( QgsExpressionNode *n : nodeList )
734  {
735  createMarkers( n );
736  }
737  }
738  break;
739  }
740  case QgsExpressionNode::NodeType::ntLiteral:
741  {
742  break;
743  }
744  case QgsExpressionNode::NodeType::ntUnaryOperator:
745  {
746  const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
747  createMarkers( node->operand() );
748  break;
749  }
750  case QgsExpressionNode::NodeType::ntBinaryOperator:
751  {
752  const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
753  createMarkers( node->opLeft() );
754  createMarkers( node->opRight() );
755  break;
756  }
757  case QgsExpressionNode::NodeType::ntColumnRef:
758  {
759  break;
760  }
761  case QgsExpressionNode::NodeType::ntInOperator:
762  {
763  const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
764  if ( node->list() )
765  {
766  const QList< QgsExpressionNode * > nodeList = node->list()->list();
767  for ( QgsExpressionNode *n : nodeList )
768  {
769  createMarkers( n );
770  }
771  }
772  break;
773  }
774  case QgsExpressionNode::NodeType::ntCondition:
775  {
776  const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
777  const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
778  for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
779  {
780  createMarkers( cond->whenExp() );
781  createMarkers( cond->thenExp() );
782  }
783  if ( node->elseExp() )
784  {
785  createMarkers( node->elseExp() );
786  }
787  break;
788  }
789  case QgsExpressionNode::NodeType::ntIndexOperator:
790  {
791  break;
792  }
793  }
794 }
795 
796 void QgsExpressionBuilderWidget::clearFunctionMarkers()
797 {
798  int lastLine = txtExpressionString->lines() - 1;
799  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
800 }
801 
802 void QgsExpressionBuilderWidget::clearErrors()
803 {
804  int lastLine = txtExpressionString->lines() - 1;
805  // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
806  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
807  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
808  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
809  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
810  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
811 }
812 
813 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
814 {
815  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
816  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
817 }
818 
819 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
820 {
821  // Insert the item text or replace selected text
822  txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
823  txtExpressionString->setFocus();
824 }
825 
826 void QgsExpressionBuilderWidget::operatorButtonClicked()
827 {
828  QPushButton *button = qobject_cast<QPushButton *>( sender() );
829 
830  // Insert the button text or replace selected text
831  txtExpressionString->insertText( ' ' + button->text() + ' ' );
832  txtExpressionString->setFocus();
833 }
834 
836 {
837  QgsExpressionItem *item = mExpressionTreeView->currentItem();
838  // TODO We should really return a error the user of the widget that
839  // the there is no layer set.
840  if ( !mLayer || !item )
841  return;
842 
843  mValueGroupBox->show();
844  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), 10 );
845 }
846 
848 {
849  QgsExpressionItem *item = mExpressionTreeView->currentItem();
850  // TODO We should really return a error the user of the widget that
851  // the there is no layer set.
852  if ( !mLayer || !item )
853  return;
854 
855  mValueGroupBox->show();
856  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), -1 );
857 }
858 
860 {
861  QgsExpressionItem *item = mExpressionTreeView->currentItem();
862  // TODO We should really return a error the user of the widget that
863  // the there is no layer set.
864  if ( !mLayer || !item )
865  return;
866 
867  mValueGroupBox->show();
868  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), 10, true );
869 }
870 
872 {
873  QgsExpressionItem *item = mExpressionTreeView->currentItem();
874  // TODO We should really return a error the user of the widget that
875  // the there is no layer set.
876  if ( !mLayer || !item )
877  return;
878 
879  mValueGroupBox->show();
880  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), -1, true );
881 }
882 
883 void QgsExpressionBuilderWidget::txtPython_textChanged()
884 {
885  lblAutoSave->setText( tr( "Saving…" ) );
886  if ( mAutoSave )
887  {
888  autosave();
889  }
890 }
891 
893 {
894  // Don't auto save if not on function editor that would be silly.
895  if ( tabWidget->currentIndex() != 1 )
896  return;
897 
898  QListWidgetItem *item = cmbFileNames->currentItem();
899  if ( !item )
900  return;
901 
902  QString file = item->text();
903  saveFunctionFile( file );
904  lblAutoSave->setText( QStringLiteral( "Saved" ) );
905  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
906  lblAutoSave->setGraphicsEffect( effect );
907  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
908  anim->setDuration( 2000 );
909  anim->setStartValue( 1.0 );
910  anim->setEndValue( 0.0 );
911  anim->setEasingCurve( QEasingCurve::OutQuad );
912  anim->start( QAbstractAnimation::DeleteWhenStopped );
913 }
914 
916 {
917  const QString expression { this->expressionText() };
918  QgsExpressionStoreDialog dlg { expression, expression, QString( ), mExpressionTreeView->userExpressionLabels() };
919  if ( dlg.exec() == QDialog::DialogCode::Accepted )
920  {
921  mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
922  }
923 }
924 
926 {
927  // Get the item
928  QgsExpressionItem *item = mExpressionTreeView->currentItem();
929  if ( !item )
930  return;
931 
932  // Don't handle remove if we are on a header node or the parent
933  // is not the user group
934  if ( item->getItemType() == QgsExpressionItem::Header ||
935  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
936  return;
937 
938  QgsSettings settings;
939  QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
940  QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
941 
942  if ( dlg.exec() == QDialog::DialogCode::Accepted )
943  {
944  // label has changed removed the old one before adding the new one
945  if ( dlg.isLabelModified() )
946  {
947  mExpressionTreeView->removeFromUserExpressions( item->text() );
948  }
949 
950  mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
951  }
952 }
953 
955 {
956  // Get the item
957  QgsExpressionItem *item = mExpressionTreeView->currentItem();
958 
959  if ( !item )
960  return;
961 
962  // Don't handle remove if we are on a header node or the parent
963  // is not the user group
964  if ( item->getItemType() == QgsExpressionItem::Header ||
965  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
966  return;
967 
968  if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ),
969  tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ),
970  QMessageBox::Yes | QMessageBox::No ) )
971  {
972  mExpressionTreeView->removeFromUserExpressions( item->text() );
973  }
974 
975 }
976 
977 void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
978 {
979  QgsSettings settings;
980  QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
981  QString saveFileName = QFileDialog::getSaveFileName(
982  this,
983  tr( "Export User Expressions" ),
984  lastSaveDir,
985  tr( "User expressions" ) + " (*.json)" );
986 
987  if ( saveFileName.isEmpty() )
988  return;
989 
990  QFileInfo saveFileInfo( saveFileName );
991 
992  if ( saveFileInfo.suffix().isEmpty() )
993  {
994  QString saveFileNameWithSuffix = saveFileName.append( ".json" );
995  saveFileInfo = QFileInfo( saveFileNameWithSuffix );
996  }
997 
998  settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
999 
1000  QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1001  QFile jsonFile( saveFileName );
1002 
1003  if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1004  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1005 
1006  if ( ! jsonFile.write( exportJson.toJson() ) )
1007  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1008  else
1009  jsonFile.close();
1010 }
1011 
1012 void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1013 {
1014  QgsSettings settings;
1015  QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1016  QString loadFileName = QFileDialog::getOpenFileName(
1017  this,
1018  tr( "Import User Expressions" ),
1019  lastImportDir,
1020  tr( "User expressions" ) + " (*.json)" );
1021 
1022  if ( loadFileName.isEmpty() )
1023  return;
1024 
1025  QFileInfo loadFileInfo( loadFileName );
1026 
1027  settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), loadFileInfo.absolutePath(), QgsSettings::App );
1028 
1029  QFile jsonFile( loadFileName );
1030 
1031  if ( !jsonFile.open( QFile::ReadOnly ) )
1032  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1033 
1034  QTextStream jsonStream( &jsonFile );
1035  QString jsonString = jsonFile.readAll();
1036  jsonFile.close();
1037 
1038  QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1039 
1040  if ( importJson.isNull() )
1041  {
1042  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1043  return;
1044  }
1045 
1046  mExpressionTreeView->loadExpressionsFromJson( importJson );
1047 }
1048 
1049 
1050 const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1051 {
1052  return mExpressionTreeView->findExpressions( label );
1053 }
1054 
1055 void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1056 {
1057  if ( state & Qt::ControlModifier )
1058  {
1059  int position = txtExpressionString->positionFromLineIndex( line, index );
1060  long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1061  QgsExpressionFunction *func = QgsExpression::Functions()[fncIndex];
1062  QString help = getFunctionHelp( func );
1063  txtHelpText->setText( help );
1064  }
1065 }
1066 
1067 void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1068 {
1069  clearErrors();
1070 
1071  mExpressionValid = state;
1072  if ( state )
1073  {
1074  createMarkers( mExpressionPreviewWidget->rootNode() );
1075  }
1076  else
1077  {
1078  createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1079  }
1080 }
1081 
1082 QString QgsExpressionBuilderWidget::helpStylesheet() const
1083 {
1084  //start with default QGIS report style
1085  QString style = QgsApplication::reportStyleSheet();
1086 
1087  //add some tweaks
1088  style += " .functionname {color: #0a6099; font-weight: bold;} "
1089  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1090  " td.argument { padding-right: 10px; }";
1091 
1092  return style;
1093 }
1094 
1095 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1096 {
1097  if ( !expressionItem )
1098  return QString();
1099 
1100  QString helpContents = expressionItem->getHelpText();
1101 
1102  // Return the function help that is set for the function if there is one.
1103  if ( helpContents.isEmpty() )
1104  {
1105  QString name = expressionItem->data( Qt::UserRole ).toString();
1106 
1107  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1108  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1109  else
1110  helpContents = QgsExpression::helpText( name );
1111  }
1112 
1113  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1114 }
1115 
1116 
1117 // *************
1118 // Menu provider
1119 
1120 QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1121 {
1122  QMenu *menu = nullptr;
1123  QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1124  if ( item->getItemType() == QgsExpressionItem::Field && layer )
1125  {
1126  menu = new QMenu( mExpressionBuilderWidget );
1127  menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1128  menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1129 
1130  if ( formatterCanProvideAvailableValues( layer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) )
1131  {
1132  menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1133  menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1134  }
1135  }
1136  return menu;
1137 }
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 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 init(const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize without any layer.
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.
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 setExpressionPreviewVisible(bool isVisible)
Sets whether the expression preview is visible.
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 initWithLayer(QgsVectorLayer *layer, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with a layer.
void updateFunctionFileList(const QString &path)
Updates the list of function files found at the given path.
void showEvent(QShowEvent *e) override
void initWithFields(const QgsFields &fields, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with given fields without any layer.
void setExpectedOutputFormat(const QString &expected)
The set expected format string.
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.
Q_DECL_DEPRECATED void saveToUserExpressions(const QString &label, const QString &expression, const QString &helpText)
Stores the user expression with given label and helpText.
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...
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:101
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:1730
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1729
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1173
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.