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