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