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