QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 {
154 functionsplit->setSizes( QList<int>( {mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(),
155 mHelpAndValuesWidget->minimumWidth()
156 } ) );
157 mShowHelpButton->setEnabled( false );
158 } );
159 connect( functionsplit, &QSplitter::splitterMoved, this, [ = ]( int, int )
160 {
161 mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
162 } );
163
164 QgsSettings settings;
165 splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
166 editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
167 functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
168 mShowHelpButton->setEnabled( functionsplit->sizes().at( 1 ) == 0 );
169
171 {
172 QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
173 updateFunctionFileList( mFunctionsPath );
174 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
175 }
176 else
177 {
178 tab_2->hide();
179 }
180
181 txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
182 lblAutoSave->clear();
183
184 // Note: If you add a indicator here you should add it to clearErrors method if you need to clear it on text parse.
185 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionUnknown );
186 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionWrongArgs );
187 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionInvalidParams );
188 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::FunctionNamedArgsError );
189#if defined(QSCINTILLA_VERSION) && QSCINTILLA_VERSION >= 0x20a00
190 txtExpressionString->indicatorDefine( QgsCodeEditor::TriangleIndicator, QgsExpression::ParserError::Unknown );
191#else
192 txtExpressionString->indicatorDefine( QgsCodeEditor::SquiggleIndicator, QgsExpression::ParserError::Unknown );
193#endif
194
195 // Set all the error markers as red. -1 is all.
196 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::red ), -1 );
197 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::red ), -1 );
198 txtExpressionString->setIndicatorOutlineColor( QColor( Qt::red ), -1 );
199
200 // Hidden function markers.
201 txtExpressionString->indicatorDefine( QgsCodeEditor::HiddenIndicator, FUNCTION_MARKER_ID );
202 txtExpressionString->setIndicatorForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
203 txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
204 txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
205
206 connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
207 txtExpressionString->setAutoCompletionCaseSensitivity( false );
208 txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
209 txtExpressionString->setCallTipsVisible( 0 );
210
211 setExpectedOutputFormat( QString() );
212 mFunctionBuilderHelp->setLineNumbersVisible( false );
213 mFunctionBuilderHelp->setFoldingVisible( false );
214 mFunctionBuilderHelp->setEdgeMode( QsciScintilla::EdgeNone );
215 mFunctionBuilderHelp->setEdgeColumn( 0 );
216 mFunctionBuilderHelp->setReadOnly( true );
217 mFunctionBuilderHelp->setText( tr( "\"\"\"Define a new function using the @qgsfunction decorator.\n\
218\n\
219 Besides its normal arguments, the function may specify the following arguments in its signature\n\
220 Those will not need to be specified when calling the function, but will be automatically injected \n\
221\n\
222 : param feature: The current feature\n\
223 : param parent: The QgsExpression object\n\
224 : param context: ``QgsExpressionContext`` object, that gives access to various additional information like\n\
225 expression variables. E.g. ``context.variable( 'layer_id' )``\n\
226 : returns: The result of the expression.\n\
227\n\
228\n\
229\n\
230 The @qgsfunction decorator accepts the following arguments:\n\
231\n\
232\n\
233 : param group: The name of the group under which this expression function will\n\
234 be listed.\n\
235 : param handlesnull: Set this to True if your function has custom handling for NULL values.\n\
236 If False, the result will always be NULL as soon as any parameter is NULL.\n\
237 Defaults to False.\n\
238 : param usesgeometry : Set this to True if your function requires access to\n\
239 feature.geometry(). Defaults to False.\n\
240 : param referenced_columns: An array of attribute names that are required to run\n\
241 this function. Defaults to [QgsFeatureRequest.ALL_ATTRIBUTES].\n\
242 : param params_as_list : Set this to True to pass the function parameters as a list. Can be used to mimic \n\
243 behavior before 3.32, when args was not \"auto\". Defaults to False.\n\
244\"\"\"" ) );
245}
246
247
249{
250 QgsSettings settings;
251 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
252 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
253 settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
254 delete mExpressionTreeMenuProvider;
255}
256
257void QgsExpressionBuilderWidget::init( const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
258{
259 setExpressionContext( context );
260
261 if ( flags.testFlag( LoadRecent ) )
262 mExpressionTreeView->loadRecent( recentCollection );
263
264 if ( flags.testFlag( LoadUserExpressions ) )
265 mExpressionTreeView->loadUserExpressions();
266}
267
269{
270 init( context, recentCollection, flags );
271 setLayer( layer );
272}
273
274void QgsExpressionBuilderWidget::initWithFields( const QgsFields &fields, const QgsExpressionContext &context, const QString &recentCollection, QgsExpressionBuilderWidget::Flags flags )
275{
276 init( context, recentCollection, flags );
277 mExpressionTreeView->loadFieldNames( fields );
278}
279
280
282{
283 mLayer = layer;
284 mExpressionTreeView->setLayer( mLayer );
285 mExpressionPreviewWidget->setLayer( mLayer );
286
287 //TODO - remove existing layer scope from context
288
289 if ( mLayer )
290 {
291 mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
292 expressionContextUpdated();
293 txtExpressionString->setFields( mLayer->fields() );
294 }
295}
296
297void QgsExpressionBuilderWidget::expressionContextUpdated()
298{
299 txtExpressionString->setExpressionContext( mExpressionContext );
300 mExpressionTreeView->setExpressionContext( mExpressionContext );
301 mExpressionPreviewWidget->setExpressionContext( mExpressionContext );
302}
303
305{
306 return mLayer;
307}
308
309void QgsExpressionBuilderWidget::expressionTreeItemChanged( QgsExpressionItem *item )
310{
311 txtSearchEditValues->clear();
312
313 if ( !item )
314 return;
315
316 bool isField = mLayer && item->getItemType() == QgsExpressionItem::Field;
317 if ( isField )
318 {
319 mValuesModel->clear();
320
321 cbxValuesInUse->setVisible( formatterCanProvideAvailableValues( mLayer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) );
322 cbxValuesInUse->setChecked( false );
323 }
324 mValueGroupBox->setVisible( isField );
325
326 mShowHelpButton->setText( isField ? tr( "Show Values" ) : tr( "Show Help" ) );
327
328 // Show the help for the current item.
329 QString help = loadFunctionHelp( item );
330 txtHelpText->setText( help );
331
332 bool isUserExpression = item->parent() && item->parent()->text() == mUserExpressionsGroupName;
333
334 btnRemoveExpression->setEnabled( isUserExpression );
335 btnEditExpression->setEnabled( isUserExpression );
336}
337
338void QgsExpressionBuilderWidget::btnRun_pressed()
339{
340 if ( !cmbFileNames->currentItem() )
341 return;
342
343 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == QLatin1String( "project" ) )
344 {
346 }
347 else
348 {
349 QString file = cmbFileNames->currentItem()->text();
350 saveFunctionFile( file );
351 }
352
353 runPythonCode( txtPython->text() );
354}
355
356void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
357{
359 {
360 QString pythontext = code;
361 QgsPythonRunner::run( pythontext );
362 }
363 mExpressionTreeView->refresh();
364}
365
366QgsVectorLayer *QgsExpressionBuilderWidget::contextLayer( const QgsExpressionItem *item ) const
367{
368 QgsVectorLayer *layer = nullptr;
370 {
371 layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( item->data( QgsExpressionItem::LAYER_ID_ROLE ).toString() ) );
372 }
373 else
374 {
375 layer = mLayer;
376 }
377 return layer;
378}
379
380
382{
383 QDir myDir( mFunctionsPath );
384 if ( !myDir.exists() )
385 {
386 myDir.mkpath( mFunctionsPath );
387 }
388
389 if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
390 {
391 fileName.append( ".py" );
392 }
393
394 fileName = mFunctionsPath + QDir::separator() + fileName;
395 QFile myFile( fileName );
396 if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
397 {
398 QTextStream myFileStream( &myFile );
399#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
400 myFileStream.setCodec( "UTF-8" );
401#endif
402 myFileStream << txtPython->text() << Qt::endl;
403 myFile.close();
404 }
405}
406
408{
409 mProject->writeEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), txtPython->text() );
410}
411
413{
414 mFunctionsPath = path;
415 QDir dir( path );
416 dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
417 QStringList files = dir.entryList( QDir::Files );
418 cmbFileNames->clear();
419 const auto constFiles = files;
420 for ( const QString &name : constFiles )
421 {
422 QFileInfo info( mFunctionsPath + QDir::separator() + name );
423 if ( info.baseName() == QLatin1String( "__init__" ) ) 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 '''" ).arg( txtPython->text() ) );
448 saveFunctionFile( QStringLiteral( "default" ) );
449 }
450}
451
452void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
453{
454 QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
455 if ( !items.isEmpty() )
456 return;
457
458 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.svg" ) ), fileName );
459 cmbFileNames->insertItem( 0, item );
460 cmbFileNames->setCurrentRow( 0 );
461
462 QString templateText;
463 QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templateText );
464 txtPython->setText( templateText );
465 saveFunctionFile( fileName );
466}
467
468void QgsExpressionBuilderWidget::btnNewFile_pressed()
469{
470 // If a project has an entry for functions, then we should
471 // already have a 'Project functions' item in the file list.
472 // Since only one item should correspond to 'Project functions',
473 // we'll disable this option in the 'add function file' dialog.
474 bool ok = false;
475 mProject->readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), QString(), &ok );
476
478 if ( dlg.exec() == QDialog::DialogCode::Accepted )
479 {
480 if ( dlg.createProjectFunctions() )
481 {
482 QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconQgsProjectFile.svg" ) ), DEFAULT_PROJECT_FUNCTIONS_ITEM_NAME );
483 item->setData( Qt::UserRole, QStringLiteral( "project" ) );
484 cmbFileNames->insertItem( 0, item );
485 cmbFileNames->setCurrentRow( 0 );
486
487 QString templateText;
488 QgsPythonRunner::eval( QStringLiteral( "qgis.user.default_expression_template" ), templateText );
489 txtPython->setText( templateText );
491 }
492 else
493 {
494 newFunctionFile( dlg.fileName() );
495 }
496 btnRemoveFile->setEnabled( cmbFileNames->count() > 0 );
497 }
498}
499
500void QgsExpressionBuilderWidget::btnRemoveFile_pressed()
501{
502 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == QLatin1String( "project" ) )
503 {
504 if ( QMessageBox::question( this, tr( "Remove Project Functions" ),
505 tr( "Are you sure you want to remove the project functions?" ),
506 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
507 return;
508
509 mProject->removeEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ) );
510 }
511 else
512 {
513 if ( QMessageBox::question( this, tr( "Remove File" ),
514 tr( "Are you sure you want to remove current functions file?" ),
515 QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
516 return;
517
518 QString fileName = cmbFileNames->currentItem()->text();
519 if ( !QFile::remove( mFunctionsPath + QDir::separator() + fileName.append( ".py" ) ) )
520 {
521 QMessageBox::warning( this, tr( "Remove file" ), tr( "Failed to remove function file '%1'." ).arg( fileName ) );
522 return;
523 }
524 }
525
526 int currentRow = cmbFileNames->currentRow();
527 {
528 QListWidgetItem *itemToRemove = whileBlocking( cmbFileNames )->takeItem( currentRow );
529 delete itemToRemove;
530 }
531
532 if ( cmbFileNames->count() > 0 )
533 {
534 whileBlocking( cmbFileNames )->setCurrentRow( currentRow > 0 ? currentRow - 1 : 0 );
535 if ( cmbFileNames->currentItem()->data( Qt::UserRole ) == QLatin1String( "project" ) )
536 {
538 }
539 else
540 {
541 loadCodeFromFile( mFunctionsPath + QDir::separator() + cmbFileNames->currentItem()->text() );
542 }
543 }
544 else
545 {
546 btnRemoveFile->setEnabled( false );
547 txtPython->clear();
548 }
549
550}
551
552void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
553{
554 if ( lastitem )
555 {
556 if ( lastitem->data( Qt::UserRole ) == QLatin1String( "project" ) )
557 {
559 }
560 else
561 {
562 QString filename = lastitem->text();
563 saveFunctionFile( filename );
564 }
565 }
566
567 if ( item->data( Qt::UserRole ) == QLatin1String( "project" ) )
568 {
570 }
571 else
572 {
573 QString path = mFunctionsPath + QDir::separator() + item->text();
574 loadCodeFromFile( path );
575 }
576}
577
579{
580 if ( !path.endsWith( QLatin1String( ".py" ) ) )
581 path.append( ".py" );
582
583 txtPython->loadScript( path );
584}
585
587{
588 loadFunctionCode( mProject->readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ) ) );
589}
590
592{
593 txtPython->setText( code );
594}
595
596void QgsExpressionBuilderWidget::insertExpressionText( const QString &text )
597{
598 // Insert the expression text or replace selected text
599 txtExpressionString->insertText( text );
600 txtExpressionString->setFocus();
601}
602
603void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
604{
605 Q_UNUSED( fieldValues )
606 // This is not maintained and setLayer() should be used instead.
607}
608
609void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, QgsVectorLayer *layer, int countLimit, bool forceUsedValues )
610{
611 // TODO We should really return a error the user of the widget that
612 // the there is no layer set.
613 if ( !layer )
614 return;
615
616 // TODO We should thread this so that we don't hold the user up if the layer is massive.
617
618 const QgsFields fields = layer->fields();
619 int fieldIndex = fields.lookupField( fieldName );
620
621 if ( fieldIndex < 0 )
622 return;
623
624 const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup();
626
627 QVariantList values;
628 if ( cbxValuesInUse->isVisible() && !cbxValuesInUse->isChecked() && !forceUsedValues )
629 {
630 QgsFieldFormatterContext fieldFormatterContext;
631 fieldFormatterContext.setProject( mProject );
632 values = formatter->availableValues( setup.config(), countLimit, fieldFormatterContext );
633 }
634 else
635 {
636 values = qgis::setToList( layer->uniqueValues( fieldIndex, countLimit ) );
637 }
638 std::sort( values.begin(), values.end() );
639
640 mValuesModel->clear();
641 for ( const QVariant &value : std::as_const( values ) )
642 {
643 QString strValue;
644 bool forceRepresentedValue = false;
645 if ( QgsVariantUtils::isNull( value ) )
646 strValue = QStringLiteral( "NULL" );
647 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
648 strValue = value.toString();
649 else if ( value.userType() == QMetaType::Type::QStringList )
650 {
651 QString result;
652 const QStringList strList = value.toStringList();
653 for ( QString str : strList )
654 {
655 if ( !result.isEmpty() )
656 result.append( QStringLiteral( ", " ) );
657
658 result.append( '\'' + str.replace( '\'', QLatin1String( "''" ) ) + '\'' );
659 }
660 strValue = QStringLiteral( "array(%1)" ).arg( result );
661 forceRepresentedValue = true;
662 }
663 else if ( value.userType() == QMetaType::Type::QVariantList )
664 {
665 QString result;
666 const QList list = value.toList();
667 for ( const QVariant &item : list )
668 {
669 if ( !result.isEmpty() )
670 result.append( QStringLiteral( ", " ) );
671
672 result.append( item.toString() );
673 }
674 strValue = QStringLiteral( "array(%1)" ).arg( result );
675 forceRepresentedValue = true;
676 }
677 else
678 strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
679
680 QString representedValue = formatter->representValue( layer, fieldIndex, setup.config(), QVariant(), value );
681 if ( forceRepresentedValue || representedValue != value.toString() )
682 representedValue = representedValue + QStringLiteral( " [" ) + strValue + ']';
683
684 QStandardItem *item = new QStandardItem( representedValue );
685 item->setData( strValue );
686 mValuesModel->appendRow( item );
687 }
688}
689
690QString QgsExpressionBuilderWidget::getFunctionHelp( QgsExpressionFunction *function )
691{
692 if ( !function )
693 return QString();
694
695 QString helpContents = QgsExpression::helpText( function->name() );
696
697 return QStringLiteral( "<head><style>" ) + helpStylesheet() + QStringLiteral( "</style></head><body>" ) + helpContents + QStringLiteral( "</body>" );
698
699}
700
701
702
704{
705 return mExpressionValid;
706}
707
708void QgsExpressionBuilderWidget::setCustomPreviewGenerator( const QString &label, const QList<QPair<QString, QVariant> > &choices, const std::function<QgsExpressionContext( const QVariant & )> &previewContextGenerator )
709{
710 mExpressionPreviewWidget->setCustomPreviewGenerator( label, choices, previewContextGenerator );
711}
712
713void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
714{
715 mExpressionTreeView->saveToRecent( expressionText(), collection );
716}
717
718void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
719{
720 mExpressionTreeView->loadRecent( collection );
721}
722
724{
725 return mExpressionTreeView;
726}
727
728// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
730{
731 mExpressionTreeView->loadUserExpressions();
732}
733
734void QgsExpressionBuilderWidget::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
735{
736 mExpressionTreeView->saveToUserExpressions( label, expression, helpText );
737}
738
740{
741 mExpressionTreeView->removeFromUserExpressions( label );
742}
743
744
746{
747 mExpressionPreviewWidget->setGeomCalculator( da );
748}
749
751{
752 return txtExpressionString->text();
753}
754
755void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
756{
757 txtExpressionString->setText( expression );
758}
759
761{
762 return lblExpected->text();
763}
764
766{
767 lblExpected->setText( expected );
768 mExpectedOutputFrame->setVisible( !expected.isNull() );
769}
770
772{
773 mExpressionContext = context;
774 expressionContextUpdated();
775}
776
777void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
778{
779 QString text = expressionText();
780
781 btnClearEditor->setEnabled( ! txtExpressionString->text().isEmpty() );
782 btnSaveExpression->setEnabled( false );
783
784 mExpressionPreviewWidget->setExpressionText( text );
785}
786
788{
789 return mExpressionPreviewWidget->parserError();
790}
791
793{
794 mExpressionPreviewWidget->setVisible( isVisible );
795}
796
798{
799 return mExpressionPreviewWidget->evalError();
800}
801
803{
805 return mExpressionTreeView->model();
807}
808
810{
811 return mProject;
812}
813
815{
816 mProject = project;
817 mExpressionTreeView->setProject( project );
818}
819
821{
822 QWidget::showEvent( e );
823 txtExpressionString->setFocus();
824}
825
826void QgsExpressionBuilderWidget::createErrorMarkers( const QList<QgsExpression::ParserError> &errors )
827{
828 clearErrors();
829 for ( const QgsExpression::ParserError &error : errors )
830 {
831 int errorFirstLine = error.firstLine - 1 ;
832 int errorFirstColumn = error.firstColumn - 1;
833 int errorLastColumn = error.lastColumn - 1;
834 int errorLastLine = error.lastLine - 1;
835
836 // If we have a unknown error we just mark the point that hit the error for now
837 // until we can handle others more.
838 if ( error.errorType == QgsExpression::ParserError::Unknown )
839 {
840 errorFirstLine = errorLastLine;
841 errorFirstColumn = errorLastColumn - 1;
842 }
843 txtExpressionString->fillIndicatorRange( errorFirstLine,
844 errorFirstColumn,
845 errorLastLine,
846 errorLastColumn, error.errorType );
847 }
848}
849
850void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode )
851{
852 switch ( inNode->nodeType() )
853 {
855 {
856 const QgsExpressionNodeFunction *node = static_cast<const QgsExpressionNodeFunction *>( inNode );
857 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORCURRENT, FUNCTION_MARKER_ID );
858 txtExpressionString->SendScintilla( QsciScintilla::SCI_SETINDICATORVALUE, node->fnIndex() );
859 int start = inNode->parserFirstColumn - 1;
860 int end = inNode->parserLastColumn - 1;
861 int start_pos = txtExpressionString->positionFromLineIndex( inNode->parserFirstLine - 1, start );
862 txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORFILLRANGE, start_pos, end - start );
863 if ( node->args() )
864 {
865 const QList< QgsExpressionNode * > nodeList = node->args()->list();
866 for ( QgsExpressionNode *n : nodeList )
867 {
868 createMarkers( n );
869 }
870 }
871 break;
872 }
874 {
875 break;
876 }
878 {
879 const QgsExpressionNodeUnaryOperator *node = static_cast<const QgsExpressionNodeUnaryOperator *>( inNode );
880 createMarkers( node->operand() );
881 break;
882 }
884 {
885 const QgsExpressionNodeBetweenOperator *node = static_cast<const QgsExpressionNodeBetweenOperator *>( inNode );
886 createMarkers( node->lowerBound() );
887 createMarkers( node->higherBound() );
888 break;
889 }
891 {
892 const QgsExpressionNodeBinaryOperator *node = static_cast<const QgsExpressionNodeBinaryOperator *>( inNode );
893 createMarkers( node->opLeft() );
894 createMarkers( node->opRight() );
895 break;
896 }
898 {
899 break;
900 }
902 {
903 const QgsExpressionNodeInOperator *node = static_cast<const QgsExpressionNodeInOperator *>( inNode );
904 if ( node->list() )
905 {
906 const QList< QgsExpressionNode * > nodeList = node->list()->list();
907 for ( QgsExpressionNode *n : nodeList )
908 {
909 createMarkers( n );
910 }
911 }
912 break;
913 }
915 {
916 const QgsExpressionNodeCondition *node = static_cast<const QgsExpressionNodeCondition *>( inNode );
917 const QList<QgsExpressionNodeCondition::WhenThen *> conditions = node->conditions();
918 for ( QgsExpressionNodeCondition::WhenThen *cond : conditions )
919 {
920 createMarkers( cond->whenExp() );
921 createMarkers( cond->thenExp() );
922 }
923 if ( node->elseExp() )
924 {
925 createMarkers( node->elseExp() );
926 }
927 break;
928 }
930 {
931 break;
932 }
933 }
934}
935
936void QgsExpressionBuilderWidget::clearFunctionMarkers()
937{
938 int lastLine = txtExpressionString->lines() - 1;
939 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length() - 1, FUNCTION_MARKER_ID );
940}
941
942void QgsExpressionBuilderWidget::clearErrors()
943{
944 int lastLine = txtExpressionString->lines() - 1;
945 // Note: -1 here doesn't seem to do the clear all like the other functions. Will need to make this a bit smarter.
946 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::Unknown );
947 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionInvalidParams );
948 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionUnknown );
949 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionWrongArgs );
950 txtExpressionString->clearIndicatorRange( 0, 0, lastLine, txtExpressionString->text( lastLine ).length(), QgsExpression::ParserError::FunctionNamedArgsError );
951}
952
953void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
954{
955 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
956 mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
957}
958
959void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
960{
961 // Insert the item text or replace selected text
962 txtExpressionString->insertText( ' ' + index.data( Qt::UserRole + 1 ).toString() + ' ' );
963 txtExpressionString->setFocus();
964}
965
966void QgsExpressionBuilderWidget::operatorButtonClicked()
967{
968 QPushButton *button = qobject_cast<QPushButton *>( sender() );
969
970 // Insert the button text or replace selected text
971 txtExpressionString->insertText( ' ' + button->text() + ' ' );
972 txtExpressionString->setFocus();
973}
974
976{
977 QgsExpressionItem *item = mExpressionTreeView->currentItem();
978 if ( ! item )
979 {
980 return;
981 }
982
983 QgsVectorLayer *layer { contextLayer( item ) };
984 // TODO We should really return a error the user of the widget that
985 // the there is no layer set.
986 if ( !layer )
987 {
988 return;
989 }
990
991 mValueGroupBox->show();
992 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10 );
993}
994
996{
997 QgsExpressionItem *item = mExpressionTreeView->currentItem();
998 if ( ! item )
999 {
1000 return;
1001 }
1002
1003 QgsVectorLayer *layer { contextLayer( item ) };
1004 // TODO We should really return a error the user of the widget that
1005 // the there is no layer set.
1006 if ( !layer )
1007 {
1008 return;
1009 }
1010
1011 mValueGroupBox->show();
1012 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1 );
1013}
1014
1016{
1017 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1018 if ( ! item )
1019 {
1020 return;
1021 }
1022
1023 QgsVectorLayer *layer { contextLayer( item ) };
1024 // TODO We should really return a error the user of the widget that
1025 // the there is no layer set.
1026 if ( !layer )
1027 {
1028 return;
1029 }
1030
1031 mValueGroupBox->show();
1032 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, 10, true );
1033}
1034
1036{
1037 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1038 if ( ! item )
1039 {
1040 return;
1041 }
1042
1043 QgsVectorLayer *layer { contextLayer( item ) };
1044 // TODO We should really return a error the user of the widget that
1045 // the there is no layer set.
1046 if ( !layer )
1047 {
1048 return;
1049 }
1050
1051 mValueGroupBox->show();
1052 fillFieldValues( item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString(), layer, -1, true );
1053}
1054
1055void QgsExpressionBuilderWidget::txtPython_textChanged()
1056{
1057 lblAutoSave->setText( tr( "Saving…" ) );
1058 if ( mAutoSave )
1059 {
1060 autosave();
1061 }
1062}
1063
1065{
1066 // Don't auto save if not on function editor that would be silly.
1067 if ( tabWidget->currentIndex() != 1 )
1068 return;
1069
1070 QListWidgetItem *item = cmbFileNames->currentItem();
1071 if ( !item )
1072 return;
1073
1074 if ( item->data( Qt::UserRole ) == QLatin1String( "project" ) )
1075 {
1077 }
1078 else
1079 {
1080 QString file = item->text();
1081 saveFunctionFile( file );
1082 }
1083
1084 lblAutoSave->setText( QStringLiteral( "Saved" ) );
1085 QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
1086 lblAutoSave->setGraphicsEffect( effect );
1087 QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
1088 anim->setDuration( 2000 );
1089 anim->setStartValue( 1.0 );
1090 anim->setEndValue( 0.0 );
1091 anim->setEasingCurve( QEasingCurve::OutQuad );
1092 anim->start( QAbstractAnimation::DeleteWhenStopped );
1093}
1094
1096{
1097 const QString expression { this->expressionText() };
1098 QgsExpressionStoreDialog dlg { expression, expression, QString( ), mExpressionTreeView->userExpressionLabels() };
1099 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1100 {
1101 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1102 }
1103}
1104
1106{
1107 // Get the item
1108 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1109 if ( !item )
1110 return;
1111
1112 // Don't handle remove if we are on a header node or the parent
1113 // is not the user group
1114 if ( item->getItemType() == QgsExpressionItem::Header ||
1115 ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1116 return;
1117
1118 QgsSettings settings;
1119 QString helpText = settings.value( QStringLiteral( "user/%1/helpText" ).arg( item->text() ), "", QgsSettings::Section::Expressions ).toString();
1120 QgsExpressionStoreDialog dlg { item->text(), item->getExpressionText(), helpText, mExpressionTreeView->userExpressionLabels() };
1121
1122 if ( dlg.exec() == QDialog::DialogCode::Accepted )
1123 {
1124 // label has changed removed the old one before adding the new one
1125 if ( dlg.isLabelModified() )
1126 {
1127 mExpressionTreeView->removeFromUserExpressions( item->text() );
1128 }
1129
1130 mExpressionTreeView->saveToUserExpressions( dlg.label().simplified(), dlg.expression(), dlg.helpText() );
1131 }
1132}
1133
1135{
1136 // Get the item
1137 QgsExpressionItem *item = mExpressionTreeView->currentItem();
1138
1139 if ( !item )
1140 return;
1141
1142 // Don't handle remove if we are on a header node or the parent
1143 // is not the user group
1144 if ( item->getItemType() == QgsExpressionItem::Header ||
1145 ( item->parent() && item->parent()->text() != mUserExpressionsGroupName ) )
1146 return;
1147
1148 if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ),
1149 tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ),
1150 QMessageBox::Yes | QMessageBox::No ) )
1151 {
1152 mExpressionTreeView->removeFromUserExpressions( item->text() );
1153 }
1154
1155}
1156
1157void QgsExpressionBuilderWidget::exportUserExpressions_pressed()
1158{
1159 QgsSettings settings;
1160 QString lastSaveDir = settings.value( QStringLiteral( "lastExportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1161 QString saveFileName = QFileDialog::getSaveFileName(
1162 this,
1163 tr( "Export User Expressions" ),
1164 lastSaveDir,
1165 tr( "User expressions" ) + " (*.json)" );
1166
1167 // return dialog focus on Mac
1168 activateWindow();
1169 raise();
1170 if ( saveFileName.isEmpty() )
1171 return;
1172
1173 QFileInfo saveFileInfo( saveFileName );
1174
1175 if ( saveFileInfo.suffix().isEmpty() )
1176 {
1177 QString saveFileNameWithSuffix = saveFileName.append( ".json" );
1178 saveFileInfo = QFileInfo( saveFileNameWithSuffix );
1179 }
1180
1181 settings.setValue( QStringLiteral( "lastExportExpressionsDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
1182
1183 QJsonDocument exportJson = mExpressionTreeView->exportUserExpressions();
1184 QFile jsonFile( saveFileName );
1185
1186 if ( !jsonFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
1187 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1188
1189 if ( ! jsonFile.write( exportJson.toJson() ) )
1190 QMessageBox::warning( this, tr( "Export user expressions" ), tr( "Error while creating the expressions file." ) );
1191 else
1192 jsonFile.close();
1193}
1194
1195void QgsExpressionBuilderWidget::importUserExpressions_pressed()
1196{
1197 QgsSettings settings;
1198 QString lastImportDir = settings.value( QStringLiteral( "lastImportExpressionsDir" ), QDir::homePath(), QgsSettings::App ).toString();
1199 QString loadFileName = QFileDialog::getOpenFileName(
1200 this,
1201 tr( "Import User Expressions" ),
1202 lastImportDir,
1203 tr( "User expressions" ) + " (*.json)" );
1204
1205 if ( loadFileName.isEmpty() )
1206 return;
1207
1208 QFileInfo loadFileInfo( loadFileName );
1209
1210 settings.setValue( QStringLiteral( "lastImportExpressionsDir" ), loadFileInfo.absolutePath(), QgsSettings::App );
1211
1212 QFile jsonFile( loadFileName );
1213
1214 if ( !jsonFile.open( QFile::ReadOnly ) )
1215 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1216
1217 QTextStream jsonStream( &jsonFile );
1218 QString jsonString = jsonFile.readAll();
1219 jsonFile.close();
1220
1221 QJsonDocument importJson = QJsonDocument::fromJson( jsonString.toUtf8() );
1222
1223 if ( importJson.isNull() )
1224 {
1225 QMessageBox::warning( this, tr( "Import User Expressions" ), tr( "Error while reading the expressions file." ) );
1226 return;
1227 }
1228
1229 mExpressionTreeView->loadExpressionsFromJson( importJson );
1230}
1231
1232
1233const QList<QgsExpressionItem *> QgsExpressionBuilderWidget::findExpressions( const QString &label )
1234{
1235 return mExpressionTreeView->findExpressions( label );
1236}
1237
1238void QgsExpressionBuilderWidget::indicatorClicked( int line, int index, Qt::KeyboardModifiers state )
1239{
1240 if ( state & Qt::ControlModifier )
1241 {
1242 int position = txtExpressionString->positionFromLineIndex( line, index );
1243 long fncIndex = txtExpressionString->SendScintilla( QsciScintilla::SCI_INDICATORVALUEAT, FUNCTION_MARKER_ID, static_cast<long int>( position ) );
1245 QString help = getFunctionHelp( func );
1246 txtHelpText->setText( help );
1247 }
1248}
1249
1250void QgsExpressionBuilderWidget::onExpressionParsed( bool state )
1251{
1252 clearErrors();
1253
1254 mExpressionValid = state;
1255 if ( state )
1256 {
1257 createMarkers( mExpressionPreviewWidget->rootNode() );
1258 }
1259 else
1260 {
1261 createErrorMarkers( mExpressionPreviewWidget->parserErrors() );
1262 }
1263}
1264
1265QString QgsExpressionBuilderWidget::helpStylesheet() const
1266{
1267 //start with default QGIS report style
1268 QString style = QgsApplication::reportStyleSheet();
1269
1270 //add some tweaks
1271 style += " .functionname {color: #0a6099; font-weight: bold;} "
1272 " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
1273 " td.argument { padding-right: 10px; }";
1274
1275 return style;
1276}
1277
1278QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
1279{
1280 if ( !expressionItem )
1281 return QString();
1282
1283 QString helpContents = expressionItem->getHelpText();
1284
1285 // Return the function help that is set for the function if there is one.
1286 if ( helpContents.isEmpty() )
1287 {
1288 QString name = expressionItem->data( Qt::UserRole ).toString();
1289
1290 if ( expressionItem->getItemType() == QgsExpressionItem::Field )
1291 helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
1292 else
1293 helpContents = QgsExpression::helpText( name );
1294 }
1295
1296 return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
1297}
1298
1299
1300// *************
1301// Menu provider
1302
1303QMenu *QgsExpressionBuilderWidget::ExpressionTreeMenuProvider::createContextMenu( QgsExpressionItem *item )
1304{
1305 QMenu *menu = nullptr;
1306 QgsVectorLayer *layer = mExpressionBuilderWidget->layer();
1307 if ( item->getItemType() == QgsExpressionItem::Field && layer )
1308 {
1309 menu = new QMenu( mExpressionBuilderWidget );
1310 menu->addAction( tr( "Load First 10 Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleValues );
1311 menu->addAction( tr( "Load All Unique Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllValues );
1312
1313 if ( formatterCanProvideAvailableValues( layer, item->data( QgsExpressionItem::ITEM_NAME_ROLE ).toString() ) )
1314 {
1315 menu->addAction( tr( "Load First 10 Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadSampleUsedValues );
1316 menu->addAction( tr( "Load All Unique Used Values" ), mExpressionBuilderWidget, &QgsExpressionBuilderWidget::loadAllUsedValues );
1317 }
1318 }
1319 return menu;
1320}
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 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.
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.
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.
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 str(x)
Definition qgis.cpp:39
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6535
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6534
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5862
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.