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