QGIS API Documentation 4.1.0-Master (ca2ac17535b)
Loading...
Searching...
No Matches
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
18
19#include "qgsapplication.h"
20#include "qgscodeeditorwidget.h"
21#include "qgsexpression.h"
28#include "qgsfeature.h"
29#include "qgsfieldformatter.h"
31#include "qgsgeometry.h"
32#include "qgslogger.h"
33#include "qgsproject.h"
34#include "qgspythonrunner.h"
35#include "qgsrelation.h"
36#include "qgssettings.h"
37#include "qgsvectorlayer.h"
38
39#include <QComboBox>
40#include <QDateTime>
41#include <QDir>
42#include <QFile>
43#include <QFileDialog>
44#include <QGraphicsOpacityEffect>
45#include <QJsonArray>
46#include <QJsonDocument>
47#include <QJsonObject>
48#include <QMenu>
49#include <QMessageBox>
50#include <QPropertyAnimation>
51#include <QString>
52#include <QTextStream>
53#include <QVersionNumber>
54
55#include "moc_qgsexpressionbuilderwidget.cpp"
56
57using namespace Qt::StringLiterals;
58
59bool formatterCanProvideAvailableValues( QgsVectorLayer *layer, const QString &fieldName )
60{
61 if ( layer )
62 {
63 const QgsFields fields = layer->fields();
64 int fieldIndex = fields.lookupField( fieldName );
65 if ( fieldIndex != -1 )
66 {
67 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
69
70 return ( formatter->flags() & QgsFieldFormatter::CanProvideAvailableValues );
71 }
72 }
73 return false;
74}
75
76
78 : QWidget( parent )
79 , mExpressionTreeMenuProvider( this )
80 , mProject( QgsProject::instance() )
81{
82 setupUi( this );
83
84 txtExpressionString = new QgsCodeEditorExpression();
85 QgsCodeEditorWidget *codeEditorWidget = new QgsCodeEditorWidget( txtExpressionString );
86 QVBoxLayout *vl = new QVBoxLayout();
87 vl->setContentsMargins( 0, 0, 0, 0 );
88 vl->addWidget( codeEditorWidget );
89 mExpressionEditorContainer->setLayout( vl );
90
91 connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
92 connect( btnNewFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
93 connect( btnRemoveFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnRemoveFile_pressed );
94 connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
95 connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
96 connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
97 connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
98 connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
99 connect( btnSaveExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::storeCurrentUserExpression );
100 connect( btnEditExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::editSelectedUserExpression );
101 connect( btnRemoveExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::removeSelectedUserExpression );
102 connect( btnImportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::importUserExpressions_pressed );
103 connect( btnExportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::exportUserExpressions_pressed );
104 connect( btnClearEditor, &QToolButton::clicked, txtExpressionString, &QgsCodeEditorExpression::clear );
105 connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, mExpressionTreeView, &QgsExpressionTreeView::setSearchText );
106
107 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::toolTipChanged, txtExpressionString, &QgsCodeEditorExpression::setToolTip );
108 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::onExpressionParsed );
109 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, btnSaveExpression, &QToolButton::setEnabled );
110 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::expressionParsed ); // signal-to-signal
111 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::parserErrorChanged, this, &QgsExpressionBuilderWidget::parserErrorChanged ); // signal-to-signal
112 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::evalErrorChanged, this, &QgsExpressionBuilderWidget::evalErrorChanged ); // signal-to-signal
113
114 connect( mExpressionTreeView, &QgsExpressionTreeView::expressionItemDoubleClicked, this, &QgsExpressionBuilderWidget::insertExpressionText );
115 connect( mExpressionTreeView, &QgsExpressionTreeView::currentExpressionItemChanged, this, &QgsExpressionBuilderWidget::expressionTreeItemChanged );
116
117 mExpressionTreeView->setMenuProvider( &mExpressionTreeMenuProvider );
118
119 txtHelpText->setOpenExternalLinks( true );
120 mValueGroupBox->hide();
121 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
122
123 // Note: must be in sync with the json help file for UserGroup
124 mUserExpressionsGroupName = QgsExpression::group( u"UserGroup"_s );
125
126 // Set icons for tool buttons
127 btnSaveExpression->setIcon( QgsApplication::getThemeIcon( u"mActionFileSave.svg"_s ) );
128 btnEditExpression->setIcon( QgsApplication::getThemeIcon( u"symbologyEdit.svg"_s ) );
129 btnRemoveExpression->setIcon( QgsApplication::getThemeIcon( u"mActionDeleteSelected.svg"_s ) );
130 btnExportExpressions->setIcon( QgsApplication::getThemeIcon( u"mActionSharingExport.svg"_s ) );
131 btnImportExpressions->setIcon( QgsApplication::getThemeIcon( u"mActionSharingImport.svg"_s ) );
132 btnClearEditor->setIcon( QgsApplication::getThemeIcon( u"mActionFileNew.svg"_s ) );
133
134 connect( btnLoadAll, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::loadAllValues );
135 connect( btnLoadSample, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::loadSampleValues );
136
137 const auto pushButtons { mOperatorsGroupBox->findChildren<QPushButton *>() };
138 for ( QPushButton *button : pushButtons )
139 {
140 connect( button, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
141 }
142
143 connect( btnCommentLinePushButton, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::commentLinesClicked );
144
145 txtSearchEdit->setShowSearchIcon( true );
146 txtSearchEdit->setPlaceholderText( tr( "Search…" ) );
147
148 mValuesModel = std::make_unique<QStandardItemModel>();
149 mProxyValues = std::make_unique<QSortFilterProxyModel>();
150 mProxyValues->setSourceModel( mValuesModel.get() );
151 mValuesListView->setModel( mProxyValues.get() );
152 txtSearchEditValues->setShowSearchIcon( true );
153 txtSearchEditValues->setPlaceholderText( tr( "Search…" ) );
154
155 editorSplit->setSizes( QList<int>( { 175, 300 } ) );
156
157 functionsplit->setCollapsible( 0, false );
158 connect( mShowHelpButton, &QPushButton::clicked, this, [this]() {
159 functionsplit->setSizes( QList<int>( { mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(), mHelpAndValuesWidget->minimumWidth() } ) );
160 mShowHelpButton->setEnabled( false );
161 } );
162 connect( functionsplit, &QSplitter::splitterMoved, this, [this]( int, int ) { mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 ); } );
163
164 QgsSettings settings;
165 splitter->restoreState( settings.value( u"Windows/QgsExpressionBuilderWidget/splitter"_s ).toByteArray() );
166 editorSplit->restoreState( settings.value( u"Windows/QgsExpressionBuilderWidget/editorsplitter"_s ).toByteArray() );
167 functionsplit->restoreState( settings.value( u"Windows/QgsExpressionBuilderWidget/functionsplitter"_s ).toByteArray() );
168 mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
169
171 {
172 QgsPythonRunner::eval( u"qgis.user.expressionspath"_s, mFunctionsPath );
173 updateFunctionFileList( mFunctionsPath );
174 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
175 }
176 else
177 {
178 tab_2->hide();
179 }
180
181 txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
182 lblAutoSave->clear();
183
184 // Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
185 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
186 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
187 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionInvalidParams );
188 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionNamedArgsError );
189#if defined( QSCINTILLA_VERSION ) && QSCINTILLA_VERSION >= 0x20a00
190 txtExpressionString->indicatorDefine( QgsCodeEditor::TriangleIndicator, QgsExpression::ParserError::Unknown );
191#else
192 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::Unknown );
193#endif
194
195 // Set all the error markers as red. -1 is all.
196 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
197 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
198 txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
199
200 // Hidden function markers.
201 txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
202 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
203 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
204 txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
205
206 connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
207 txtExpressionString->setAutoCompletionCaseSensitivity( false );
208 txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
209 txtExpressionString->setCallTipsVisible( 0 );
210
211 setExpectedOutputFormat( QString() );
212 mFunctionBuilderHelp->setLineNumbersVisible( false );
213 mFunctionBuilderHelp->setFoldingVisible( false );
214 mFunctionBuilderHelp->setEdgeMode( QsciScintilla::EdgeNone );
215 mFunctionBuilderHelp->setEdgeColumn( 0 );
216 mFunctionBuilderHelp->setReadOnly( true );
217 mFunctionBuilderHelp->setText( tr( "\"\"\"Define a new function using the @qgsfunction decorator.\n\
218\n\
219 Besides its normal arguments, the function may specify the following arguments in its signature\n\
220 Those will not need to be specified when calling the function, but will be automatically injected \n\
221\n\
222 : param feature: The current feature\n\
223 : param parent: The QgsExpression object\n\
224 : param context: ``QgsExpressionContext`` object, that gives access to various additional information like\n\
225 expression variables. E.g. ``context.variable( 'layer_id' )``\n\
226 : returns: The result of the expression.\n\
227\n\
228\n\
229\n\
230 The @qgsfunction decorator accepts the following arguments:\n\
231\n\
232\n\
233 : param group: The name of the group under which this expression function will\n\
234 be listed.\n\
235 : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
236 If False, the result will always be NULL as soon as any parameter is NULL.\n\
237 Defaults to False.\n\
238 : param usesgeometry : Set this to True if your function requires access to\n\
239 feature.geometry(). Defaults to False.\n\
240 : param referenced_columns: An array of attribute names that are required to run\n\
241 this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
242 : param params_as_list : Set this to True to pass the function parameters as a list. Can be used to mimic \n\
243 behavior before 3.32, when args was not \"auto\". Defaults to False.\n\
244\"\"\"" ) );
245
246 txtExpressionString->setFocus();
247}
248
249
251{
252 QgsSettings settings;
253 settings.setValue( u"Windows/QgsExpressionBuilderWidget/splitter"_s, splitter->saveState() );
254 settings.setValue( u"Windows/QgsExpressionBuilderWidget/editorsplitter"_s, editorSplit->saveState() );
255 settings.setValue( u"Windows/QgsExpressionBuilderWidget/functionsplitter"_s, functionsplit->saveState() );
256}
257
258void QgsExpressionBuilderWidget::init( const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
259{
260 setExpressionContext( context );
261
262 if ( flags.testFlag( LoadRecent ) )
263 mExpressionTreeView->loadRecent( recentCollection );
264
265 if ( flags.testFlag( LoadUserExpressions ) )
266 mExpressionTreeView->loadUserExpressions();
267}
268
270{
271 init( context, recentCollection, flags );
272 setLayer( layer );
273}
274
275void QgsExpressionBuilderWidget::initWithFields( const QgsFields &fields, const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
276{
277 init( context, recentCollection, flags );
278 mExpressionTreeView->loadFieldNames( fields );
279}
280
281
283{
284 mLayer = layer;
285 mExpressionTreeView->setLayer( mLayer );
286 mExpressionPreviewWidget->setLayer( mLayer );
287
288 //TODO - remove existing layer scope from context
289
290 if ( mLayer )
291 {
292 mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
293 expressionContextUpdated();
294 txtExpressionString->setFields( mLayer->fields() );
295 }
296}
297
298void QgsExpressionBuilderWidget::expressionContextUpdated()
299{
300 txtExpressionString->setExpressionContext( mExpressionContext );
301 mExpressionTreeView->setExpressionContext( mExpressionContext );
302 mExpressionPreviewWidget->setExpressionContext( mExpressionContext );
303}
304
306{
307 return mLayer;
308}
309
310void QgsExpressionBuilderWidget::expressionTreeItemChanged( QgsExpressionItem *item )
311{
312 txtSearchEditValues->clear();
313
314 if ( !item )
315 return;
316
317 bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
318 if ( isField )
319 {
320 mValuesModel->clear();
321
322 cbxValuesInUse->setVisible( formatterCanProvideAvailableValues( mLayer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) );
323 cbxValuesInUse->setChecked( false );
324 }
325 mValueGroupBox->setVisible( isField );
326
327 mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
328
329 // Show the help for the current item.
330 QString help = loadFunctionHelp( item );
331 txtHelpText->setText( help );
332
333 bool isUserExpression = item->parent() && item->parent()->text() == mUserExpressionsGroupName;
334
335 btnRemoveExpression->setEnabled( isUserExpression );
336 btnEditExpression->setEnabled( isUserExpression );
337}
338
339void QgsExpressionBuilderWidget::btnRun_pressed()
340{
341 if ( !cmbFileNames->currentItem() )
342 return;
343
344 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
345 {
347 }
348 else
349 {
350 QString file = cmbFileNames->currentItem()->text();
351 saveFunctionFile( file );
352 }
353
354 runPythonCode( txtPython->text() );
355}
356
357void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
358{
360 {
361 QString pythontext = code;
362 QgsPythonRunner::run( pythontext );
363 }
364 mExpressionTreeView->refresh();
365}
366
367QgsVectorLayer *QgsExpressionBuilderWidget::contextLayer( const QgsExpressionItem *item ) const
368{
369 QgsVectorLayer *layer = nullptr;
371 {
372 layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( item->data( QgsExpressionItem::LAYER_ID_ROLE ).toString() ) );
373 }
374 else
375 {
376 layer = mLayer;
377 }
378 return layer;
379}
380
381
383{
384 QDir myDir( mFunctionsPath );
385 if ( !myDir.exists() )
386 {
387 myDir.mkpath( mFunctionsPath );
388 }
389
390 if ( !fileName.endsWith( ".py"_L1 ) )
391 {
392 fileName.append( ".py" );
393 }
394
395 fileName = mFunctionsPath + QDir::separator() + fileName;
396 QFile myFile( fileName );
397 if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
398 {
399 QTextStream myFileStream( &myFile );
400 myFileStream << txtPython->text() << Qt::endl;
401 myFile.close();
402 }
403}
404
406{
407 mProject->writeEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, txtPython->text() );
408}
409
411{
412 mFunctionsPath = path;
413 QDir dir( path );
414 dir.setNameFilters( QStringList() << u"*.py"_s );
415 QStringList files = dir.entryList( QDir::Files );
416 cmbFileNames->clear();
417 const auto constFiles = files;
418 for ( const QString &name : constFiles )
419 {
420 QFileInfo info( mFunctionsPath + QDir::separator() + name );
421 if ( info.baseName() == "__init__"_L1 )
422 continue;
423 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"console/iconTabEditorConsole.svg"_s ), info.baseName() );
424 cmbFileNames->addItem( item );
425 }
426
427 bool ok = false;
428 mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString(), &ok );
429 if ( ok )
430 {
431 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"mIconQgsProjectFile.svg"_s ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
432 item->setData( Qt::UserRole, u"project"_s );
433 cmbFileNames->insertItem( 0, item );
434 }
435
436 if ( !cmbFileNames->currentItem() )
437 {
438 cmbFileNames->setCurrentRow( 0 );
439 }
440
441 if ( cmbFileNames->count() == 0 )
442 {
443 // Create default sample entry.
444 newFunctionFile( u"default"_s );
445 txtPython->setText( QStringLiteral(
446 "'''\n#Sample custom function file\n"
447 "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''"
448 )
449 .arg( txtPython->text() ) );
450 saveFunctionFile( u"default"_s );
451 }
452}
453
454void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
455{
456 QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
457 if ( !items.isEmpty() )
458 return;
459
460 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"console/iconTabEditorConsole.svg"_s ), fileName );
461 cmbFileNames->insertItem( 0, item );
462 cmbFileNames->setCurrentRow( 0 );
463
464 QString templateText;
465 QgsPythonRunner::eval( u"qgis.user.default_expression_template"_s, templateText );
466 txtPython->setText( templateText );
467 saveFunctionFile( fileName );
468}
469
470void QgsExpressionBuilderWidget::btnNewFile_pressed()
471{
472 // If a project has an entry for functions, then we should
473 // already have a 'Project functions' item in the file list.
474 // Since only one item should correspond to 'Project functions',
475 // we'll disable this option in the 'add function file' dialog.
476 bool ok = false;
477 mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString(), &ok );
478
479 QgsExpressionAddFunctionFileDialog dlg { !ok, this };
480 if ( dlg.exec() == QDialog::DialogCode::Accepted )
481 {
482 if ( dlg.createProjectFunctions() )
483 {
484 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"mIconQgsProjectFile.svg"_s ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
485 item->setData( Qt::UserRole, u"project"_s );
486 cmbFileNames->insertItem( 0, item );
487 cmbFileNames->setCurrentRow( 0 );
488
489 QString templateText;
490 QgsPythonRunner::eval( u"qgis.user.default_expression_template"_s, templateText );
491 txtPython->setText( templateText );
493 }
494 else
495 {
496 newFunctionFile( dlg.fileName() );
497 }
498 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
499 }
500}
501
502void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
503{
504 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
505 {
506 if ( QMessageBox::question( this, tr( "Remove Project Functions" ), tr( "Are you sure you want to remove the project functions?" ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No )
507 == QMessageBox::No )
508 return;
509
510 mProject->removeEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s );
511 }
512 else
513 {
514 if ( QMessageBox::question( this, tr( "Remove File" ), tr( "Are you sure you want to remove current functions file?" ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
515 return;
516
517 QString fileName = cmbFileNames->currentItem()->text();
518 if ( !QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
519 {
520 QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
521 return;
522 }
523 }
524
525 int currentRow = cmbFileNames->currentRow();
526 {
527 QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
528 delete itemToRemove;
529 }
530
531 if ( cmbFileNames->count() > 0 )
532 {
533 whileBlocking( cmbFileNames )->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
534 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
535 {
537 }
538 else
539 {
540 loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
541 }
542 }
543 else
544 {
545 btnRemoveFile->setEnabled( false );
546 txtPython->clear();
547 }
548}
549
550void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
551{
552 if ( lastitem )
553 {
554 if ( lastitem->data( Qt::UserRole ) != "project"_L1 )
555 {
556 QString filename = lastitem->text();
557 saveFunctionFile( filename );
558 }
559 }
560
561 if ( item->data( Qt::UserRole ) == "project"_L1 )
562 {
563 // Avoid making the project dirty when just loading functions
564 txtPython->blockSignals( true );
566 txtPython->blockSignals( false );
567
568 btnRun->setText( tr( "Load or update functions" ) );
569 btnRun->setToolTip( tr(
570 "Loads or updates functions from current script file in QGIS.\n"
571 "\n"
572 "Note the functions will only be stored when saving the project."
573 ) );
574 }
575 else
576 {
577 QString path = mFunctionsPath + QDir::separator() + item->text();
578 loadCodeFromFile( path );
579 if ( mAutoSave )
580 {
581 btnRun->setText( tr( "Load or update functions" ) );
582 btnRun->setToolTip( tr(
583 "Loads or updates functions from current script file in QGIS.\n"
584 "\n"
585 "Saved scripts are auto loaded on QGIS startup."
586 ) );
587 }
588 else
589 {
590 btnRun->setText( tr( "Save and Load Functions" ) );
591 btnRun->setToolTip( tr(
592 "Saves current script file and loads or updates its functions in QGIS.\n"
593 "\n"
594 "Saved scripts are auto loaded on QGIS startup."
595 ) );
596 }
597 }
598}
599
601{
602 if ( !path.endsWith( ".py"_L1 ) )
603 path.append( ".py" );
604
605 txtPython->loadScript( path );
606}
607
609{
610 loadFunctionCode( mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s ) );
611}
612
614{
615 txtPython->setText( code );
616}
617
618void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
619{
620 // Insert the expression text or replace selected text
621 txtExpressionString->insertText( text );
622 txtExpressionString->setFocus();
623}
624
625void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
626{
627 Q_UNUSED( fieldValues )
628 // This is not maintained and setLayer() should be used instead.
629}
630
631void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, QgsVectorLayer *layer, int countLimit, bool forceUsedValues )
632{
633 // TODO We should really return a error the user of the widget that
634 // the there is no layer set.
635 if ( !layer )
636 return;
637
638 // TODO We should thread this so that we don't hold the user up if the layer is massive.
639
640 const QgsFields fields = layer->fields();
641 int fieldIndex = fields.lookupField( fieldName );
642
643 if ( fieldIndex < 0 )
644 return;
645
646 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
648
649 QVariantList values;
650 if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
651 {
652 QgsFieldFormatterContext fieldFormatterContext;
653 fieldFormatterContext.setProject( mProject );
654 values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
655 }
656 else
657 {
658 values = qgis::setToList( layer->uniqueValues( fieldIndex, countLimit ) );
659 }
660 std::sort( values.begin(), values.end() );
661
662 mValuesModel->clear();
663 for ( const QVariant &value : std::as_const( values ) )
664 {
665 QString strValue;
666 bool forceRepresentedValue = false;
667 if ( QgsVariantUtils::isNull( value ) )
668 strValue = u"NULL"_s;
669 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
670 strValue = value.toString();
671 else if ( value.userType() == QMetaType::Type::QStringList )
672 {
673 QString result;
674 const QStringList strList = value.toStringList();
675 for ( QString str : strList )
676 {
677 if ( !result.isEmpty() )
678 result.append( u", "_s );
679
680 result.append( '\'' + str.replace( '\'', "''"_L1 ) + '\'' );
681 }
682 strValue = u"array(%1)"_s.arg( result );
683 forceRepresentedValue = true;
684 }
685 else if ( value.userType() == QMetaType::Type::QVariantList )
686 {
687 QString result;
688 const QList list = value.toList();
689 for ( const QVariant &item : list )
690 {
691 if ( !result.isEmpty() )
692 result.append( u", "_s );
693
694 result.append( item.toString() );
695 }
696 strValue = u"array(%1)"_s.arg( result );
697 forceRepresentedValue = true;
698 }
699 else
700 strValue = '\'' + value.toString().replace( '\'', "''"_L1 ) + '\'';
701
702 QString representedValue = formatter->representValue( layer, fieldIndex, setup.config(), QVariant(), value );
703 if ( forceRepresentedValue || representedValue != value.toString() )
704 representedValue = representedValue + u" ["_s + strValue + ']';
705
706 QStandardItem *item = new QStandardItem( representedValue );
707 item->setData( strValue );
708 mValuesModel->appendRow( item );
709 }
710}
711
712QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
713{
714 if ( !function )
715 return QString();
716
717 QString helpContents = QgsExpression::helpText( function->name() );
718
719 return u"<head><style>"_s + helpStylesheet() + u"</style></head><body>"_s + helpContents + u"</body>"_s;
720}
721
722
724{
725 return mExpressionValid;
726}
727
729 const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator
730)
731{
732 mExpressionPreviewWidget->setCustomPreviewGenerator( label, choices, previewContextGenerator );
733}
734
735void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
736{
737 mExpressionTreeView->saveToRecent( expressionText(), collection );
738}
739
740void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
741{
742 mExpressionTreeView->loadRecent( collection );
743}
744
746{
747 return mExpressionTreeView;
748}
749
750// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
752{
753 mExpressionTreeView->loadUserExpressions();
754}
755
756void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
757{
758 mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
759}
760
762{
763 mExpressionTreeView->removeFromUserExpressions( label );
764}
765
766
768{
769 mExpressionPreviewWidget->setGeomCalculator( da );
770}
771
773{
774 return txtExpressionString->text();
775}
776
777void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
778{
779 txtExpressionString->setText( expression );
780}
781
783{
784 return lblExpected->text();
785}
786
788{
789 lblExpected->setText( expected );
790 mExpectedOutputFrame->setVisible( !expected.isNull() );
791}
792
794{
795 mExpressionContext = context;
796 expressionContextUpdated();
797}
798
799void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
800{
801 QString text = expressionText();
802
803 btnClearEditor->setEnabled( !txtExpressionString->text().isEmpty() );
804 btnSaveExpression->setEnabled( false );
805
806 mExpressionPreviewWidget->setExpressionText( text );
807}
808
810{
811 return mExpressionPreviewWidget->parserError();
812}
813
815{
816 mExpressionPreviewWidget->setVisible( isVisible );
817}
818
820{
821 return mExpressionPreviewWidget->evalError();
822}
823
825{
827 return mExpressionTreeView->model();
829}
830
832{
833 return mProject;
834}
835
837{
838 mProject = project;
839 mExpressionTreeView->setProject( project );
840}
841
842void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
843{
844 clearErrors();
845 for ( const QgsExpression::ParserError &error : errors )
846 {
847 int errorFirstLine = error.firstLine - 1;
848 int errorFirstColumn = error.firstColumn - 1;
849 int errorLastColumn = error.lastColumn - 1;
850 int errorLastLine = error.lastLine - 1;
851
852 // If we have a unknown error we just mark the point that hit the error for now
853 // until we can handle others more.
854 if ( error.errorType == QgsExpression::ParserError::Unknown )
855 {
856 errorFirstLine = errorLastLine;
857 errorFirstColumn = errorLastColumn - 1;
858 }
859 txtExpressionString->fillIndicatorRange( errorFirstLine, errorFirstColumn, errorLastLine, errorLastColumn, error.errorType );
860 }
861}
862
863void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
864{
865 switch ( inNode->nodeType() )
866 {
868 {
869 const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
870 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
871 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
872 int start = inNode->parserFirstColumn - 1;
873 int end = inNode->parserLastColumn - 1;
874 int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
875 txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
876 if ( node->args() )
877 {
878 const QList<QgsExpressionNode *> nodeList = node->args()->list();
879 for ( QgsExpressionNode *n : nodeList )
880 {
881 createMarkers( n );
882 }
883 }
884 break;
885 }
887 {
888 break;
889 }
891 {
892 const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
893 createMarkers( node->operand() );
894 break;
895 }
897 {
898 const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
899 createMarkers( node->lowerBound() );
900 createMarkers( node->higherBound() );
901 break;
902 }
904 {
905 const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
906 createMarkers( node->opLeft() );
907 createMarkers( node->opRight() );
908 break;
909 }
911 {
912 break;
913 }
915 {
916 const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
917 if ( node->list() )
918 {
919 const QList<QgsExpressionNode *> nodeList = node->list()->list();
920 for ( QgsExpressionNode *n : nodeList )
921 {
922 createMarkers( n );
923 }
924 }
925 break;
926 }
928 {
929 const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
930 const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
931 for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
932 {
933 createMarkers( cond->whenExp() );
934 createMarkers( cond->thenExp() );
935 }
936 if ( node->elseExp() )
937 {
938 createMarkers( node->elseExp() );
939 }
940 break;
941 }
943 {
944 break;
945 }
946 }
947}
948
949void QgsExpressionBuilderWidget::clearFunctionMarkers()
950{
951 int lastLine = txtExpressionString->lines() - 1;
952 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
953}
954
955void QgsExpressionBuilderWidget::clearErrors()
956{
957 int lastLine = txtExpressionString->lines() - 1;
958 // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
959 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
960 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
961 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
962 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
963 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
964}
965
966void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
967{
968 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
969 mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
970}
971
972void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
973{
974 // Insert the item text or replace selected text
975 txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
976 txtExpressionString->setFocus();
977}
978
979void QgsExpressionBuilderWidget::operatorButtonClicked()
980{
981 QPushButton *button = qobject_cast<QPushButton *>( sender() );
982
983 // Insert the button text or replace selected text
984 txtExpressionString->insertText( ' ' + button->text() + ' ' );
985 txtExpressionString->setFocus();
986}
987
988void QgsExpressionBuilderWidget::commentLinesClicked()
989{
990 txtExpressionString->toggleComment();
991}
992
994{
995 QgsExpressionItem *item = mExpressionTreeView->currentItem();
996 if ( !item )
997 {
998 return;
999 }
1000
1001 QgsVectorLayer *layer { contextLayer( item ) };
1002 // TODO We should really return a error the user of the widget that
1003 // the there is no layer set.
1004 if ( !layer )
1005 {
1006 return;
1007 }
1008
1009 mValueGroupBox->show();
1010 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
1011}
1012
1014{
1015 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1016 if ( !item )
1017 {
1018 return;
1019 }
1020
1021 QgsVectorLayer *layer { contextLayer( item ) };
1022 // TODO We should really return a error the user of the widget that
1023 // the there is no layer set.
1024 if ( !layer )
1025 {
1026 return;
1027 }
1028
1029 mValueGroupBox->show();
1030 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
1031}
1032
1034{
1035 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1036 if ( !item )
1037 {
1038 return;
1039 }
1040
1041 QgsVectorLayer *layer { contextLayer( item ) };
1042 // TODO We should really return a error the user of the widget that
1043 // the there is no layer set.
1044 if ( !layer )
1045 {
1046 return;
1047 }
1048
1049 mValueGroupBox->show();
1050 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
1051}
1052
1054{
1055 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1056 if ( !item )
1057 {
1058 return;
1059 }
1060
1061 QgsVectorLayer *layer { contextLayer( item ) };
1062 // TODO We should really return a error the user of the widget that
1063 // the there is no layer set.
1064 if ( !layer )
1065 {
1066 return;
1067 }
1068
1069 mValueGroupBox->show();
1070 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
1071}
1072
1073void QgsExpressionBuilderWidget::txtPython_textChanged()
1074{
1075 if ( tabWidget->currentIndex() != 1 )
1076 return;
1077
1078 QListWidgetItem *item = cmbFileNames->currentItem();
1079 if ( !item )
1080 return;
1081
1082 if ( item->data( Qt::UserRole ) == "project"_L1 )
1083 {
1084 saveProjectFunctionsEntry(); // Makes project dirty
1085 displayTemporaryLabel( tr( "Project changed" ) );
1086 }
1087 else if ( mAutoSave )
1088 {
1089 autosave();
1090 }
1091}
1092
1094{
1095 // Don't auto save if not on function editor that would be silly.
1096 if ( tabWidget->currentIndex() != 1 )
1097 return;
1098
1099 QListWidgetItem *item = cmbFileNames->currentItem();
1100 if ( !item )
1101 return;
1102
1103 if ( item->data( Qt::UserRole ) != "project"_L1 )
1104 {
1105 QString file = item->text();
1106 saveFunctionFile( file );
1107 displayTemporaryLabel( tr( "Function file saved" ) );
1108 }
1109}
1110
1111void QgsExpressionBuilderWidget::displayTemporaryLabel( const QString &text )
1112{
1113 lblAutoSave->setText( text );
1114 QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
1115 lblAutoSave->setGraphicsEffect( effect );
1116 QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
1117 anim->setDuration( 2000 );
1118 anim->setStartValue( 1.0 );
1119 anim->setEndValue( 0.0 );
1120 anim->setEasingCurve( QEasingCurve::OutQuad );
1121 anim->start( QAbstractAnimation::DeleteWhenStopped );
1122}
1123
1125{
1126 const QString expression { this->expressionText() };
1127 QgsExpressionStoreDialog dlg { expression, expression, QString(), mExpressionTreeView->userExpressionLabels() };
1128 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1129 {
1130 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1131 }
1132}
1133
1135{
1136 // Get the item
1137 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1138 if ( !item )
1139 return;
1140
1141 // Don't handle remove if we are on a header node or the parent
1142 // is not the user group
1143 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1144 return;
1145
1146 QgsSettings settings;
1147 QString helpText = settings.value( u"user/%1/helpText"_s.arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1148 QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1149
1150 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1151 {
1152 // label has changed removed the old one before adding the new one
1153 if ( dlg.isLabelModified() )
1154 {
1155 mExpressionTreeView->removeFromUserExpressions( item->text() );
1156 }
1157
1158 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1159 }
1160}
1161
1163{
1164 // Get the item
1165 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1166
1167 if ( !item )
1168 return;
1169
1170 // Don't handle remove if we are on a header node or the parent
1171 // is not the user group
1172 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1173 return;
1174
1175 if ( QMessageBox::Yes
1176 == QMessageBox::question( this, tr( "Remove Stored Expression" ), tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ), QMessageBox::Yes | QMessageBox::No ) )
1177 {
1178 mExpressionTreeView->removeFromUserExpressions( item->text() );
1179 }
1180}
1181
1182void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1183{
1184 QgsSettings settings;
1185 QString lastSaveDir = settings.value( u"lastExportExpressionsDir"_s, QDir::homePath(), QgsSettings::App ).toString();
1186 QString saveFileName = QFileDialog::getSaveFileName( this, tr( "Export User Expressions" ), lastSaveDir, tr( "User expressions" ) + " (*.json)" );
1187
1188 // return dialog focus on Mac
1189 activateWindow();
1190 raise();
1191 if ( saveFileName.isEmpty() )
1192 return;
1193
1194 QFileInfo saveFileInfo( saveFileName );
1195
1196 if ( saveFileInfo.suffix().isEmpty() )
1197 {
1198 QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1199 saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1200 }
1201
1202 settings.setValue( u"lastExportExpressionsDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
1203
1204 QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1205 QFile jsonFile( saveFileName );
1206
1207 if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1208 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1209
1210 if ( !jsonFile.write( exportJson.toJson() ) )
1211 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1212 else
1213 jsonFile.close();
1214}
1215
1216void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1217{
1218 QgsSettings settings;
1219 QString lastImportDir = settings.value( u"lastImportExpressionsDir"_s, QDir::homePath(), QgsSettings::App ).toString();
1220 QString loadFileName = QFileDialog::getOpenFileName( this, tr( "Import User Expressions" ), lastImportDir, tr( "User expressions" ) + " (*.json)" );
1221
1222 if ( loadFileName.isEmpty() )
1223 return;
1224
1225 QFileInfo loadFileInfo( loadFileName );
1226
1227 settings.setValue( u"lastImportExpressionsDir"_s, loadFileInfo.absolutePath(), QgsSettings::App );
1228
1229 QFile jsonFile( loadFileName );
1230
1231 if ( !jsonFile.open( QFile::ReadOnly ) )
1232 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1233
1234 QTextStream jsonStream( &jsonFile );
1235 QString jsonString = jsonFile.readAll();
1236 jsonFile.close();
1237
1238 QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1239
1240 if ( importJson.isNull() )
1241 {
1242 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1243 return;
1244 }
1245
1246 mExpressionTreeView->loadExpressionsFromJson( importJson );
1247}
1248
1249
1250const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1251{
1252 return mExpressionTreeView->findExpressions( label );
1253}
1254
1255void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1256{
1257 if ( state & Qt::ControlModifier )
1258 {
1259 int position = txtExpressionString->positionFromLineIndex( line, index );
1260 long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1262 QString help = getFunctionHelp( func );
1263 txtHelpText->setText( help );
1264 }
1265}
1266
1267void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1268{
1269 clearErrors();
1270
1271 mExpressionValid = state;
1272 if ( state )
1273 {
1274 createMarkers( mExpressionPreviewWidget->rootNode() );
1275 }
1276 else
1277 {
1278 createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1279 }
1280}
1281
1282QString QgsExpressionBuilderWidget::helpStylesheet() const
1283{
1284 //start with default QGIS report style
1285 QString style = QgsApplication::reportStyleSheet();
1286
1287 //add some tweaks
1288 style += " .functionname {color: #0a6099; font-weight: bold;} "
1289 " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1290 " td.argument { padding-right: 10px; }";
1291
1292 return style;
1293}
1294
1295QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1296{
1297 if ( !expressionItem )
1298 return QString();
1299
1300 QString helpContents = expressionItem->getHelpText();
1301
1302 // Return the function help that is set for the function if there is one.
1303 if ( helpContents.isEmpty() )
1304 {
1305 QString name = expressionItem->data( Qt::UserRole ).toString();
1306
1307 if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1308 helpContents = QgsExpression::helpText( u"Field"_s );
1309 else
1310 helpContents = QgsExpression::helpText( name );
1311 }
1312
1313 return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1314}
1315
1316
1317// *************
1318// Menu provider
1319
1320QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1321{
1322 QMenu *menu = nullptr;
1323 QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1324 if ( item->getItemType() == QgsExpressionItem::Field && layer )
1325 {
1326 menu = new QMenu( mExpressionBuilderWidget );
1327 menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1328 menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1329
1331 {
1332 menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1333 menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1334 }
1335 }
1336 return menu;
1337}
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 QGIS expression editor based on QScintilla2.
void setExpressionContext(const QgsExpressionContext &context)
Variables and functions from this expression context will be added to the API.
A widget which wraps a QgsCodeEditor in additional functionality.
void insertText(const QString &text)
Insert text at cursor position, or replace any selected text if user has made a selection.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
Holder for the widget type and its configuration for a field.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
A dialog to select whether to create a function file or project functions.
bool createProjectFunctions() const
Returns whether user has selected to create project functions.
Q_DECL_DEPRECATED void loadRecent(const QString &collection=u"generic"_s)
Loads the recent expressions from the given collection.
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 loadCodeFromProjectFunctions()
Loads code from the project into the function editor.
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 loadFunctionCode(const QString &code)
Loads code into the function editor.
Q_DECL_DEPRECATED QStandardItemModel * model()
Returns a pointer to the dialog's function item model.
QgsExpressionTreeView * expressionTree() const
Returns the expression tree.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context.
bool isExpressionValid()
Returns if the expression is valid.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
void loadCodeFromFile(QString path)
Loads code from the given file into the function editor.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
void initWithLayer(QgsVectorLayer *layer, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=u"generic"_s, QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with a layer.
bool parserError() const
Will be set to true if the current expression text reports a parser error with the context.
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.
QgsProject * project()
Returns the project currently associated with the widget.
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 updateFunctionFileList(const QString &path)
Updates the list of function files found at the given path.
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...
void init(const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=u"generic"_s, QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize without any layer.
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.
void saveProjectFunctionsEntry()
Writes the current function editor text to a project entry.
void initWithFields(const QgsFields &fields, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=u"generic"_s, QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with given fields without any layer.
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...
An 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.
QgsExpressionNode * lowerBound() const
Returns the lower bound expression node of the range.
QgsExpressionNode * higherBound() const
Returns the higher bound expression node of the range.
QgsExpressionNode * opLeft() const
Returns the node to the left of the operator.
QgsExpressionNode * opRight() const
Returns the node to the right of the operator.
QgsExpressionNode * elseExp() const
The ELSE expression used for the condition.
WhenThenList conditions() const
The list of WHEN THEN expression parts of the expression.
int fnIndex() const
Returns the index of the node's function.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
QgsExpressionNode::NodeList * list() const
Returns the list of nodes to search for matching values within.
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.
@ ntBetweenOperator
Between operator.
@ ntIndexOperator
Index operator.
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.
QString label()
Returns the label text.
QString expression()
Returns the expression text.
QString helpText() const
Returns the help text.
bool isLabelModified() const SIP_SKIP
Returns whether the label text was modified either manually by the user, or automatically because it ...
A tree view to list all expressions functions, variables and fields that can be used in an expression...
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.
void loadUserExpressions()
Loads the user expressions.
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.
Flags flags() const
Returns the flags.
virtual QVariantList availableValues(const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context) const
Returns a list of the values that would be possible to select with this widget type On a RelationRefe...
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.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition qgsfield.cpp:749
Container of fields for a vector layer.
Definition qgsfields.h:46
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
static QgsProject * instance()
Returns the QgsProject singleton instance.
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).
Stores settings for use within QGIS.
Definition qgssettings.h:68
void clear()
Removes all entries in the user settings.
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 dataset.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7780
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7779
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:7141
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.