QGIS API Documentation  3.27.0-Master (bef583a8ef)
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::ntBetweenOperator:
795  {
796  const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
797  createMarkers( node->lowerBound() );
798  createMarkers( node->higherBound() );
799  break;
800  }
801  case QgsExpressionNode::NodeType::ntBinaryOperator:
802  {
803  const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
804  createMarkers( node->opLeft() );
805  createMarkers( node->opRight() );
806  break;
807  }
808  case QgsExpressionNode::NodeType::ntColumnRef:
809  {
810  break;
811  }
812  case QgsExpressionNode::NodeType::ntInOperator:
813  {
814  const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
815  if ( node->list() )
816  {
817  const QList< QgsExpressionNode * > nodeList = node->list()->list();
818  for ( QgsExpressionNode *n : nodeList )
819  {
820  createMarkers( n );
821  }
822  }
823  break;
824  }
825  case QgsExpressionNode::NodeType::ntCondition:
826  {
827  const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
828  const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
829  for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
830  {
831  createMarkers( cond->whenExp() );
832  createMarkers( cond->thenExp() );
833  }
834  if ( node->elseExp() )
835  {
836  createMarkers( node->elseExp() );
837  }
838  break;
839  }
840  case QgsExpressionNode::NodeType::ntIndexOperator:
841  {
842  break;
843  }
844  }
845 }
846 
847 void QgsExpressionBuilderWidget::clearFunctionMarkers()
848 {
849  int lastLine = txtExpressionString->lines() - 1;
850  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
851 }
852 
853 void QgsExpressionBuilderWidget::clearErrors()
854 {
855  int lastLine = txtExpressionString->lines() - 1;
856  // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
857  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
858  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
859  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
860  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
861  txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
862 }
863 
864 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
865 {
866  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
867  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
868 }
869 
870 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
871 {
872  // Insert the item text or replace selected text
873  txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
874  txtExpressionString->setFocus();
875 }
876 
877 void QgsExpressionBuilderWidget::operatorButtonClicked()
878 {
879  QPushButton *button = qobject_cast<QPushButton *>( sender() );
880 
881  // Insert the button text or replace selected text
882  txtExpressionString->insertText( ' ' + button->text() + ' ' );
883  txtExpressionString->setFocus();
884 }
885 
887 {
888  QgsExpressionItem *item = mExpressionTreeView->currentItem();
889  if ( ! item )
890  {
891  return;
892  }
893 
894  QgsVectorLayer *layer { contextLayer( item ) };
895  // TODO We should really return a error the user of the widget that
896  // the there is no layer set.
897  if ( !layer )
898  {
899  return;
900  }
901 
902  mValueGroupBox->show();
903  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
904 }
905 
907 {
908  QgsExpressionItem *item = mExpressionTreeView->currentItem();
909  if ( ! item )
910  {
911  return;
912  }
913 
914  QgsVectorLayer *layer { contextLayer( item ) };
915  // TODO We should really return a error the user of the widget that
916  // the there is no layer set.
917  if ( !layer )
918  {
919  return;
920  }
921 
922  mValueGroupBox->show();
923  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
924 }
925 
927 {
928  QgsExpressionItem *item = mExpressionTreeView->currentItem();
929  if ( ! item )
930  {
931  return;
932  }
933 
934  QgsVectorLayer *layer { contextLayer( item ) };
935  // TODO We should really return a error the user of the widget that
936  // the there is no layer set.
937  if ( !layer )
938  {
939  return;
940  }
941 
942  mValueGroupBox->show();
943  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
944 }
945 
947 {
948  QgsExpressionItem *item = mExpressionTreeView->currentItem();
949  if ( ! item )
950  {
951  return;
952  }
953 
954  QgsVectorLayer *layer { contextLayer( item ) };
955  // TODO We should really return a error the user of the widget that
956  // the there is no layer set.
957  if ( !layer )
958  {
959  return;
960  }
961 
962  mValueGroupBox->show();
963  fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
964 }
965 
966 void QgsExpressionBuilderWidget::txtPython_textChanged()
967 {
968  lblAutoSave->setText( tr( "Saving…" ) );
969  if ( mAutoSave )
970  {
971  autosave();
972  }
973 }
974 
976 {
977  // Don't auto save if not on function editor that would be silly.
978  if ( tabWidget->currentIndex() != 1 )
979  return;
980 
981  QListWidgetItem *item = cmbFileNames->currentItem();
982  if ( !item )
983  return;
984 
985  QString file = item->text();
986  saveFunctionFile( file );
987  lblAutoSave->setText( QStringLiteral( "Saved" ) );
988  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
989  lblAutoSave->setGraphicsEffect( effect );
990  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
991  anim->setDuration( 2000 );
992  anim->setStartValue( 1.0 );
993  anim->setEndValue( 0.0 );
994  anim->setEasingCurve( QEasingCurve::OutQuad );
995  anim->start( QAbstractAnimation::DeleteWhenStopped );
996 }
997 
999 {
1000  const QString expression { this->expressionText() };
1001  QgsExpressionStoreDialog dlg { expression, expression, QString( ), mExpressionTreeView->userExpressionLabels() };
1002  if ( dlg.exec() == QDialog::DialogCode::Accepted )
1003  {
1004  mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1005  }
1006 }
1007 
1009 {
1010  // Get the item
1011  QgsExpressionItem *item = mExpressionTreeView->currentItem();
1012  if ( !item )
1013  return;
1014 
1015  // Don't handle remove if we are on a header node or the parent
1016  // is not the user group
1017  if ( item->getItemType() == QgsExpressionItem::Header ||
1018  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1019  return;
1020 
1021  QgsSettings settings;
1022  QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1023  QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1024 
1025  if ( dlg.exec() == QDialog::DialogCode::Accepted )
1026  {
1027  // label has changed removed the old one before adding the new one
1028  if ( dlg.isLabelModified() )
1029  {
1030  mExpressionTreeView->removeFromUserExpressions( item->text() );
1031  }
1032 
1033  mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1034  }
1035 }
1036 
1038 {
1039  // Get the item
1040  QgsExpressionItem *item = mExpressionTreeView->currentItem();
1041 
1042  if ( !item )
1043  return;
1044 
1045  // Don't handle remove if we are on a header node or the parent
1046  // is not the user group
1047  if ( item->getItemType() == QgsExpressionItem::Header ||
1048  ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1049  return;
1050 
1051  if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ),
1052  tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ),
1053  QMessageBox::Yes | QMessageBox::No ) )
1054  {
1055  mExpressionTreeView->removeFromUserExpressions( item->text() );
1056  }
1057 
1058 }
1059 
1060 void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1061 {
1062  QgsSettings settings;
1063  QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1064  QString saveFileName = QFileDialog::getSaveFileName(
1065  this,
1066  tr( "Export User Expressions" ),
1067  lastSaveDir,
1068  tr( "User expressions" ) + " (*.json)" );
1069 
1070  if ( saveFileName.isEmpty() )
1071  return;
1072 
1073  QFileInfo saveFileInfo( saveFileName );
1074 
1075  if ( saveFileInfo.suffix().isEmpty() )
1076  {
1077  QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1078  saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1079  }
1080 
1081  settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
1082 
1083  QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1084  QFile jsonFile( saveFileName );
1085 
1086  if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1087  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1088 
1089  if ( ! jsonFile.write( exportJson.toJson() ) )
1090  QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1091  else
1092  jsonFile.close();
1093 }
1094 
1095 void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1096 {
1097  QgsSettings settings;
1098  QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1099  QString loadFileName = QFileDialog::getOpenFileName(
1100  this,
1101  tr( "Import User Expressions" ),
1102  lastImportDir,
1103  tr( "User expressions" ) + " (*.json)" );
1104 
1105  if ( loadFileName.isEmpty() )
1106  return;
1107 
1108  QFileInfo loadFileInfo( loadFileName );
1109 
1110  settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), loadFileInfo.absolutePath(), QgsSettings::App );
1111 
1112  QFile jsonFile( loadFileName );
1113 
1114  if ( !jsonFile.open( QFile::ReadOnly ) )
1115  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1116 
1117  QTextStream jsonStream( &jsonFile );
1118  QString jsonString = jsonFile.readAll();
1119  jsonFile.close();
1120 
1121  QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1122 
1123  if ( importJson.isNull() )
1124  {
1125  QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1126  return;
1127  }
1128 
1129  mExpressionTreeView->loadExpressionsFromJson( importJson );
1130 }
1131 
1132 
1133 const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1134 {
1135  return mExpressionTreeView->findExpressions( label );
1136 }
1137 
1138 void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1139 {
1140  if ( state & Qt::ControlModifier )
1141  {
1142  int position = txtExpressionString->positionFromLineIndex( line, index );
1143  long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1144  QgsExpressionFunction *func = QgsExpression::Functions()[fncIndex];
1145  QString help = getFunctionHelp( func );
1146  txtHelpText->setText( help );
1147  }
1148 }
1149 
1150 void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1151 {
1152  clearErrors();
1153 
1154  mExpressionValid = state;
1155  if ( state )
1156  {
1157  createMarkers( mExpressionPreviewWidget->rootNode() );
1158  }
1159  else
1160  {
1161  createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1162  }
1163 }
1164 
1165 QString QgsExpressionBuilderWidget::helpStylesheet() const
1166 {
1167  //start with default QGIS report style
1168  QString style = QgsApplication::reportStyleSheet();
1169 
1170  //add some tweaks
1171  style += " .functionname {color: #0a6099; font-weight: bold;} "
1172  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1173  " td.argument { padding-right: 10px; }";
1174 
1175  return style;
1176 }
1177 
1178 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1179 {
1180  if ( !expressionItem )
1181  return QString();
1182 
1183  QString helpContents = expressionItem->getHelpText();
1184 
1185  // Return the function help that is set for the function if there is one.
1186  if ( helpContents.isEmpty() )
1187  {
1188  QString name = expressionItem->data( Qt::UserRole ).toString();
1189 
1190  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1191  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1192  else
1193  helpContents = QgsExpression::helpText( name );
1194  }
1195 
1196  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1197 }
1198 
1199 
1200 // *************
1201 // Menu provider
1202 
1203 QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1204 {
1205  QMenu *menu = nullptr;
1206  QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1207  if ( item->getItemType() == QgsExpressionItem::Field && layer )
1208  {
1209  menu = new QMenu( mExpressionBuilderWidget );
1210  menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1211  menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1212 
1213  if ( formatterCanProvideAvailableValues( layer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) )
1214  {
1215  menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1216  menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1217  }
1218  }
1219  return menu;
1220 }
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.
SQL-like BETWEEN and NOT BETWEEN predicates.
QgsExpressionNode * lowerBound() const
Returns the lower bound expression node of the range.
QgsExpressionNode * higherBound() const
Returns the higher bound expression node of the range.
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:104
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:479
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:2815
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2814
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2186
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.