QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgisexpressionbuilderwidget.cpp - A generic expression 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"
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"
52
53
54
55bool 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
66 return ( formatter->flags() & QgsFieldFormatter::CanProvideAvailableValues );
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
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 Besides its normal arguments, the function may specify the following arguments in its signature\n\
213 Those will not need to be specified when calling the function, but will be automatically injected \n\
214\n\
215 : param feature: The current feature\n\
216 : param parent: The QgsExpression object\n\
217 : param context: ``QgsExpressionContext`` object, that gives access to various additional information like\n\
218 expression variables. E.g. ``context.variable( 'layer_id' )``\n\
219 : returns: The result of the expression.\n\
220\n\
221\n\
222\n\
223 The @qgsfunction decorator accepts the following arguments:\n\
224\n\
225\n\
226 : param group: The name of the group under which this expression function will\n\
227 be listed.\n\
228 : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
229 If False, the result will always be NULL as soon as any parameter is NULL.\n\
230 Defaults to False.\n\
231 : param usesgeometry : Set this to True if your function requires access to\n\
232 feature.geometry(). Defaults to False.\n\
233 : param referenced_columns: An array of attribute names that are required to run\n\
234 this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
235 : param params_as_list : Set this to True to pass the function parameters as a list. Can be used to mimic \n\
236 behavior before 3.32, when args was not \"auto\". Defaults to False.\n\
237\"\"\"" ) );
238}
239
240
242{
243 QgsSettings settings;
244 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
245 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
246 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
247 delete mExpressionTreeMenuProvider;
248}
249
250void QgsExpressionBuilderWidget::init( const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
251{
252 setExpressionContext( context );
253
254 if ( flags.testFlag( LoadRecent ) )
255 mExpressionTreeView->loadRecent( recentCollection );
256
257 if ( flags.testFlag( LoadUserExpressions ) )
258 mExpressionTreeView->loadUserExpressions();
259}
260
262{
263 init( context, recentCollection, flags );
264 setLayer( layer );
265}
266
267void QgsExpressionBuilderWidget::initWithFields( const QgsFields &fields, const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
268{
269 init( context, recentCollection, flags );
270 mExpressionTreeView->loadFieldNames( fields );
271}
272
273
275{
276 mLayer = layer;
277 mExpressionTreeView->setLayer( mLayer );
278 mExpressionPreviewWidget->setLayer( mLayer );
279
280 //TODO - remove existing layer scope from context
281
282 if ( mLayer )
283 {
284 mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
285 expressionContextUpdated();
286 txtExpressionString->setFields( mLayer->fields() );
287 }
288}
289
290void QgsExpressionBuilderWidget::expressionContextUpdated()
291{
292 txtExpressionString->setExpressionContext( mExpressionContext );
293 mExpressionTreeView->setExpressionContext( mExpressionContext );
294 mExpressionPreviewWidget->setExpressionContext( mExpressionContext );
295}
296
298{
299 return mLayer;
300}
301
302void QgsExpressionBuilderWidget::expressionTreeItemChanged( QgsExpressionItem *item )
303{
304 txtSearchEditValues->clear();
305
306 if ( !item )
307 return;
308
309 bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
310 if ( isField )
311 {
312 mValuesModel->clear();
313
314 cbxValuesInUse->setVisible( formatterCanProvideAvailableValues( mLayer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) );
315 cbxValuesInUse->setChecked( false );
316 }
317 mValueGroupBox->setVisible( isField );
318
319 mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
320
321 // Show the help for the current item.
322 QString help = loadFunctionHelp( item );
323 txtHelpText->setText( help );
324
325 bool isUserExpression = item->parent() && item->parent()->text() == mUserExpressionsGroupName;
326
327 btnRemoveExpression->setEnabled( isUserExpression );
328 btnEditExpression->setEnabled( isUserExpression );
329}
330
331void QgsExpressionBuilderWidget::btnRun_pressed()
332{
333 if ( !cmbFileNames->currentItem() )
334 return;
335
336 QString file = cmbFileNames->currentItem()->text();
337 saveFunctionFile( file );
338 runPythonCode( txtPython->text() );
339}
340
341void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
342{
344 {
345 QString pythontext = code;
346 QgsPythonRunner::run( pythontext );
347 }
348 mExpressionTreeView->refresh();
349}
350
351QgsVectorLayer *QgsExpressionBuilderWidget::contextLayer( const QgsExpressionItem *item ) const
352{
353 QgsVectorLayer *layer = nullptr;
355 {
356 layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( item->data( QgsExpressionItem::LAYER_ID_ROLE ).toString() ) );
357 }
358 else
359 {
360 layer = mLayer;
361 }
362 return layer;
363}
364
365
367{
368 QDir myDir( mFunctionsPath );
369 if ( !myDir.exists() )
370 {
371 myDir.mkpath( mFunctionsPath );
372 }
373
374 if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
375 {
376 fileName.append( ".py" );
377 }
378
379 fileName = mFunctionsPath + QDir::separator() + fileName;
380 QFile myFile( fileName );
381 if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
382 {
383 QTextStream myFileStream( &myFile );
384#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
385 myFileStream.setCodec( "UTF-8" );
386#endif
387 myFileStream << txtPython->text() << Qt::endl;
388 myFile.close();
389 }
390}
391
393{
394 mFunctionsPath = path;
395 QDir dir( path );
396 dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
397 QStringList files = dir.entryList( QDir::Files );
398 cmbFileNames->clear();
399 const auto constFiles = files;
400 for ( const QString &name : constFiles )
401 {
402 QFileInfo info( mFunctionsPath + QDir::separator() + name );
403 if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
404 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), info.baseName() );
405 cmbFileNames->addItem( item );
406 }
407 if ( !cmbFileNames->currentItem() )
408 {
409 cmbFileNames->setCurrentRow( 0 );
410 }
411
412 if ( cmbFileNames->count() == 0 )
413 {
414 // Create default sample entry.
415 newFunctionFile( QStringLiteral( "default" ) );
416 txtPython->setText( QStringLiteral( "'''\n#Sample custom function file\n"
417 "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''" ).arg( txtPython->text() ) );
418 saveFunctionFile( QStringLiteral( "default" ) );
419 }
420}
421
422void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
423{
424 QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
425 if ( !items.isEmpty() )
426 return;
427
428 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
429 cmbFileNames->insertItem( 0, item );
430 cmbFileNames->setCurrentRow( 0 );
431
432 QString templatetxt;
433 QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templatetxt );
434 txtPython->setText( templatetxt );
435 saveFunctionFile( fileName );
436}
437
438void QgsExpressionBuilderWidget::btnNewFile_pressed()
439{
440 bool ok;
441 QString text = QInputDialog::getText( this, tr( "New File" ),
442 tr( "New file name:" ), QLineEdit::Normal,
443 QString(), &ok );
444 if ( ok && !text.isEmpty() )
445 {
446 newFunctionFile( text );
447 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
448 }
449}
450
451void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
452{
453 if ( QMessageBox::question( this, tr( "Remove File" ),
454 tr( "Are you sure you want to remove current functions file?" ),
455 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
456 return;
457
458 int currentRow = cmbFileNames->currentRow();
459 QString fileName = cmbFileNames->currentItem()->text();
460 if ( QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
461 {
462 {
463 QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
464 delete itemToRemove;
465 }
466
467 if ( cmbFileNames->count() > 0 )
468 {
469 whileBlocking( cmbFileNames )->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
470 loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
471 }
472 else
473 {
474 btnRemoveFile->setEnabled( false );
475 txtPython->clear();
476 }
477 }
478 else
479 {
480 QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
481 }
482}
483
484void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
485{
486 if ( lastitem )
487 {
488 QString filename = lastitem->text();
489 saveFunctionFile( filename );
490 }
491 QString path = mFunctionsPath + QDir::separator() + item->text();
492 loadCodeFromFile( path );
493}
494
496{
497 if ( !path.endsWith( QLatin1String( ".py" ) ) )
498 path.append( ".py" );
499
500 txtPython->loadScript( path );
501}
502
504{
505 txtPython->setText( code );
506}
507
508void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
509{
510 // Insert the expression text or replace selected text
511 txtExpressionString->insertText( text );
512 txtExpressionString->setFocus();
513}
514
515void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
516{
517 Q_UNUSED( fieldValues )
518 // This is not maintained and setLayer() should be used instead.
519}
520
521void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, QgsVectorLayer *layer, int countLimit, bool forceUsedValues )
522{
523 // TODO We should really return a error the user of the widget that
524 // the there is no layer set.
525 if ( !layer )
526 return;
527
528 // TODO We should thread this so that we don't hold the user up if the layer is massive.
529
530 const QgsFields fields = layer->fields();
531 int fieldIndex = fields.lookupField( fieldName );
532
533 if ( fieldIndex < 0 )
534 return;
535
536 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
538
539 QVariantList values;
540 if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
541 {
542 QgsFieldFormatterContext fieldFormatterContext;
543 fieldFormatterContext.setProject( mProject );
544 values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
545 }
546 else
547 {
548 values = qgis::setToList( layer->uniqueValues( fieldIndex, countLimit ) );
549 }
550 std::sort( values.begin(), values.end() );
551
552 mValuesModel->clear();
553 for ( const QVariant &value : std::as_const( values ) )
554 {
555 QString strValue;
556 bool forceRepresentedValue = false;
557 if ( QgsVariantUtils::isNull( value ) )
558 strValue = QStringLiteral( "NULL" );
559 else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || value.type() == QVariant::Bool )
560 strValue = value.toString();
561 else if ( value.type() == QVariant::StringList )
562 {
563 QString result;
564 const QStringList strList = value.toStringList();
565 for ( QString str : strList )
566 {
567 if ( !result.isEmpty() )
568 result.append( QStringLiteral( ", " ) );
569
570 result.append( '\'' + str.replace( '\'', QLatin1String( "''" ) ) + '\'' );
571 }
572 strValue = QStringLiteral( "array(%1)" ).arg( result );
573 forceRepresentedValue = true;
574 }
575 else if ( value.type() == QVariant::List )
576 {
577 QString result;
578 const QList list = value.toList();
579 for ( const QVariant &item : list )
580 {
581 if ( !result.isEmpty() )
582 result.append( QStringLiteral( ", " ) );
583
584 result.append( item.toString() );
585 }
586 strValue = QStringLiteral( "array(%1)" ).arg( result );
587 forceRepresentedValue = true;
588 }
589 else
590 strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
591
592 QString representedValue = formatter->representValue( layer, fieldIndex, setup.config(), QVariant(), value );
593 if ( forceRepresentedValue || representedValue != value.toString() )
594 representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
595
596 QStandardItem *item = new QStandardItem( representedValue );
597 item->setData( strValue );
598 mValuesModel->appendRow( item );
599 }
600}
601
602QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
603{
604 if ( !function )
605 return QString();
606
607 QString helpContents = QgsExpression::helpText( function->name() );
608
609 return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
610
611}
612
613
614
616{
617 return mExpressionValid;
618}
619
620void QgsExpressionBuilderWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant> > &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
621{
622 mExpressionPreviewWidget->setCustomPreviewGenerator( label, choices, previewContextGenerator );
623}
624
625void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
626{
627 mExpressionTreeView->saveToRecent( expressionText(), collection );
628}
629
630void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
631{
632 mExpressionTreeView->loadRecent( collection );
633}
634
636{
637 return mExpressionTreeView;
638}
639
640// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
642{
643 mExpressionTreeView->loadUserExpressions();
644}
645
646void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
647{
648 mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
649}
650
652{
653 mExpressionTreeView->removeFromUserExpressions( label );
654}
655
656
658{
659 mExpressionPreviewWidget->setGeomCalculator( da );
660}
661
663{
664 return txtExpressionString->text();
665}
666
667void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
668{
669 txtExpressionString->setText( expression );
670}
671
673{
674 return lblExpected->text();
675}
676
678{
679 lblExpected->setText( expected );
680 mExpectedOutputFrame->setVisible( !expected.isNull() );
681}
682
684{
685 mExpressionContext = context;
686 expressionContextUpdated();
687}
688
689void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
690{
691 QString text = expressionText();
692
693 btnClearEditor->setEnabled( ! txtExpressionString->text().isEmpty() );
694 btnSaveExpression->setEnabled( false );
695
696 mExpressionPreviewWidget->setExpressionText( text );
697}
698
700{
701 return mExpressionPreviewWidget->parserError();
702}
703
705{
706 mExpressionPreviewWidget->setVisible( isVisible );
707}
708
710{
711 return mExpressionPreviewWidget->evalError();
712}
713
715{
717 return mExpressionTreeView->model();
719}
720
722{
723 return mProject;
724}
725
727{
728 mProject = project;
729 mExpressionTreeView->setProject( project );
730}
731
733{
734 QWidget::showEvent( e );
735 txtExpressionString->setFocus();
736}
737
738void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
739{
740 clearErrors();
741 for ( const QgsExpression::ParserError &error : errors )
742 {
743 int errorFirstLine = error.firstLine - 1 ;
744 int errorFirstColumn = error.firstColumn - 1;
745 int errorLastColumn = error.lastColumn - 1;
746 int errorLastLine = error.lastLine - 1;
747
748 // If we have a unknown error we just mark the point that hit the error for now
749 // until we can handle others more.
750 if ( error.errorType == QgsExpression::ParserError::Unknown )
751 {
752 errorFirstLine = errorLastLine;
753 errorFirstColumn = errorLastColumn - 1;
754 }
755 txtExpressionString->fillIndicatorRange( errorFirstLine,
756 errorFirstColumn,
757 errorLastLine,
758 errorLastColumn, error.errorType );
759 }
760}
761
762void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
763{
764 switch ( inNode->nodeType() )
765 {
766 case QgsExpressionNode::NodeType::ntFunction:
767 {
768 const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
769 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
770 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
771 int start = inNode->parserFirstColumn - 1;
772 int end = inNode->parserLastColumn - 1;
773 int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
774 txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
775 if ( node->args() )
776 {
777 const QList< QgsExpressionNode * > nodeList = node->args()->list();
778 for ( QgsExpressionNode *n : nodeList )
779 {
780 createMarkers( n );
781 }
782 }
783 break;
784 }
785 case QgsExpressionNode::NodeType::ntLiteral:
786 {
787 break;
788 }
789 case QgsExpressionNode::NodeType::ntUnaryOperator:
790 {
791 const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
792 createMarkers( node->operand() );
793 break;
794 }
795 case QgsExpressionNode::NodeType::ntBetweenOperator:
796 {
797 const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
798 createMarkers( node->lowerBound() );
799 createMarkers( node->higherBound() );
800 break;
801 }
802 case QgsExpressionNode::NodeType::ntBinaryOperator:
803 {
804 const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
805 createMarkers( node->opLeft() );
806 createMarkers( node->opRight() );
807 break;
808 }
809 case QgsExpressionNode::NodeType::ntColumnRef:
810 {
811 break;
812 }
813 case QgsExpressionNode::NodeType::ntInOperator:
814 {
815 const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
816 if ( node->list() )
817 {
818 const QList< QgsExpressionNode * > nodeList = node->list()->list();
819 for ( QgsExpressionNode *n : nodeList )
820 {
821 createMarkers( n );
822 }
823 }
824 break;
825 }
826 case QgsExpressionNode::NodeType::ntCondition:
827 {
828 const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
829 const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
830 for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
831 {
832 createMarkers( cond->whenExp() );
833 createMarkers( cond->thenExp() );
834 }
835 if ( node->elseExp() )
836 {
837 createMarkers( node->elseExp() );
838 }
839 break;
840 }
841 case QgsExpressionNode::NodeType::ntIndexOperator:
842 {
843 break;
844 }
845 }
846}
847
848void QgsExpressionBuilderWidget::clearFunctionMarkers()
849{
850 int lastLine = txtExpressionString->lines() - 1;
851 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
852}
853
854void QgsExpressionBuilderWidget::clearErrors()
855{
856 int lastLine = txtExpressionString->lines() - 1;
857 // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
858 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
859 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
860 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
861 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
862 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
863}
864
865void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
866{
867 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
868 mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
869}
870
871void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
872{
873 // Insert the item text or replace selected text
874 txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
875 txtExpressionString->setFocus();
876}
877
878void QgsExpressionBuilderWidget::operatorButtonClicked()
879{
880 QPushButton *button = qobject_cast<QPushButton *>( sender() );
881
882 // Insert the button text or replace selected text
883 txtExpressionString->insertText( ' ' + button->text() + ' ' );
884 txtExpressionString->setFocus();
885}
886
888{
889 QgsExpressionItem *item = mExpressionTreeView->currentItem();
890 if ( ! item )
891 {
892 return;
893 }
894
895 QgsVectorLayer *layer { contextLayer( item ) };
896 // TODO We should really return a error the user of the widget that
897 // the there is no layer set.
898 if ( !layer )
899 {
900 return;
901 }
902
903 mValueGroupBox->show();
904 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
905}
906
908{
909 QgsExpressionItem *item = mExpressionTreeView->currentItem();
910 if ( ! item )
911 {
912 return;
913 }
914
915 QgsVectorLayer *layer { contextLayer( item ) };
916 // TODO We should really return a error the user of the widget that
917 // the there is no layer set.
918 if ( !layer )
919 {
920 return;
921 }
922
923 mValueGroupBox->show();
924 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
925}
926
928{
929 QgsExpressionItem *item = mExpressionTreeView->currentItem();
930 if ( ! item )
931 {
932 return;
933 }
934
935 QgsVectorLayer *layer { contextLayer( item ) };
936 // TODO We should really return a error the user of the widget that
937 // the there is no layer set.
938 if ( !layer )
939 {
940 return;
941 }
942
943 mValueGroupBox->show();
944 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
945}
946
948{
949 QgsExpressionItem *item = mExpressionTreeView->currentItem();
950 if ( ! item )
951 {
952 return;
953 }
954
955 QgsVectorLayer *layer { contextLayer( item ) };
956 // TODO We should really return a error the user of the widget that
957 // the there is no layer set.
958 if ( !layer )
959 {
960 return;
961 }
962
963 mValueGroupBox->show();
964 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
965}
966
967void QgsExpressionBuilderWidget::txtPython_textChanged()
968{
969 lblAutoSave->setText( tr( "Saving…" ) );
970 if ( mAutoSave )
971 {
972 autosave();
973 }
974}
975
977{
978 // Don't auto save if not on function editor that would be silly.
979 if ( tabWidget->currentIndex() != 1 )
980 return;
981
982 QListWidgetItem *item = cmbFileNames->currentItem();
983 if ( !item )
984 return;
985
986 QString file = item->text();
987 saveFunctionFile( file );
988 lblAutoSave->setText( QStringLiteral( "Saved" ) );
989 QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
990 lblAutoSave->setGraphicsEffect( effect );
991 QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
992 anim->setDuration( 2000 );
993 anim->setStartValue( 1.0 );
994 anim->setEndValue( 0.0 );
995 anim->setEasingCurve( QEasingCurve::OutQuad );
996 anim->start( QAbstractAnimation::DeleteWhenStopped );
997}
998
1000{
1001 const QString expression { this->expressionText() };
1002 QgsExpressionStoreDialog dlg { expression, expression, QString( ), mExpressionTreeView->userExpressionLabels() };
1003 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1004 {
1005 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1006 }
1007}
1008
1010{
1011 // Get the item
1012 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1013 if ( !item )
1014 return;
1015
1016 // Don't handle remove if we are on a header node or the parent
1017 // is not the user group
1018 if ( item->getItemType() == QgsExpressionItem::Header ||
1019 ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1020 return;
1021
1022 QgsSettings settings;
1023 QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1024 QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1025
1026 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1027 {
1028 // label has changed removed the old one before adding the new one
1029 if ( dlg.isLabelModified() )
1030 {
1031 mExpressionTreeView->removeFromUserExpressions( item->text() );
1032 }
1033
1034 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1035 }
1036}
1037
1039{
1040 // Get the item
1041 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1042
1043 if ( !item )
1044 return;
1045
1046 // Don't handle remove if we are on a header node or the parent
1047 // is not the user group
1048 if ( item->getItemType() == QgsExpressionItem::Header ||
1049 ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1050 return;
1051
1052 if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ),
1053 tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ),
1054 QMessageBox::Yes | QMessageBox::No ) )
1055 {
1056 mExpressionTreeView->removeFromUserExpressions( item->text() );
1057 }
1058
1059}
1060
1061void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1062{
1063 QgsSettings settings;
1064 QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1065 QString saveFileName = QFileDialog::getSaveFileName(
1066 this,
1067 tr( "Export User Expressions" ),
1068 lastSaveDir,
1069 tr( "User expressions" ) + " (*.json)" );
1070
1071 // return dialog focus on Mac
1072 activateWindow();
1073 raise();
1074 if ( saveFileName.isEmpty() )
1075 return;
1076
1077 QFileInfo saveFileInfo( saveFileName );
1078
1079 if ( saveFileInfo.suffix().isEmpty() )
1080 {
1081 QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1082 saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1083 }
1084
1085 settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
1086
1087 QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1088 QFile jsonFile( saveFileName );
1089
1090 if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1091 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1092
1093 if ( ! jsonFile.write( exportJson.toJson() ) )
1094 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1095 else
1096 jsonFile.close();
1097}
1098
1099void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1100{
1101 QgsSettings settings;
1102 QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1103 QString loadFileName = QFileDialog::getOpenFileName(
1104 this,
1105 tr( "Import User Expressions" ),
1106 lastImportDir,
1107 tr( "User expressions" ) + " (*.json)" );
1108
1109 if ( loadFileName.isEmpty() )
1110 return;
1111
1112 QFileInfo loadFileInfo( loadFileName );
1113
1114 settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), loadFileInfo.absolutePath(), QgsSettings::App );
1115
1116 QFile jsonFile( loadFileName );
1117
1118 if ( !jsonFile.open( QFile::ReadOnly ) )
1119 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1120
1121 QTextStream jsonStream( &jsonFile );
1122 QString jsonString = jsonFile.readAll();
1123 jsonFile.close();
1124
1125 QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1126
1127 if ( importJson.isNull() )
1128 {
1129 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1130 return;
1131 }
1132
1133 mExpressionTreeView->loadExpressionsFromJson( importJson );
1134}
1135
1136
1137const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1138{
1139 return mExpressionTreeView->findExpressions( label );
1140}
1141
1142void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1143{
1144 if ( state & Qt::ControlModifier )
1145 {
1146 int position = txtExpressionString->positionFromLineIndex( line, index );
1147 long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1149 QString help = getFunctionHelp( func );
1150 txtHelpText->setText( help );
1151 }
1152}
1153
1154void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1155{
1156 clearErrors();
1157
1158 mExpressionValid = state;
1159 if ( state )
1160 {
1161 createMarkers( mExpressionPreviewWidget->rootNode() );
1162 }
1163 else
1164 {
1165 createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1166 }
1167}
1168
1169QString QgsExpressionBuilderWidget::helpStylesheet() const
1170{
1171 //start with default QGIS report style
1172 QString style = QgsApplication::reportStyleSheet();
1173
1174 //add some tweaks
1175 style += " .functionname {color: #0a6099; font-weight: bold;} "
1176 " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1177 " td.argument { padding-right: 10px; }";
1178
1179 return style;
1180}
1181
1182QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1183{
1184 if ( !expressionItem )
1185 return QString();
1186
1187 QString helpContents = expressionItem->getHelpText();
1188
1189 // Return the function help that is set for the function if there is one.
1190 if ( helpContents.isEmpty() )
1191 {
1192 QString name = expressionItem->data( Qt::UserRole ).toString();
1193
1194 if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1195 helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1196 else
1197 helpContents = QgsExpression::helpText( name );
1198 }
1199
1200 return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1201}
1202
1203
1204// *************
1205// Menu provider
1206
1207QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1208{
1209 QMenu *menu = nullptr;
1210 QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1211 if ( item->getItemType() == QgsExpressionItem::Field && layer )
1212 {
1213 menu = new QMenu( mExpressionBuilderWidget );
1214 menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1215 menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1216
1217 if ( formatterCanProvideAvailableValues( layer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) )
1218 {
1219 menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1220 menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1221 }
1222 }
1223 return menu;
1224}
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 setCustomPreviewGenerator(const QString &label, const QList< QPair< QString, QVariant > > &choices, const std::function< QgsExpressionContext(const QVariant &) > &previewContextGenerator)
Sets the widget to run using a custom preview generator.
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.
QString name() const
The name of the function.
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 * opLeft() const
Returns the node to the left of the operator.
QgsExpressionNode * opRight() const
Returns the node to the right 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 PRIVATE 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.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:714
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:359
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
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:64
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.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL 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.
CORE_EXPORT const QStringList files(const QString &zip)
Returns the list of files within a zip file.
#define str(x)
Definition: qgis.cpp:38
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:5776
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:5775
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111
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.