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