QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <QTextStream>
52#include <QVersionNumber>
53
54#include "moc_qgsexpressionbuilderwidget.cpp"
55
56bool formatterCanProvideAvailableValues( QgsVectorLayer *layer, const QString &fieldName )
57{
58 if ( layer )
59 {
60 const QgsFields fields = layer->fields();
61 int fieldIndex = fields.lookupField( fieldName );
62 if ( fieldIndex != -1 )
63 {
64 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
66
67 return ( formatter->flags() & QgsFieldFormatter::CanProvideAvailableValues );
68 }
69 }
70 return false;
71}
72
73
75 : QWidget( parent )
76 , mProject( QgsProject::instance() )
77{
78 setupUi( this );
79
80 txtExpressionString = new QgsCodeEditorExpression();
81 QgsCodeEditorWidget *codeEditorWidget = new QgsCodeEditorWidget( txtExpressionString );
82 QVBoxLayout *vl = new QVBoxLayout();
83 vl->setContentsMargins( 0, 0, 0, 0 );
84 vl->addWidget( codeEditorWidget );
85 mExpressionEditorContainer->setLayout( vl );
86
87 connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
88 connect( btnNewFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
89 connect( btnRemoveFile, &QPushButton::clicked, this, &QgsExpressionBuilderWidget::btnRemoveFile_pressed );
90 connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
91 connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
92 connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
93 connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
94 connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
95 connect( btnSaveExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::storeCurrentUserExpression );
96 connect( btnEditExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::editSelectedUserExpression );
97 connect( btnRemoveExpression, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::removeSelectedUserExpression );
98 connect( btnImportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::importUserExpressions_pressed );
99 connect( btnExportExpressions, &QToolButton::clicked, this, &QgsExpressionBuilderWidget::exportUserExpressions_pressed );
100 connect( btnClearEditor, &QToolButton::clicked, txtExpressionString, &QgsCodeEditorExpression::clear );
101 connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, mExpressionTreeView, &QgsExpressionTreeView::setSearchText );
102
103 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::toolTipChanged, txtExpressionString, &QgsCodeEditorExpression::setToolTip );
104 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::onExpressionParsed );
105 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, btnSaveExpression, &QToolButton::setEnabled );
106 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::expressionParsed, this, &QgsExpressionBuilderWidget::expressionParsed ); // signal-to-signal
107 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::parserErrorChanged, this, &QgsExpressionBuilderWidget::parserErrorChanged ); // signal-to-signal
108 connect( mExpressionPreviewWidget, &QgsExpressionPreviewWidget::evalErrorChanged, this, &QgsExpressionBuilderWidget::evalErrorChanged ); // signal-to-signal
109
110 connect( mExpressionTreeView, &QgsExpressionTreeView::expressionItemDoubleClicked, this, &QgsExpressionBuilderWidget::insertExpressionText );
111 connect( mExpressionTreeView, &QgsExpressionTreeView::currentExpressionItemChanged, this, &QgsExpressionBuilderWidget::expressionTreeItemChanged );
112
113 mExpressionTreeMenuProvider = new ExpressionTreeMenuProvider( this );
114 mExpressionTreeView->setMenuProvider( mExpressionTreeMenuProvider );
115
116 txtHelpText->setOpenExternalLinks( true );
117 mValueGroupBox->hide();
118 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
119
120 // Note: must be in sync with the json help file for UserGroup
121 mUserExpressionsGroupName = QgsExpression::group( QStringLiteral( "UserGroup" ) );
122
123 // Set icons for tool buttons
124 btnSaveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileSave.svg" ) ) );
125 btnEditExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "symbologyEdit.svg" ) ) );
126 btnRemoveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionDeleteSelected.svg" ) ) );
127 btnExportExpressions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionSharingExport.svg" ) ) );
128 btnImportExpressions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionSharingImport.svg" ) ) );
129 btnClearEditor->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileNew.svg" ) ) );
130
131 connect( btnLoadAll, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::loadAllValues );
132 connect( btnLoadSample, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::loadSampleValues );
133
134 const auto pushButtons { mOperatorsGroupBox->findChildren<QPushButton *>() };
135 for ( QPushButton *button : pushButtons )
136 {
137 connect( button, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
138 }
139
140 connect( btnCommentLinePushButton, &QAbstractButton::clicked, this, &QgsExpressionBuilderWidget::commentLinesClicked );
141
142 txtSearchEdit->setShowSearchIcon( true );
143 txtSearchEdit->setPlaceholderText( tr( "Search…" ) );
144
145 mValuesModel = std::make_unique<QStandardItemModel>();
146 mProxyValues = std::make_unique<QSortFilterProxyModel>();
147 mProxyValues->setSourceModel( mValuesModel.get() );
148 mValuesListView->setModel( mProxyValues.get() );
149 txtSearchEditValues->setShowSearchIcon( true );
150 txtSearchEditValues->setPlaceholderText( tr( "Search…" ) );
151
152 editorSplit->setSizes( QList<int>( { 175, 300 } ) );
153
154 functionsplit->setCollapsible( 0, false );
155 connect( mShowHelpButton, &QPushButton::clicked, this, [this]() {
156 functionsplit->setSizes( QList<int>( { mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(), mHelpAndValuesWidget->minimumWidth() } ) );
157 mShowHelpButton->setEnabled( false );
158 } );
159 connect( functionsplit, &QSplitter::splitterMoved, this, [this]( int, int ) {
160 mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
161 } );
162
163 QgsSettings settings;
164 splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
165 editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
166 functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
167 mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
168
170 {
171 QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
172 updateFunctionFileList( mFunctionsPath );
173 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
174 }
175 else
176 {
177 tab_2->hide();
178 }
179
180 txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
181 lblAutoSave->clear();
182
183 // Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
184 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
185 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
186 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionInvalidParams );
187 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionNamedArgsError );
188#if defined( QSCINTILLA_VERSION ) && QSCINTILLA_VERSION >= 0x20a00
189 txtExpressionString->indicatorDefine( QgsCodeEditor::TriangleIndicator, QgsExpression::ParserError::Unknown );
190#else
191 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::Unknown );
192#endif
193
194 // Set all the error markers as red. -1 is all.
195 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
196 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
197 txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
198
199 // Hidden function markers.
200 txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
201 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
202 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
203 txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
204
205 connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
206 txtExpressionString->setAutoCompletionCaseSensitivity( false );
207 txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
208 txtExpressionString->setCallTipsVisible( 0 );
209
210 setExpectedOutputFormat( QString() );
211 mFunctionBuilderHelp->setLineNumbersVisible( false );
212 mFunctionBuilderHelp->setFoldingVisible( false );
213 mFunctionBuilderHelp->setEdgeMode( QsciScintilla::EdgeNone );
214 mFunctionBuilderHelp->setEdgeColumn( 0 );
215 mFunctionBuilderHelp->setReadOnly( true );
216 mFunctionBuilderHelp->setText( tr( "\"\"\"Define a new function using the @qgsfunction decorator.\n\
217\n\
218 Besides its normal arguments, the function may specify the following arguments in its signature\n\
219 Those will not need to be specified when calling the function, but will be automatically injected \n\
220\n\
221 : param feature: The current feature\n\
222 : param parent: The QgsExpression object\n\
223 : param context: ``QgsExpressionContext`` object, that gives access to various additional information like\n\
224 expression variables. E.g. ``context.variable( 'layer_id' )``\n\
225 : returns: The result of the expression.\n\
226\n\
227\n\
228\n\
229 The @qgsfunction decorator accepts the following arguments:\n\
230\n\
231\n\
232 : param group: The name of the group under which this expression function will\n\
233 be listed.\n\
234 : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
235 If False, the result will always be NULL as soon as any parameter is NULL.\n\
236 Defaults to False.\n\
237 : param usesgeometry : Set this to True if your function requires access to\n\
238 feature.geometry(). Defaults to False.\n\
239 : param referenced_columns: An array of attribute names that are required to run\n\
240 this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
241 : param params_as_list : Set this to True to pass the function parameters as a list. Can be used to mimic \n\
242 behavior before 3.32, when args was not \"auto\". Defaults to False.\n\
243\"\"\"" ) );
244}
245
246
248{
249 QgsSettings settings;
250 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
251 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
252 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
253 delete mExpressionTreeMenuProvider;
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 ) == QLatin1String( "project" ) )
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( QLatin1String( ".py" ) ) )
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#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
399 myFileStream.setCodec( "UTF-8" );
400#endif
401 myFileStream << txtPython->text() << Qt::endl;
402 myFile.close();
403 }
404}
405
407{
408 mProject->writeEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), txtPython->text() );
409}
410
412{
413 mFunctionsPath = path;
414 QDir dir( path );
415 dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
416 QStringList files = dir.entryList( QDir::Files );
417 cmbFileNames->clear();
418 const auto constFiles = files;
419 for ( const QString &name : constFiles )
420 {
421 QFileInfo info( mFunctionsPath + QDir::separator() + name );
422 if ( info.baseName() == QLatin1String( "__init__" ) )
423 continue;
424 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), info.baseName() );
425 cmbFileNames->addItem( item );
426 }
427
428 bool ok = false;
429 mProject->readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), QString(), &ok );
430 if ( ok )
431 {
432 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconQgsProjectFile.svg" ) ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
433 item->setData( Qt::UserRole, QStringLiteral( "project" ) );
434 cmbFileNames->insertItem( 0, item );
435 }
436
437 if ( !cmbFileNames->currentItem() )
438 {
439 cmbFileNames->setCurrentRow( 0 );
440 }
441
442 if ( cmbFileNames->count() == 0 )
443 {
444 // Create default sample entry.
445 newFunctionFile( QStringLiteral( "default" ) );
446 txtPython->setText( QStringLiteral( "'''\n#Sample custom function file\n"
447 "#(uncomment to use and customize or Add button to create a new file) \n%1 \n '''" )
448 .arg( txtPython->text() ) );
449 saveFunctionFile( QStringLiteral( "default" ) );
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( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
460 cmbFileNames->insertItem( 0, item );
461 cmbFileNames->setCurrentRow( 0 );
462
463 QString templateText;
464 QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), 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( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), 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( QStringLiteral( "mIconQgsProjectFile.svg" ) ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
484 item->setData( Qt::UserRole, QStringLiteral( "project" ) );
485 cmbFileNames->insertItem( 0, item );
486 cmbFileNames->setCurrentRow( 0 );
487
488 QString templateText;
489 QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), 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 ) == QLatin1String( "project" ) )
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 ) == QMessageBox::No )
506 return;
507
508 mProject->removeEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ) );
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 ) == QLatin1String( "project" ) )
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 ) != QLatin1String( "project" ) )
553 {
554 QString filename = lastitem->text();
555 saveFunctionFile( filename );
556 }
557 }
558
559 if ( item->data( Qt::UserRole ) == QLatin1String( "project" ) )
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( "Loads or updates functions from current script file in QGIS.\n"
568 "\n"
569 "Note the functions will only be stored when saving the project." ) );
570 }
571 else
572 {
573 QString path = mFunctionsPath + QDir::separator() + item->text();
574 loadCodeFromFile( path );
575 if ( mAutoSave )
576 {
577 btnRun->setText( tr( "Load or update functions" ) );
578 btnRun->setToolTip( tr( "Loads or updates functions from current script file in QGIS.\n"
579 "\n"
580 "Saved scripts are auto loaded on QGIS startup." ) );
581 }
582 else
583 {
584 btnRun->setText( tr( "Save and Load Functions" ) );
585 btnRun->setToolTip( tr( "Saves current script file and loads or updates its functions in QGIS.\n"
586 "\n"
587 "Saved scripts are auto loaded on QGIS startup." ) );
588 }
589 }
590}
591
593{
594 if ( !path.endsWith( QLatin1String( ".py" ) ) )
595 path.append( ".py" );
596
597 txtPython->loadScript( path );
598}
599
601{
602 loadFunctionCode( mProject->readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ) ) );
603}
604
606{
607 txtPython->setText( code );
608}
609
610void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
611{
612 // Insert the expression text or replace selected text
613 txtExpressionString->insertText( text );
614 txtExpressionString->setFocus();
615}
616
617void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
618{
619 Q_UNUSED( fieldValues )
620 // This is not maintained and setLayer() should be used instead.
621}
622
623void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, QgsVectorLayer *layer, int countLimit, bool forceUsedValues )
624{
625 // TODO We should really return a error the user of the widget that
626 // the there is no layer set.
627 if ( !layer )
628 return;
629
630 // TODO We should thread this so that we don't hold the user up if the layer is massive.
631
632 const QgsFields fields = layer->fields();
633 int fieldIndex = fields.lookupField( fieldName );
634
635 if ( fieldIndex < 0 )
636 return;
637
638 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
640
641 QVariantList values;
642 if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
643 {
644 QgsFieldFormatterContext fieldFormatterContext;
645 fieldFormatterContext.setProject( mProject );
646 values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
647 }
648 else
649 {
650 values = qgis::setToList( layer->uniqueValues( fieldIndex, countLimit ) );
651 }
652 std::sort( values.begin(), values.end() );
653
654 mValuesModel->clear();
655 for ( const QVariant &value : std::as_const( values ) )
656 {
657 QString strValue;
658 bool forceRepresentedValue = false;
659 if ( QgsVariantUtils::isNull( value ) )
660 strValue = QStringLiteral( "NULL" );
661 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
662 strValue = value.toString();
663 else if ( value.userType() == QMetaType::Type::QStringList )
664 {
665 QString result;
666 const QStringList strList = value.toStringList();
667 for ( QString str : strList )
668 {
669 if ( !result.isEmpty() )
670 result.append( QStringLiteral( ", " ) );
671
672 result.append( '\'' + str.replace( '\'', QLatin1String( "''" ) ) + '\'' );
673 }
674 strValue = QStringLiteral( "array(%1)" ).arg( result );
675 forceRepresentedValue = true;
676 }
677 else if ( value.userType() == QMetaType::Type::QVariantList )
678 {
679 QString result;
680 const QList list = value.toList();
681 for ( const QVariant &item : list )
682 {
683 if ( !result.isEmpty() )
684 result.append( QStringLiteral( ", " ) );
685
686 result.append( item.toString() );
687 }
688 strValue = QStringLiteral( "array(%1)" ).arg( result );
689 forceRepresentedValue = true;
690 }
691 else
692 strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
693
694 QString representedValue = formatter->representValue( layer, fieldIndex, setup.config(), QVariant(), value );
695 if ( forceRepresentedValue || representedValue != value.toString() )
696 representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
697
698 QStandardItem *item = new QStandardItem( representedValue );
699 item->setData( strValue );
700 mValuesModel->appendRow( item );
701 }
702}
703
704QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
705{
706 if ( !function )
707 return QString();
708
709 QString helpContents = QgsExpression::helpText( function->name() );
710
711 return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
712}
713
714
716{
717 return mExpressionValid;
718}
719
720void QgsExpressionBuilderWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant>> &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
721{
722 mExpressionPreviewWidget->setCustomPreviewGenerator( label, choices, previewContextGenerator );
723}
724
725void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
726{
727 mExpressionTreeView->saveToRecent( expressionText(), collection );
728}
729
730void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
731{
732 mExpressionTreeView->loadRecent( collection );
733}
734
736{
737 return mExpressionTreeView;
738}
739
740// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
742{
743 mExpressionTreeView->loadUserExpressions();
744}
745
746void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
747{
748 mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
749}
750
752{
753 mExpressionTreeView->removeFromUserExpressions( label );
754}
755
756
758{
759 mExpressionPreviewWidget->setGeomCalculator( da );
760}
761
763{
764 return txtExpressionString->text();
765}
766
767void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
768{
769 txtExpressionString->setText( expression );
770}
771
773{
774 return lblExpected->text();
775}
776
778{
779 lblExpected->setText( expected );
780 mExpectedOutputFrame->setVisible( !expected.isNull() );
781}
782
784{
785 mExpressionContext = context;
786 expressionContextUpdated();
787}
788
789void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
790{
791 QString text = expressionText();
792
793 btnClearEditor->setEnabled( !txtExpressionString->text().isEmpty() );
794 btnSaveExpression->setEnabled( false );
795
796 mExpressionPreviewWidget->setExpressionText( text );
797}
798
800{
801 return mExpressionPreviewWidget->parserError();
802}
803
805{
806 mExpressionPreviewWidget->setVisible( isVisible );
807}
808
810{
811 return mExpressionPreviewWidget->evalError();
812}
813
815{
817 return mExpressionTreeView->model();
819}
820
822{
823 return mProject;
824}
825
827{
828 mProject = project;
829 mExpressionTreeView->setProject( project );
830}
831
833{
834 QWidget::showEvent( e );
835 txtExpressionString->setFocus();
836}
837
838void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
839{
840 clearErrors();
841 for ( const QgsExpression::ParserError &error : errors )
842 {
843 int errorFirstLine = error.firstLine - 1;
844 int errorFirstColumn = error.firstColumn - 1;
845 int errorLastColumn = error.lastColumn - 1;
846 int errorLastLine = error.lastLine - 1;
847
848 // If we have a unknown error we just mark the point that hit the error for now
849 // until we can handle others more.
850 if ( error.errorType == QgsExpression::ParserError::Unknown )
851 {
852 errorFirstLine = errorLastLine;
853 errorFirstColumn = errorLastColumn - 1;
854 }
855 txtExpressionString->fillIndicatorRange( errorFirstLine, errorFirstColumn, errorLastLine, errorLastColumn, error.errorType );
856 }
857}
858
859void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
860{
861 switch ( inNode->nodeType() )
862 {
864 {
865 const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
866 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
867 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
868 int start = inNode->parserFirstColumn - 1;
869 int end = inNode->parserLastColumn - 1;
870 int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
871 txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
872 if ( node->args() )
873 {
874 const QList<QgsExpressionNode *> nodeList = node->args()->list();
875 for ( QgsExpressionNode *n : nodeList )
876 {
877 createMarkers( n );
878 }
879 }
880 break;
881 }
883 {
884 break;
885 }
887 {
888 const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
889 createMarkers( node->operand() );
890 break;
891 }
893 {
894 const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
895 createMarkers( node->lowerBound() );
896 createMarkers( node->higherBound() );
897 break;
898 }
900 {
901 const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
902 createMarkers( node->opLeft() );
903 createMarkers( node->opRight() );
904 break;
905 }
907 {
908 break;
909 }
911 {
912 const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
913 if ( node->list() )
914 {
915 const QList<QgsExpressionNode *> nodeList = node->list()->list();
916 for ( QgsExpressionNode *n : nodeList )
917 {
918 createMarkers( n );
919 }
920 }
921 break;
922 }
924 {
925 const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
926 const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
927 for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
928 {
929 createMarkers( cond->whenExp() );
930 createMarkers( cond->thenExp() );
931 }
932 if ( node->elseExp() )
933 {
934 createMarkers( node->elseExp() );
935 }
936 break;
937 }
939 {
940 break;
941 }
942 }
943}
944
945void QgsExpressionBuilderWidget::clearFunctionMarkers()
946{
947 int lastLine = txtExpressionString->lines() - 1;
948 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
949}
950
951void QgsExpressionBuilderWidget::clearErrors()
952{
953 int lastLine = txtExpressionString->lines() - 1;
954 // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
955 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
956 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
957 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
958 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
959 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
960}
961
962void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
963{
964 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
965 mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
966}
967
968void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
969{
970 // Insert the item text or replace selected text
971 txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
972 txtExpressionString->setFocus();
973}
974
975void QgsExpressionBuilderWidget::operatorButtonClicked()
976{
977 QPushButton *button = qobject_cast<QPushButton *>( sender() );
978
979 // Insert the button text or replace selected text
980 txtExpressionString->insertText( ' ' + button->text() + ' ' );
981 txtExpressionString->setFocus();
982}
983
984void QgsExpressionBuilderWidget::commentLinesClicked()
985{
986 txtExpressionString->toggleComment();
987}
988
990{
991 QgsExpressionItem *item = mExpressionTreeView->currentItem();
992 if ( !item )
993 {
994 return;
995 }
996
997 QgsVectorLayer *layer { contextLayer( item ) };
998 // TODO We should really return a error the user of the widget that
999 // the there is no layer set.
1000 if ( !layer )
1001 {
1002 return;
1003 }
1004
1005 mValueGroupBox->show();
1006 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
1007}
1008
1010{
1011 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1012 if ( !item )
1013 {
1014 return;
1015 }
1016
1017 QgsVectorLayer *layer { contextLayer( item ) };
1018 // TODO We should really return a error the user of the widget that
1019 // the there is no layer set.
1020 if ( !layer )
1021 {
1022 return;
1023 }
1024
1025 mValueGroupBox->show();
1026 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
1027}
1028
1030{
1031 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1032 if ( !item )
1033 {
1034 return;
1035 }
1036
1037 QgsVectorLayer *layer { contextLayer( item ) };
1038 // TODO We should really return a error the user of the widget that
1039 // the there is no layer set.
1040 if ( !layer )
1041 {
1042 return;
1043 }
1044
1045 mValueGroupBox->show();
1046 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
1047}
1048
1050{
1051 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1052 if ( !item )
1053 {
1054 return;
1055 }
1056
1057 QgsVectorLayer *layer { contextLayer( item ) };
1058 // TODO We should really return a error the user of the widget that
1059 // the there is no layer set.
1060 if ( !layer )
1061 {
1062 return;
1063 }
1064
1065 mValueGroupBox->show();
1066 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
1067}
1068
1069void QgsExpressionBuilderWidget::txtPython_textChanged()
1070{
1071 if ( tabWidget->currentIndex() != 1 )
1072 return;
1073
1074 QListWidgetItem *item = cmbFileNames->currentItem();
1075 if ( !item )
1076 return;
1077
1078 if ( item->data( Qt::UserRole ) == QLatin1String( "project" ) )
1079 {
1080 saveProjectFunctionsEntry(); // Makes project dirty
1081 displayTemporaryLabel( tr( "Project changed" ) );
1082 }
1083 else if ( mAutoSave )
1084 {
1085 autosave();
1086 }
1087}
1088
1090{
1091 // Don't auto save if not on function editor that would be silly.
1092 if ( tabWidget->currentIndex() != 1 )
1093 return;
1094
1095 QListWidgetItem *item = cmbFileNames->currentItem();
1096 if ( !item )
1097 return;
1098
1099 if ( item->data( Qt::UserRole ) != QLatin1String( "project" ) )
1100 {
1101 QString file = item->text();
1102 saveFunctionFile( file );
1103 displayTemporaryLabel( tr( "Function file saved" ) );
1104 }
1105}
1106
1107void QgsExpressionBuilderWidget::displayTemporaryLabel( const QString &text )
1108{
1109 lblAutoSave->setText( text );
1110 QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
1111 lblAutoSave->setGraphicsEffect( effect );
1112 QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
1113 anim->setDuration( 2000 );
1114 anim->setStartValue( 1.0 );
1115 anim->setEndValue( 0.0 );
1116 anim->setEasingCurve( QEasingCurve::OutQuad );
1117 anim->start( QAbstractAnimation::DeleteWhenStopped );
1118}
1119
1121{
1122 const QString expression { this->expressionText() };
1123 QgsExpressionStoreDialog dlg { expression, expression, QString(), mExpressionTreeView->userExpressionLabels() };
1124 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1125 {
1126 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1127 }
1128}
1129
1131{
1132 // Get the item
1133 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1134 if ( !item )
1135 return;
1136
1137 // Don't handle remove if we are on a header node or the parent
1138 // is not the user group
1139 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1140 return;
1141
1142 QgsSettings settings;
1143 QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1144 QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1145
1146 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1147 {
1148 // label has changed removed the old one before adding the new one
1149 if ( dlg.isLabelModified() )
1150 {
1151 mExpressionTreeView->removeFromUserExpressions( item->text() );
1152 }
1153
1154 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1155 }
1156}
1157
1159{
1160 // Get the item
1161 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1162
1163 if ( !item )
1164 return;
1165
1166 // Don't handle remove if we are on a header node or the parent
1167 // is not the user group
1168 if ( item->getItemType() == QgsExpressionItem::Header || ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1169 return;
1170
1171 if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ), tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ), QMessageBox::Yes | QMessageBox::No ) )
1172 {
1173 mExpressionTreeView->removeFromUserExpressions( item->text() );
1174 }
1175}
1176
1177void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1178{
1179 QgsSettings settings;
1180 QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1181 QString saveFileName = QFileDialog::getSaveFileName(
1182 this,
1183 tr( "Export User Expressions" ),
1184 lastSaveDir,
1185 tr( "User expressions" ) + " (*.json)"
1186 );
1187
1188 // return dialog focus on Mac
1189 activateWindow();
1190 raise();
1191 if ( saveFileName.isEmpty() )
1192 return;
1193
1194 QFileInfo saveFileInfo( saveFileName );
1195
1196 if ( saveFileInfo.suffix().isEmpty() )
1197 {
1198 QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1199 saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1200 }
1201
1202 settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
1203
1204 QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1205 QFile jsonFile( saveFileName );
1206
1207 if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1208 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1209
1210 if ( !jsonFile.write( exportJson.toJson() ) )
1211 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1212 else
1213 jsonFile.close();
1214}
1215
1216void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1217{
1218 QgsSettings settings;
1219 QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1220 QString loadFileName = QFileDialog::getOpenFileName(
1221 this,
1222 tr( "Import User Expressions" ),
1223 lastImportDir,
1224 tr( "User expressions" ) + " (*.json)"
1225 );
1226
1227 if ( loadFileName.isEmpty() )
1228 return;
1229
1230 QFileInfo loadFileInfo( loadFileName );
1231
1232 settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), 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( QStringLiteral( "Field" ) );
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 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 init(const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize without any layer.
void loadFunctionCode(const QString &code)
Loads code into the function editor.
Q_DECL_DEPRECATED QStandardItemModel * model()
Returns a pointer to the dialog's function item model.
QgsExpressionTreeView * expressionTree() const
Returns the expression tree.
void evalErrorChanged()
Will be set to true if the current expression text reported an eval error with the context.
bool isExpressionValid()
Returns if the expression is valid.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
void loadCodeFromFile(QString path)
Loads code from the given file into the function editor.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
bool parserError() const
Will be set to true if the current expression text reports a parser error with the context.
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.
Q_DECL_DEPRECATED void loadRecent(const QString &collection=QStringLiteral("generic"))
Loads the recent expressions from the given collection.
void setExpressionPreviewVisible(bool isVisible)
Sets whether the expression preview is visible.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
bool evalError() const
Will be set to true if the current expression text reported an eval error with the context.
void storeCurrentUserExpression()
Adds the current expressions to the stored user expressions.
void initWithLayer(QgsVectorLayer *layer, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with a layer.
void updateFunctionFileList(const QString &path)
Updates the list of function files found at the given path.
void showEvent(QShowEvent *e) override
void initWithFields(const QgsFields &fields, const QgsExpressionContext &context=QgsExpressionContext(), const QString &recentCollection=QStringLiteral("generic"), QgsExpressionBuilderWidget::Flags flags=LoadAll)
Initialize with given fields without any layer.
void setExpectedOutputFormat(const QString &expected)
The set expected format string.
void removeSelectedUserExpression()
Removes the selected expression from the stored user expressions, the selected expression must be a u...
void newFunctionFile(const QString &fileName="scratch")
Creates a new file in the function editor.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the widget.
void loadAllUsedValues()
Load all unique values from the set layer into the sample area.
Q_DECL_DEPRECATED void saveToUserExpressions(const QString &label, const QString &expression, const QString &helpText)
Stores the user expression with given label and helpText.
const QList< QgsExpressionItem * > findExpressions(const QString &label)
Returns the list of expression items matching a label.
void editSelectedUserExpression()
Edits the selected expression from the stored user expressions, the selected expression must be a use...
QgsVectorLayer * layer() const
Returns the current layer or a nullptr.
QgsExpressionBuilderWidget(QWidget *parent=nullptr)
Create a new expression builder widget with an optional parent.
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
void loadAllValues()
Load all unique values from the set layer into the sample area.
void autosave()
Auto save the current Python function code.
void loadSampleUsedValues()
Load used sample values into the sample value area.
void saveFunctionFile(QString fileName)
Saves the current function editor text to the given file.
void saveProjectFunctionsEntry()
Writes the current function editor text to a project entry.
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:747
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:109
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:65
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:7170
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6511
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.