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