QGIS API Documentation 4.1.0-Master (3b8ef1f72a3)
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
247
249{
250 QgsSettings settings;
251 settings.setValue( u"Windows/QgsExpressionBuilderWidget/splitter"_s, splitter->saveState() );
252 settings.setValue( u"Windows/QgsExpressionBuilderWidget/editorsplitter"_s, editorSplit->saveState() );
253 settings.setValue( u"Windows/QgsExpressionBuilderWidget/functionsplitter"_s, functionsplit->saveState() );
254}
255
256void QgsExpressionBuilderWidget::init( const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
257{
258 setExpressionContext( context );
259
260 if ( flags.testFlag( LoadRecent ) )
261 mExpressionTreeView->loadRecent( recentCollection );
262
263 if ( flags.testFlag( LoadUserExpressions ) )
264 mExpressionTreeView->loadUserExpressions();
265}
266
268{
269 init( context, recentCollection, flags );
270 setLayer( layer );
271}
272
273void QgsExpressionBuilderWidget::initWithFields( const QgsFields &fields, const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
274{
275 init( context, recentCollection, flags );
276 mExpressionTreeView->loadFieldNames( fields );
277}
278
279
281{
282 mLayer = layer;
283 mExpressionTreeView->setLayer( mLayer );
284 mExpressionPreviewWidget->setLayer( mLayer );
285
286 //TODO - remove existing layer scope from context
287
288 if ( mLayer )
289 {
290 mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
291 expressionContextUpdated();
292 txtExpressionString->setFields( mLayer->fields() );
293 }
294}
295
296void QgsExpressionBuilderWidget::expressionContextUpdated()
297{
298 txtExpressionString->setExpressionContext( mExpressionContext );
299 mExpressionTreeView->setExpressionContext( mExpressionContext );
300 mExpressionPreviewWidget->setExpressionContext( mExpressionContext );
301}
302
304{
305 return mLayer;
306}
307
308void QgsExpressionBuilderWidget::expressionTreeItemChanged( QgsExpressionItem *item )
309{
310 txtSearchEditValues->clear();
311
312 if ( !item )
313 return;
314
315 bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
316 if ( isField )
317 {
318 mValuesModel->clear();
319
320 cbxValuesInUse->setVisible( formatterCanProvideAvailableValues( mLayer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) );
321 cbxValuesInUse->setChecked( false );
322 }
323 mValueGroupBox->setVisible( isField );
324
325 mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
326
327 // Show the help for the current item.
328 QString help = loadFunctionHelp( item );
329 txtHelpText->setText( help );
330
331 bool isUserExpression = item->parent() && item->parent()->text() == mUserExpressionsGroupName;
332
333 btnRemoveExpression->setEnabled( isUserExpression );
334 btnEditExpression->setEnabled( isUserExpression );
335}
336
337void QgsExpressionBuilderWidget::btnRun_pressed()
338{
339 if ( !cmbFileNames->currentItem() )
340 return;
341
342 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
343 {
345 }
346 else
347 {
348 QString file = cmbFileNames->currentItem()->text();
349 saveFunctionFile( file );
350 }
351
352 runPythonCode( txtPython->text() );
353}
354
355void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
356{
358 {
359 QString pythontext = code;
360 QgsPythonRunner::run( pythontext );
361 }
362 mExpressionTreeView->refresh();
363}
364
365QgsVectorLayer *QgsExpressionBuilderWidget::contextLayer( const QgsExpressionItem *item ) const
366{
367 QgsVectorLayer *layer = nullptr;
369 {
370 layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( item->data( QgsExpressionItem::LAYER_ID_ROLE ).toString() ) );
371 }
372 else
373 {
374 layer = mLayer;
375 }
376 return layer;
377}
378
379
381{
382 QDir myDir( mFunctionsPath );
383 if ( !myDir.exists() )
384 {
385 myDir.mkpath( mFunctionsPath );
386 }
387
388 if ( !fileName.endsWith( ".py"_L1 ) )
389 {
390 fileName.append( ".py" );
391 }
392
393 fileName = mFunctionsPath + QDir::separator() + fileName;
394 QFile myFile( fileName );
395 if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
396 {
397 QTextStream myFileStream( &myFile );
398 myFileStream << txtPython->text() << Qt::endl;
399 myFile.close();
400 }
401}
402
404{
405 mProject->writeEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, txtPython->text() );
406}
407
409{
410 mFunctionsPath = path;
411 QDir dir( path );
412 dir.setNameFilters( QStringList() << u"*.py"_s );
413 QStringList files = dir.entryList( QDir::Files );
414 cmbFileNames->clear();
415 const auto constFiles = files;
416 for ( const QString &name : constFiles )
417 {
418 QFileInfo info( mFunctionsPath + QDir::separator() + name );
419 if ( info.baseName() == "__init__"_L1 )
420 continue;
421 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"console/iconTabEditorConsole.svg"_s ), info.baseName() );
422 cmbFileNames->addItem( item );
423 }
424
425 bool ok = false;
426 mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString(), &ok );
427 if ( ok )
428 {
429 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"mIconQgsProjectFile.svg"_s ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
430 item->setData( Qt::UserRole, u"project"_s );
431 cmbFileNames->insertItem( 0, item );
432 }
433
434 if ( !cmbFileNames->currentItem() )
435 {
436 cmbFileNames->setCurrentRow( 0 );
437 }
438
439 if ( cmbFileNames->count() == 0 )
440 {
441 // Create default sample entry.
442 newFunctionFile( u"default"_s );
443 txtPython->setText( QStringLiteral(
444 "'''\n#Sample custom function file\n"
445 "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''"
446 )
447 .arg( txtPython->text() ) );
448 saveFunctionFile( u"default"_s );
449 }
450}
451
452void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
453{
454 QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
455 if ( !items.isEmpty() )
456 return;
457
458 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"console/iconTabEditorConsole.svg"_s ), fileName );
459 cmbFileNames->insertItem( 0, item );
460 cmbFileNames->setCurrentRow( 0 );
461
462 QString templateText;
463 QgsPythonRunner::eval( u"qgis.user.default_expression_template"_s, templateText );
464 txtPython->setText( templateText );
465 saveFunctionFile( fileName );
466}
467
468void QgsExpressionBuilderWidget::btnNewFile_pressed()
469{
470 // If a project has an entry for functions, then we should
471 // already have a 'Project functions' item in the file list.
472 // Since only one item should correspond to 'Project functions',
473 // we'll disable this option in the 'add function file' dialog.
474 bool ok = false;
475 mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString(), &ok );
476
477 QgsExpressionAddFunctionFileDialog dlg { !ok, this };
478 if ( dlg.exec() == QDialog::DialogCode::Accepted )
479 {
480 if ( dlg.createProjectFunctions() )
481 {
482 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( u"mIconQgsProjectFile.svg"_s ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
483 item->setData( Qt::UserRole, u"project"_s );
484 cmbFileNames->insertItem( 0, item );
485 cmbFileNames->setCurrentRow( 0 );
486
487 QString templateText;
488 QgsPythonRunner::eval( u"qgis.user.default_expression_template"_s, templateText );
489 txtPython->setText( templateText );
491 }
492 else
493 {
494 newFunctionFile( dlg.fileName() );
495 }
496 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
497 }
498}
499
500void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
501{
502 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
503 {
504 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 )
505 == QMessageBox::No )
506 return;
507
508 mProject->removeEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s );
509 }
510 else
511 {
512 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 )
513 return;
514
515 QString fileName = cmbFileNames->currentItem()->text();
516 if ( !QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
517 {
518 QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
519 return;
520 }
521 }
522
523 int currentRow = cmbFileNames->currentRow();
524 {
525 QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
526 delete itemToRemove;
527 }
528
529 if ( cmbFileNames->count() > 0 )
530 {
531 whileBlocking( cmbFileNames )->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
532 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == "project"_L1 )
533 {
535 }
536 else
537 {
538 loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
539 }
540 }
541 else
542 {
543 btnRemoveFile->setEnabled( false );
544 txtPython->clear();
545 }
546}
547
548void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
549{
550 if ( lastitem )
551 {
552 if ( lastitem->data( Qt::UserRole ) != "project"_L1 )
553 {
554 QString filename = lastitem->text();
555 saveFunctionFile( filename );
556 }
557 }
558
559 if ( item->data( Qt::UserRole ) == "project"_L1 )
560 {
561 // Avoid making the project dirty when just loading functions
562 txtPython->blockSignals( true );
564 txtPython->blockSignals( false );
565
566 btnRun->setText( tr( "Load or update functions" ) );
567 btnRun->setToolTip( tr(
568 "Loads or updates functions from current script file in QGIS.\n"
569 "\n"
570 "Note the functions will only be stored when saving the project."
571 ) );
572 }
573 else
574 {
575 QString path = mFunctionsPath + QDir::separator() + item->text();
576 loadCodeFromFile( path );
577 if ( mAutoSave )
578 {
579 btnRun->setText( tr( "Load or update functions" ) );
580 btnRun->setToolTip( tr(
581 "Loads or updates functions from current script file in QGIS.\n"
582 "\n"
583 "Saved scripts are auto loaded on QGIS startup."
584 ) );
585 }
586 else
587 {
588 btnRun->setText( tr( "Save and Load Functions" ) );
589 btnRun->setToolTip( tr(
590 "Saves current script file and loads or updates its functions in QGIS.\n"
591 "\n"
592 "Saved scripts are auto loaded on QGIS startup."
593 ) );
594 }
595 }
596}
597
599{
600 if ( !path.endsWith( ".py"_L1 ) )
601 path.append( ".py" );
602
603 txtPython->loadScript( path );
604}
605
607{
608 loadFunctionCode( mProject->readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s ) );
609}
610
612{
613 txtPython->setText( code );
614}
615
616void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
617{
618 // Insert the expression text or replace selected text
619 txtExpressionString->insertText( text );
620 txtExpressionString->setFocus();
621}
622
623void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
624{
625 Q_UNUSED( fieldValues )
626 // This is not maintained and setLayer() should be used instead.
627}
628
629void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, QgsVectorLayer *layer, int countLimit, bool forceUsedValues )
630{
631 // TODO We should really return a error the user of the widget that
632 // the there is no layer set.
633 if ( !layer )
634 return;
635
636 // TODO We should thread this so that we don't hold the user up if the layer is massive.
637
638 const QgsFields fields = layer->fields();
639 int fieldIndex = fields.lookupField( fieldName );
640
641 if ( fieldIndex < 0 )
642 return;
643
644 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
646
647 QVariantList values;
648 if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
649 {
650 QgsFieldFormatterContext fieldFormatterContext;
651 fieldFormatterContext.setProject( mProject );
652 values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
653 }
654 else
655 {
656 values = qgis::setToList( layer->uniqueValues( fieldIndex, countLimit ) );
657 }
658 std::sort( values.begin(), values.end() );
659
660 mValuesModel->clear();
661 for ( const QVariant &value : std::as_const( values ) )
662 {
663 QString strValue;
664 bool forceRepresentedValue = false;
665 if ( QgsVariantUtils::isNull( value ) )
666 strValue = u"NULL"_s;
667 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
668 strValue = value.toString();
669 else if ( value.userType() == QMetaType::Type::QStringList )
670 {
671 QString result;
672 const QStringList strList = value.toStringList();
673 for ( QString str : strList )
674 {
675 if ( !result.isEmpty() )
676 result.append( u", "_s );
677
678 result.append( '\'' + str.replace( '\'', "''"_L1 ) + '\'' );
679 }
680 strValue = u"array(%1)"_s.arg( result );
681 forceRepresentedValue = true;
682 }
683 else if ( value.userType() == QMetaType::Type::QVariantList )
684 {
685 QString result;
686 const QList list = value.toList();
687 for ( const QVariant &item : list )
688 {
689 if ( !result.isEmpty() )
690 result.append( u", "_s );
691
692 result.append( item.toString() );
693 }
694 strValue = u"array(%1)"_s.arg( result );
695 forceRepresentedValue = true;
696 }
697 else
698 strValue = '\'' + value.toString().replace( '\'', "''"_L1 ) + '\'';
699
700 QString representedValue = formatter->representValue( layer, fieldIndex, setup.config(), QVariant(), value );
701 if ( forceRepresentedValue || representedValue != value.toString() )
702 representedValue = representedValue + u" ["_s + strValue + ']';
703
704 QStandardItem *item = new QStandardItem( representedValue );
705 item->setData( strValue );
706 mValuesModel->appendRow( item );
707 }
708}
709
710QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
711{
712 if ( !function )
713 return QString();
714
715 QString helpContents = QgsExpression::helpText( function->name() );
716
717 return u"<head><style>"_s + helpStylesheet() + u"</style></head><body>"_s + helpContents + u"</body>"_s;
718}
719
720
722{
723 return mExpressionValid;
724}
725
727 const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator
728)
729{
730 mExpressionPreviewWidget->setCustomPreviewGenerator( label, choices, previewContextGenerator );
731}
732
733void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
734{
735 mExpressionTreeView->saveToRecent( expressionText(), collection );
736}
737
738void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
739{
740 mExpressionTreeView->loadRecent( collection );
741}
742
744{
745 return mExpressionTreeView;
746}
747
748// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
750{
751 mExpressionTreeView->loadUserExpressions();
752}
753
754void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
755{
756 mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
757}
758
760{
761 mExpressionTreeView->removeFromUserExpressions( label );
762}
763
764
766{
767 mExpressionPreviewWidget->setGeomCalculator( da );
768}
769
771{
772 return txtExpressionString->text();
773}
774
775void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
776{
777 txtExpressionString->setText( expression );
778}
779
781{
782 return lblExpected->text();
783}
784
786{
787 lblExpected->setText( expected );
788 mExpectedOutputFrame->setVisible( !expected.isNull() );
789}
790
792{
793 mExpressionContext = context;
794 expressionContextUpdated();
795}
796
797void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
798{
799 QString text = expressionText();
800
801 btnClearEditor->setEnabled( !txtExpressionString->text().isEmpty() );
802 btnSaveExpression->setEnabled( false );
803
804 mExpressionPreviewWidget->setExpressionText( text );
805}
806
808{
809 return mExpressionPreviewWidget->parserError();
810}
811
813{
814 mExpressionPreviewWidget->setVisible( isVisible );
815}
816
818{
819 return mExpressionPreviewWidget->evalError();
820}
821
823{
825 return mExpressionTreeView->model();
827}
828
830{
831 return mProject;
832}
833
835{
836 mProject = project;
837 mExpressionTreeView->setProject( project );
838}
839
841{
842 QWidget::showEvent( e );
843 txtExpressionString->setFocus();
844}
845
846void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
847{
848 clearErrors();
849 for ( const QgsExpression::ParserError &error : errors )
850 {
851 int errorFirstLine = error.firstLine - 1;
852 int errorFirstColumn = error.firstColumn - 1;
853 int errorLastColumn = error.lastColumn - 1;
854 int errorLastLine = error.lastLine - 1;
855
856 // If we have a unknown error we just mark the point that hit the error for now
857 // until we can handle others more.
858 if ( error.errorType == QgsExpression::ParserError::Unknown )
859 {
860 errorFirstLine = errorLastLine;
861 errorFirstColumn = errorLastColumn - 1;
862 }
863 txtExpressionString->fillIndicatorRange( errorFirstLine, errorFirstColumn, errorLastLine, errorLastColumn, error.errorType );
864 }
865}
866
867void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
868{
869 switch ( inNode->nodeType() )
870 {
872 {
873 const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
874 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
875 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
876 int start = inNode->parserFirstColumn - 1;
877 int end = inNode->parserLastColumn - 1;
878 int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
879 txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
880 if ( node->args() )
881 {
882 const QList<QgsExpressionNode *> nodeList = node->args()->list();
883 for ( QgsExpressionNode *n : nodeList )
884 {
885 createMarkers( n );
886 }
887 }
888 break;
889 }
891 {
892 break;
893 }
895 {
896 const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
897 createMarkers( node->operand() );
898 break;
899 }
901 {
902 const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
903 createMarkers( node->lowerBound() );
904 createMarkers( node->higherBound() );
905 break;
906 }
908 {
909 const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
910 createMarkers( node->opLeft() );
911 createMarkers( node->opRight() );
912 break;
913 }
915 {
916 break;
917 }
919 {
920 const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
921 if ( node->list() )
922 {
923 const QList<QgsExpressionNode *> nodeList = node->list()->list();
924 for ( QgsExpressionNode *n : nodeList )
925 {
926 createMarkers( n );
927 }
928 }
929 break;
930 }
932 {
933 const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
934 const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
935 for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
936 {
937 createMarkers( cond->whenExp() );
938 createMarkers( cond->thenExp() );
939 }
940 if ( node->elseExp() )
941 {
942 createMarkers( node->elseExp() );
943 }
944 break;
945 }
947 {
948 break;
949 }
950 }
951}
952
953void QgsExpressionBuilderWidget::clearFunctionMarkers()
954{
955 int lastLine = txtExpressionString->lines() - 1;
956 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
957}
958
959void QgsExpressionBuilderWidget::clearErrors()
960{
961 int lastLine = txtExpressionString->lines() - 1;
962 // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
963 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
964 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
965 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
966 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
967 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
968}
969
970void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
971{
972 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
973 mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
974}
975
976void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
977{
978 // Insert the item text or replace selected text
979 txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
980 txtExpressionString->setFocus();
981}
982
983void QgsExpressionBuilderWidget::operatorButtonClicked()
984{
985 QPushButton *button = qobject_cast<QPushButton *>( sender() );
986
987 // Insert the button text or replace selected text
988 txtExpressionString->insertText( ' ' + button->text() + ' ' );
989 txtExpressionString->setFocus();
990}
991
992void QgsExpressionBuilderWidget::commentLinesClicked()
993{
994 txtExpressionString->toggleComment();
995}
996
998{
999 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1000 if ( !item )
1001 {
1002 return;
1003 }
1004
1005 QgsVectorLayer *layer { contextLayer( item ) };
1006 // TODO We should really return a error the user of the widget that
1007 // the there is no layer set.
1008 if ( !layer )
1009 {
1010 return;
1011 }
1012
1013 mValueGroupBox->show();
1014 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
1015}
1016
1018{
1019 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1020 if ( !item )
1021 {
1022 return;
1023 }
1024
1025 QgsVectorLayer *layer { contextLayer( item ) };
1026 // TODO We should really return a error the user of the widget that
1027 // the there is no layer set.
1028 if ( !layer )
1029 {
1030 return;
1031 }
1032
1033 mValueGroupBox->show();
1034 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
1035}
1036
1038{
1039 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1040 if ( !item )
1041 {
1042 return;
1043 }
1044
1045 QgsVectorLayer *layer { contextLayer( item ) };
1046 // TODO We should really return a error the user of the widget that
1047 // the there is no layer set.
1048 if ( !layer )
1049 {
1050 return;
1051 }
1052
1053 mValueGroupBox->show();
1054 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
1055}
1056
1058{
1059 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1060 if ( !item )
1061 {
1062 return;
1063 }
1064
1065 QgsVectorLayer *layer { contextLayer( item ) };
1066 // TODO We should really return a error the user of the widget that
1067 // the there is no layer set.
1068 if ( !layer )
1069 {
1070 return;
1071 }
1072
1073 mValueGroupBox->show();
1074 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
1075}
1076
1077void QgsExpressionBuilderWidget::txtPython_textChanged()
1078{
1079 if ( tabWidget->currentIndex() != 1 )
1080 return;
1081
1082 QListWidgetItem *item = cmbFileNames->currentItem();
1083 if ( !item )
1084 return;
1085
1086 if ( item->data( Qt::UserRole ) == "project"_L1 )
1087 {
1088 saveProjectFunctionsEntry(); // Makes project dirty
1089 displayTemporaryLabel( tr( "Project changed" ) );
1090 }
1091 else if ( mAutoSave )
1092 {
1093 autosave();
1094 }
1095}
1096
1098{
1099 // Don't auto save if not on function editor that would be silly.
1100 if ( tabWidget->currentIndex() != 1 )
1101 return;
1102
1103 QListWidgetItem *item = cmbFileNames->currentItem();
1104 if ( !item )
1105 return;
1106
1107 if ( item->data( Qt::UserRole ) != "project"_L1 )
1108 {
1109 QString file = item->text();
1110 saveFunctionFile( file );
1111 displayTemporaryLabel( tr( "Function file saved" ) );
1112 }
1113}
1114
1115void QgsExpressionBuilderWidget::displayTemporaryLabel( const QString &text )
1116{
1117 lblAutoSave->setText( text );
1118 QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
1119 lblAutoSave->setGraphicsEffect( effect );
1120 QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
1121 anim->setDuration( 2000 );
1122 anim->setStartValue( 1.0 );
1123 anim->setEndValue( 0.0 );
1124 anim->setEasingCurve( QEasingCurve::OutQuad );
1125 anim->start( QAbstractAnimation::DeleteWhenStopped );
1126}
1127
1129{
1130 const QString expression { this->expressionText() };
1131 QgsExpressionStoreDialog dlg { expression, expression, QString(), mExpressionTreeView->userExpressionLabels() };
1132 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1133 {
1134 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1135 }
1136}
1137
1139{
1140 // Get the item
1141 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1142 if ( !item )
1143 return;
1144
1145 // Don't handle remove if we are on a header node or the parent
1146 // is not the user group
1147 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1148 return;
1149
1150 QgsSettings settings;
1151 QString helpText = settings.value( u"user/%1/helpText"_s.arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1152 QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1153
1154 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1155 {
1156 // label has changed removed the old one before adding the new one
1157 if ( dlg.isLabelModified() )
1158 {
1159 mExpressionTreeView->removeFromUserExpressions( item->text() );
1160 }
1161
1162 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1163 }
1164}
1165
1167{
1168 // Get the item
1169 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1170
1171 if ( !item )
1172 return;
1173
1174 // Don't handle remove if we are on a header node or the parent
1175 // is not the user group
1176 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1177 return;
1178
1179 if ( QMessageBox::Yes
1180 == QMessageBox::question( this, tr( "Remove Stored Expression" ), tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ), QMessageBox::Yes | QMessageBox::No ) )
1181 {
1182 mExpressionTreeView->removeFromUserExpressions( item->text() );
1183 }
1184}
1185
1186void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1187{
1188 QgsSettings settings;
1189 QString lastSaveDir = settings.value( u"lastExportExpressionsDir"_s, QDir::homePath(), QgsSettings::App ).toString();
1190 QString saveFileName = QFileDialog::getSaveFileName( this, tr( "Export User Expressions" ), lastSaveDir, tr( "User expressions" ) + " (*.json)" );
1191
1192 // return dialog focus on Mac
1193 activateWindow();
1194 raise();
1195 if ( saveFileName.isEmpty() )
1196 return;
1197
1198 QFileInfo saveFileInfo( saveFileName );
1199
1200 if ( saveFileInfo.suffix().isEmpty() )
1201 {
1202 QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1203 saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1204 }
1205
1206 settings.setValue( u"lastExportExpressionsDir"_s, saveFileInfo.absolutePath(), QgsSettings::App );
1207
1208 QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1209 QFile jsonFile( saveFileName );
1210
1211 if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1212 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1213
1214 if ( !jsonFile.write( exportJson.toJson() ) )
1215 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1216 else
1217 jsonFile.close();
1218}
1219
1220void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1221{
1222 QgsSettings settings;
1223 QString lastImportDir = settings.value( u"lastImportExpressionsDir"_s, QDir::homePath(), QgsSettings::App ).toString();
1224 QString loadFileName = QFileDialog::getOpenFileName( this, tr( "Import User Expressions" ), lastImportDir, tr( "User expressions" ) + " (*.json)" );
1225
1226 if ( loadFileName.isEmpty() )
1227 return;
1228
1229 QFileInfo loadFileInfo( loadFileName );
1230
1231 settings.setValue( u"lastImportExpressionsDir"_s, loadFileInfo.absolutePath(), QgsSettings::App );
1232
1233 QFile jsonFile( loadFileName );
1234
1235 if ( !jsonFile.open( QFile::ReadOnly ) )
1236 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1237
1238 QTextStream jsonStream( &jsonFile );
1239 QString jsonString = jsonFile.readAll();
1240 jsonFile.close();
1241
1242 QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1243
1244 if ( importJson.isNull() )
1245 {
1246 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1247 return;
1248 }
1249
1250 mExpressionTreeView->loadExpressionsFromJson( importJson );
1251}
1252
1253
1254const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1255{
1256 return mExpressionTreeView->findExpressions( label );
1257}
1258
1259void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1260{
1261 if ( state & Qt::ControlModifier )
1262 {
1263 int position = txtExpressionString->positionFromLineIndex( line, index );
1264 long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1266 QString help = getFunctionHelp( func );
1267 txtHelpText->setText( help );
1268 }
1269}
1270
1271void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1272{
1273 clearErrors();
1274
1275 mExpressionValid = state;
1276 if ( state )
1277 {
1278 createMarkers( mExpressionPreviewWidget->rootNode() );
1279 }
1280 else
1281 {
1282 createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1283 }
1284}
1285
1286QString QgsExpressionBuilderWidget::helpStylesheet() const
1287{
1288 //start with default QGIS report style
1289 QString style = QgsApplication::reportStyleSheet();
1290
1291 //add some tweaks
1292 style += " .functionname {color: #0a6099; font-weight: bold;} "
1293 " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1294 " td.argument { padding-right: 10px; }";
1295
1296 return style;
1297}
1298
1299QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1300{
1301 if ( !expressionItem )
1302 return QString();
1303
1304 QString helpContents = expressionItem->getHelpText();
1305
1306 // Return the function help that is set for the function if there is one.
1307 if ( helpContents.isEmpty() )
1308 {
1309 QString name = expressionItem->data( Qt::UserRole ).toString();
1310
1311 if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1312 helpContents = QgsExpression::helpText( u"Field"_s );
1313 else
1314 helpContents = QgsExpression::helpText( name );
1315 }
1316
1317 return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1318}
1319
1320
1321// *************
1322// Menu provider
1323
1324QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1325{
1326 QMenu *menu = nullptr;
1327 QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1328 if ( item->getItemType() == QgsExpressionItem::Field && layer )
1329 {
1330 menu = new QMenu( mExpressionBuilderWidget );
1331 menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1332 menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1333
1335 {
1336 menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1337 menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1338 }
1339 }
1340 return menu;
1341}
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 showEvent(QShowEvent *e) override
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:739
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:113
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:7621
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7620
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6982
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.