QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsexpressiontreeview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsexpressiontreeview.cpp
3 --------------------------------------
4 Date : march 2020 - quarantine day 9
5 Copyright : (C) 2020 by Denis Rouzaud
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
17
18#include "qgis.h"
19#include "qgsapplication.h"
21#include "qgsiconutils.h"
22#include "qgsrelationmanager.h"
23#include "qgssettings.h"
24#include "qgsvectorlayer.h"
25
26#include <QMenu>
27#include <QMessageBox>
28#include <QString>
29#include <QVersionNumber>
30
31#include "moc_qgsexpressiontreeview.cpp"
32
33using namespace Qt::StringLiterals;
34
36QString formatRelationHelp( const QgsRelation &relation )
37{
38 QString text = u"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>"_s
39 .arg( QCoreApplication::translate( "relation_help", "relation %1" ).arg( relation.name() ), QObject::tr( "Inserts the relation ID for the relation named '%1'." ).arg( relation.name() ) );
40
41 text += u"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>"_s
42 .arg( QObject::tr( "Current value" ), relation.id() );
43
44 return text;
45}
46
47
49QString formatLayerHelp( const QgsMapLayer *layer )
50{
51 QString text = u"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>"_s
52 .arg( QCoreApplication::translate( "layer_help", "map layer %1" ).arg( layer->name() ), QObject::tr( "Inserts the layer ID for the layer named '%1'." ).arg( layer->name() ) );
53
54 text += u"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>"_s
55 .arg( QObject::tr( "Current value" ), layer->id() );
56
57 return text;
58}
59
61QString formatRecentExpressionHelp( const QString &label, const QString &expression )
62{
63 QString text = u"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>"_s
64 .arg( QCoreApplication::translate( "recent_expression_help", "expression %1" ).arg( label ), QCoreApplication::translate( "recent_expression_help", "Recently used expression." ) );
65
66 text += u"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>"_s
67 .arg( QObject::tr( "Expression" ), expression );
68
69 return text;
70}
71
73QString formatUserExpressionHelp( const QString &label, const QString &expression, const QString &description )
74{
75 QString text = u"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>"_s
76 .arg( QCoreApplication::translate( "user_expression_help", "expression %1" ).arg( label ), description );
77
78 text += u"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>"_s
79 .arg( QObject::tr( "Expression" ), expression );
80
81 return text;
82}
83
85QString formatVariableHelp( const QString &variable, const QString &description, bool showValue, const QVariant &value )
86{
87 QString text = u"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>"_s
88 .arg( QCoreApplication::translate( "variable_help", "variable %1" ).arg( variable ), description );
89
90 if ( showValue )
91 {
92 QString valueString = !value.isValid()
93 ? QCoreApplication::translate( "variable_help", "not set" )
94 : u"<pre>%1</pre>"_s.arg( QgsExpression::formatPreviewString( value ) );
95
96 text += u"<h4>%1</h4><div class=\"description\"><p>%2</p></div>"_s
97 .arg( QObject::tr( "Current value" ), valueString );
98 }
99
100 return text;
101}
102
103
104// ****************************
105// ****************************
106// QgsExpressionTreeView
107// ****************************
108
109
111 : QTreeView( parent )
112 , mProject( QgsProject::instance() )
113{
114 connect( this, &QTreeView::doubleClicked, this, &QgsExpressionTreeView::onDoubleClicked );
115
116 mModel = std::make_unique<QStandardItemModel>();
117 mProxyModel = std::make_unique<QgsExpressionItemSearchProxy>();
118 mProxyModel->setDynamicSortFilter( true );
119 mProxyModel->setSourceModel( mModel.get() );
120 setModel( mProxyModel.get() );
121 setSortingEnabled( true );
122 sortByColumn( 0, Qt::AscendingOrder );
123
124 setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection );
125
126 setContextMenuPolicy( Qt::CustomContextMenu );
127 connect( this, &QWidget::customContextMenuRequested, this, &QgsExpressionTreeView::showContextMenu );
128 connect( selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsExpressionTreeView::currentItemChanged );
129
130 updateFunctionTree();
132
133 // select the first item in the function list
134 // in order to avoid a blank help widget
135 QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
136 setCurrentIndex( firstItem );
137}
138
140{
141 mLayer = layer;
142
143 //TODO - remove existing layer scope from context
144
145 if ( mLayer )
146 mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
147
149}
150
152{
153 mExpressionContext = context;
154 updateFunctionTree();
156 loadRecent( mRecentKey );
158}
159
161{
162 mMenuProvider = provider;
163}
164
166{
167 updateFunctionTree();
169 loadRecent( mRecentKey );
171}
172
174{
175 QModelIndex idx = mProxyModel->mapToSource( currentIndex() );
176 QgsExpressionItem *item = static_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
177 return item;
178}
179
180QStandardItemModel *QgsExpressionTreeView::model()
181{
182 return mModel.get();
183}
184
186{
187 return mProject;
188}
189
191{
192 mProject = project;
193 updateFunctionTree();
194}
195
196
197void QgsExpressionTreeView::setSearchText( const QString &text )
198{
199 mProxyModel->setFilterString( text );
200 if ( text.isEmpty() )
201 {
202 collapseAll();
203 }
204 else
205 {
206 expandAll();
207 QModelIndex index = mProxyModel->index( 0, 0 );
208 if ( mProxyModel->hasChildren( index ) )
209 {
210 QModelIndex child = mProxyModel->index( 0, 0, index );
211 selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
212 }
213 }
214}
215
216void QgsExpressionTreeView::onDoubleClicked( const QModelIndex &index )
217{
218 QModelIndex idx = mProxyModel->mapToSource( index );
219 QgsExpressionItem *item = static_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
220 if ( !item )
221 return;
222
223 // Don't handle the double-click if we are on a header node.
224 if ( item->getItemType() == QgsExpressionItem::Header )
225 return;
226
228}
229
230void QgsExpressionTreeView::showContextMenu( QPoint pt )
231{
232 QModelIndex idx = indexAt( pt );
233 idx = mProxyModel->mapToSource( idx );
234 QgsExpressionItem *item = static_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
235 if ( !item )
236 return;
237
238 if ( !mMenuProvider )
239 return;
240
241 QMenu *menu = mMenuProvider->createContextMenu( item );
242
243 if ( menu )
244 menu->popup( mapToGlobal( pt ) );
245}
246
247void QgsExpressionTreeView::currentItemChanged( const QModelIndex &index, const QModelIndex & )
248{
249 // Get the item
250 QModelIndex idx = mProxyModel->mapToSource( index );
251 QgsExpressionItem *item = static_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
252 if ( !item )
253 return;
254
255 emit currentExpressionItemChanged( item );
256}
257
258void QgsExpressionTreeView::updateFunctionTree()
259{
260 mModel->clear();
261 mExpressionGroups.clear();
262
263 //list of pairs where the first is the name and the second is the expression value when adding it
264 static const QList<QPair<QString, QString>> operators = QList<QPair<QString, QString>>()
265 << QPair<QString, QString>( u"+"_s, u" + "_s )
266 << QPair<QString, QString>( u"-"_s, u" - "_s )
267 << QPair<QString, QString>( u"*"_s, u" * "_s )
268 << QPair<QString, QString>( u"/"_s, u" / "_s )
269 << QPair<QString, QString>( u"//"_s, u" // "_s )
270 << QPair<QString, QString>( u"%"_s, u" % "_s )
271 << QPair<QString, QString>( u"^"_s, u" ^ "_s )
272 << QPair<QString, QString>( u"="_s, u" = "_s )
273 << QPair<QString, QString>( u"~"_s, u" ~ "_s )
274 << QPair<QString, QString>( u">"_s, u" > "_s )
275 << QPair<QString, QString>( u"<"_s, u" < "_s )
276 << QPair<QString, QString>( u"<>"_s, u" <> "_s )
277 << QPair<QString, QString>( u"<="_s, u" <= "_s )
278 << QPair<QString, QString>( u">="_s, u" >= "_s )
279 << QPair<QString, QString>( u"[]"_s, u"[]"_s )
280 << QPair<QString, QString>( u"||"_s, u" || "_s )
281 << QPair<QString, QString>( u"BETWEEN"_s, u" BETWEEN "_s )
282 << QPair<QString, QString>( u"NOT BETWEEN"_s, u" NOT BETWEEN "_s )
283 << QPair<QString, QString>( u"IN"_s, u" IN "_s )
284 << QPair<QString, QString>( u"LIKE"_s, u" LIKE "_s )
285 << QPair<QString, QString>( u"ILIKE"_s, u" ILIKE "_s )
286 << QPair<QString, QString>( u"IS"_s, u" IS "_s )
287 << QPair<QString, QString>( u"IS NOT"_s, u" IS NOT "_s )
288 << QPair<QString, QString>( u"OR"_s, u" OR "_s )
289 << QPair<QString, QString>( u"AND"_s, u" AND "_s )
290 << QPair<QString, QString>( u"NOT"_s, u" NOT "_s );
291 for ( const auto &name : operators )
292 {
293 registerItem( u"Operators"_s, name.first, name.second, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( name.first ) );
294 }
295
296 QString casestring = u"CASE WHEN condition THEN result END"_s;
297 registerItem( u"Conditionals"_s, u"CASE"_s, casestring, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( "CASE" ) );
298
299 // use -1 as sort order here -- NULL should always show before the field list
300 registerItem( u"Fields and Values"_s, u"NULL"_s, u"NULL"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1 );
301
302 registerItem( u"String"_s, u"\\n (newline character)"_s, u"'\\n'"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( u"\\n"_s ) );
303 registerItem( u"String"_s, u"\\t (tab character)"_s, u"'\\t'"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( u"\\t"_s ) );
304 registerItem( u"String"_s, u"\\\\ (backslash character)"_s, u"'\\\\'"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( u"\\\\"_s ) );
305 registerItem( u"String"_s, u"\\' (quote character)"_s, u"'\\''"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1, QIcon(), QgsExpression::tags( u"\\'"_s ) );
306
307 // Load the functions from the QgsExpression class
308 int count = QgsExpression::functionCount();
309 for ( int i = 0; i < count; i++ )
310 {
311 QgsExpressionFunction *func = QgsExpression::Functions()[i];
312 QString name = func->name();
313 if ( name.startsWith( '_' ) ) // do not display private functions
314 continue;
315 if ( func->isDeprecated() ) // don't show deprecated functions
316 continue;
317 if ( func->isContextual() )
318 {
319 //don't show contextual functions by default - it's up the the QgsExpressionContext
320 //object to provide them if supported
321 continue;
322 }
323 if ( func->params() != 0 )
324 name += '(';
325 else if ( !name.startsWith( '$' ) )
326 name += "()"_L1;
327 // this is where the functions are being registered, including functions under "Custom"
328 registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ), 1, QgsExpression::tags( func->name() ) );
329 }
330
331 // load relation names
332 loadRelations();
333
334 // load layer IDs
335 loadLayers();
336
337 loadExpressionContext();
338}
339
340QgsExpressionItem *QgsExpressionTreeView::registerItem( const QString &group, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder, const QIcon &icon, const QStringList &tags, const QString &name )
341{
342 QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
343 item->setData( label, Qt::UserRole );
344 item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
345 item->setData( tags, QgsExpressionItem::SEARCH_TAGS_ROLE );
346 item->setData( name, QgsExpressionItem::ITEM_NAME_ROLE );
347 item->setIcon( icon );
348
349 // Look up the group and insert the new function.
350 if ( mExpressionGroups.contains( group ) )
351 {
352 QgsExpressionItem *groupNode = mExpressionGroups.value( group );
353 groupNode->appendRow( item );
354 }
355 else
356 {
357 // If the group doesn't exist yet we make it first.
358 QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QString(), QgsExpressionItem::Header );
359 newgroupNode->setData( group, Qt::UserRole );
360 //Recent group should always be last group
361 newgroupNode->setData( group.startsWith( "Recent ("_L1 ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
362 newgroupNode->appendRow( item );
363 newgroupNode->setBackground( QBrush( QColor( 150, 150, 150, 150 ) ) );
364 mModel->appendRow( newgroupNode );
365 mExpressionGroups.insert( group, newgroupNode );
366 }
367
368 if ( highlightedItem )
369 {
370 //insert a copy as a top level item
371 QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
372 topLevelItem->setData( label, Qt::UserRole );
373 item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
374 QFont font = topLevelItem->font();
375 font.setBold( true );
376 topLevelItem->setFont( font );
377 mModel->appendRow( topLevelItem );
378 }
379 return item;
380}
381
382void QgsExpressionTreeView::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder, const QStringList &tags )
383{
384 const auto constGroups = groups;
385 for ( const QString &group : constGroups )
386 {
387 registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder, QIcon(), tags );
388 }
389}
390
391void QgsExpressionTreeView::loadExpressionContext()
392{
393 QStringList variableNames = mExpressionContext.filteredVariableNames();
394 const auto constVariableNames = variableNames;
395 for ( const QString &variable : constVariableNames )
396 {
397 registerItem( u"Variables"_s, variable, " @" + variable + ' ', formatVariableHelp( variable, mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedVariable( variable ) );
398 }
399
400 // Load the functions from the expression context
401 QStringList contextFunctions = mExpressionContext.functionNames();
402 const auto constContextFunctions = contextFunctions;
403 for ( const QString &functionName : constContextFunctions )
404 {
405 QgsExpressionFunction *func = mExpressionContext.function( functionName );
406 QString name = func->name();
407 if ( name.startsWith( '_' ) ) // do not display private functions
408 continue;
409 if ( func->params() != 0 )
410 name += '(';
411 registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ), 1, QgsExpression::tags( func->name() ) );
412 }
413}
414
415void QgsExpressionTreeView::loadLayers()
416{
417 if ( !mProject )
418 return;
419
420 QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
421 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
422 for ( ; layerIt != layers.constEnd(); ++layerIt )
423 {
424 QIcon icon = QgsIconUtils::iconForLayer( layerIt.value() );
425 QgsExpressionItem *parentItem = registerItem( u"Map Layers"_s, layerIt.value()->name(), u"'%1'"_s.arg( layerIt.key() ), formatLayerHelp( layerIt.value() ), QgsExpressionItem::ExpressionNode, false, 99, icon );
426 loadLayerFields( qobject_cast<QgsVectorLayer *>( layerIt.value() ), parentItem );
427 }
428}
429
430void QgsExpressionTreeView::loadLayerFields( QgsVectorLayer *layer, QgsExpressionItem *parentItem )
431{
432 if ( !layer )
433 return;
434
435 const QgsFields fields = layer->fields();
436 for ( int fieldIdx = 0; fieldIdx < fields.count(); ++fieldIdx )
437 {
438 const QgsField field = fields.at( fieldIdx );
439 QIcon icon = fields.iconForField( fieldIdx );
440 const QString label { field.displayNameWithAlias() };
441 QgsExpressionItem *item = new QgsExpressionItem( label, " '" + field.name() + "' ", QString(), QgsExpressionItem::Field );
442 item->setData( label, Qt::UserRole );
443 item->setData( 99, QgsExpressionItem::CUSTOM_SORT_ROLE );
444 item->setData( QStringList(), QgsExpressionItem::SEARCH_TAGS_ROLE );
445 item->setData( field.name(), QgsExpressionItem::ITEM_NAME_ROLE );
446 item->setData( layer->id(), QgsExpressionItem::LAYER_ID_ROLE );
447 item->setIcon( icon );
448 parentItem->appendRow( item );
449 }
450}
451
453{
454 for ( int i = 0; i < fields.count(); ++i )
455 {
456 const QgsField field = fields.at( i );
457 QIcon icon = fields.iconForField( i );
458 registerItem( u"Fields and Values"_s, field.displayNameWithAlias(), " \"" + field.name() + "\" ", QString(), QgsExpressionItem::Field, false, i, icon, QStringList(), field.name() );
459 }
460}
461
463{
464 // Cleanup
465 if ( mExpressionGroups.contains( u"Fields and Values"_s ) )
466 {
467 QgsExpressionItem *node = mExpressionGroups.value( u"Fields and Values"_s );
468 node->removeRows( 0, node->rowCount() );
469 // Re-add NULL and feature variables
470 // use -1 as sort order here -- NULL and feature variables should always show before the field list
471 registerItem( u"Fields and Values"_s, u"NULL"_s, u"NULL"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1 );
472 }
473
474 if ( mLayer )
475 {
476 // Add feature variables to record and attributes group (and highlighted items)
477
478 const QString currentFeatureHelp = formatVariableHelp( u"feature"_s, QgsExpression::variableHelpText( u"feature"_s ), false, QVariant() );
479 const QString currentFeatureIdHelp = formatVariableHelp( u"id"_s, QgsExpression::variableHelpText( u"id"_s ), false, QVariant() );
480 const QString currentGeometryHelp = formatVariableHelp( u"geometry"_s, QgsExpression::variableHelpText( u"geometry"_s ), false, QVariant() );
481
482 registerItem( u"Fields and Values"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode, false, -1 );
483 registerItem( u"Fields and Values"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode, false, -1 );
484 registerItem( u"Fields and Values"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, false, -1 );
485
486 registerItem( u"Variables"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode );
487 registerItem( u"Variables"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode );
488 registerItem( u"Variables"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, false );
489
490 registerItem( u"Record and Attributes"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode, true, -1 );
491 registerItem( u"Record and Attributes"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode, true, -1 );
492 registerItem( u"Record and Attributes"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, true, -1 );
493 }
494
495 // this can happen if fields are manually set
496 if ( !mLayer )
497 return;
498
499 const QgsFields &fields = mLayer->fields();
500
501 loadFieldNames( fields );
502}
503
504void QgsExpressionTreeView::loadRelations()
505{
506 if ( !mProject )
507 return;
508
509 QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
510 QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
511 for ( ; relIt != relations.constEnd(); ++relIt )
512 {
513 registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), u"'%1'"_s.arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
514 }
515}
516
517void QgsExpressionTreeView::loadRecent( const QString &collection )
518{
519 mRecentKey = collection;
520 QString name = tr( "Recent (%1)" ).arg( collection );
521 if ( mExpressionGroups.contains( name ) )
522 {
523 QgsExpressionItem *node = mExpressionGroups.value( name );
524 node->removeRows( 0, node->rowCount() );
525 }
526
527 QgsSettings settings;
528 const QString location = u"/expressions/recent/%1"_s.arg( collection );
529 const QStringList expressions = settings.value( location ).toStringList();
530 int i = 0;
531 for ( const QString &expression : expressions )
532 {
533 QString help = formatRecentExpressionHelp( expression, expression );
534 QString label = expression;
535 label.replace( '\n', ' ' );
536 registerItem( name, label, expression, help, QgsExpressionItem::ExpressionNode, false, i );
537 i++;
538 }
539}
540
541void QgsExpressionTreeView::saveToRecent( const QString &expressionText, const QString &collection )
542{
543 QgsSettings settings;
544 QString location = u"/expressions/recent/%1"_s.arg( collection );
545 QStringList expressions = settings.value( location ).toStringList();
546 expressions.removeAll( expressionText );
547
548 expressions.prepend( expressionText );
549
550 while ( expressions.count() > 20 )
551 {
552 expressions.pop_back();
553 }
554
555 settings.setValue( location, expressions );
556 loadRecent( collection );
557}
558
559void QgsExpressionTreeView::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
560{
561 QgsSettings settings;
562 const QString location = u"user"_s;
563 settings.beginGroup( location, QgsSettings::Section::Expressions );
564 settings.beginGroup( label );
565 settings.setValue( u"expression"_s, expression );
566 settings.setValue( u"helpText"_s, helpText );
568 // Scroll
569 const QModelIndexList idxs { mModel->match( mModel->index( 0, 0 ), Qt::DisplayRole, label, 1, Qt::MatchFlag::MatchRecursive ) };
570 if ( !idxs.isEmpty() )
571 {
572 scrollTo( idxs.first() );
573 }
574}
575
577{
578 QgsSettings settings;
579 settings.remove( u"user/%1"_s.arg( label ), QgsSettings::Section::Expressions );
581}
582
583// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
585{
586 // Cleanup
587 if ( mExpressionGroups.contains( u"UserGroup"_s ) )
588 {
589 QgsExpressionItem *node = mExpressionGroups.value( u"UserGroup"_s );
590 node->removeRows( 0, node->rowCount() );
591 }
592
593 QgsSettings settings;
594 const QString location = u"user"_s;
595 settings.beginGroup( location, QgsSettings::Section::Expressions );
596 QString helpText;
597 QString expression;
598 int i = 0;
599 mUserExpressionLabels = settings.childGroups();
600 for ( const auto &label : std::as_const( mUserExpressionLabels ) )
601 {
602 settings.beginGroup( label );
603 expression = settings.value( u"expression"_s ).toString();
604 helpText = formatUserExpressionHelp( label, expression, settings.value( u"helpText"_s ).toString() );
605 registerItem( u"UserGroup"_s, label, expression, helpText, QgsExpressionItem::ExpressionNode, false, i++ );
606 settings.endGroup();
607 }
608}
609
611{
612 return mUserExpressionLabels;
613}
614
616{
617 const QString group = u"user"_s;
618 QgsSettings settings;
619 QJsonArray exportList;
620 QJsonObject exportObject {
621 { "qgis_version", Qgis::version() },
622 { "exported_at", QDateTime::currentDateTime().toString( Qt::ISODate ) },
623 { "author", QgsApplication::userFullName() },
624 { "expressions", exportList }
625 };
626
628
629 mUserExpressionLabels = settings.childGroups();
630
631 for ( const QString &label : std::as_const( mUserExpressionLabels ) )
632 {
633 settings.beginGroup( label );
634
635 const QString expression = settings.value( u"expression"_s ).toString();
636 const QString helpText = settings.value( u"helpText"_s ).toString();
637 const QJsonObject expressionObject {
638 { "name", label },
639 { "type", "expression" },
640 { "expression", expression },
641 { "group", group },
642 { "description", helpText }
643 };
644 exportList.push_back( expressionObject );
645
646 settings.endGroup();
647 }
648
649 exportObject[u"expressions"_s] = exportList;
650 QJsonDocument exportJson = QJsonDocument( exportObject );
651
652 return exportJson;
653}
654
655void QgsExpressionTreeView::loadExpressionsFromJson( const QJsonDocument &expressionsDocument )
656{
657 // if the root of the json document is not an object, it means it's a wrong file
658 if ( !expressionsDocument.isObject() )
659 return;
660
661 QJsonObject expressionsObject = expressionsDocument.object();
662
663 // validate json for manadatory fields
664 if ( !expressionsObject[u"qgis_version"_s].isString()
665 || !expressionsObject[u"exported_at"_s].isString()
666 || !expressionsObject[u"author"_s].isString()
667 || !expressionsObject[u"expressions"_s].isArray() )
668 return;
669
670 // validate versions
671 QVersionNumber qgisJsonVersion = QVersionNumber::fromString( expressionsObject[u"qgis_version"_s].toString() );
672 QVersionNumber qgisVersion = QVersionNumber::fromString( Qgis::version() );
673
674 // if the expressions are from newer version of QGIS, we ask the user to confirm
675 // they want to proceed
676 if ( qgisJsonVersion > qgisVersion )
677 {
678 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
679 switch ( QMessageBox::question( this, tr( "QGIS Version Mismatch" ), tr( "The imported expressions are from newer version of QGIS (%1) "
680 "and some of the expression might not work the current version (%2). "
681 "Are you sure you want to continue?" )
682 .arg( qgisJsonVersion.toString(), qgisVersion.toString() ),
683 buttons ) )
684 {
685 case QMessageBox::No:
686 return;
687
688 case QMessageBox::Yes:
689 break;
690
691 default:
692 break;
693 }
694 }
695
696 // we store the number of
697 QStringList skippedExpressionLabels;
698 bool isApplyToAll = false;
699 bool isOkToOverwrite = false;
700
701 QgsSettings settings;
702 settings.beginGroup( u"user"_s, QgsSettings::Section::Expressions );
703 mUserExpressionLabels = settings.childGroups();
704
705 const QJsonArray expressions = expressionsObject[u"expressions"_s].toArray();
706 for ( const QJsonValue &&expressionValue : expressions )
707 {
708 // validate the type of the array element, can be anything
709 if ( !expressionValue.isObject() )
710 {
711 // try to stringify and put and indicator what happened
712 skippedExpressionLabels.append( expressionValue.toString() );
713 continue;
714 }
715
716 QJsonObject expressionObj = expressionValue.toObject();
717
718 // make sure the required keys are the correct types
719 if ( !expressionObj[u"name"_s].isString()
720 || !expressionObj[u"type"_s].isString()
721 || !expressionObj[u"expression"_s].isString()
722 || !expressionObj[u"group"_s].isString()
723 || !expressionObj[u"description"_s].isString() )
724 {
725 // try to stringify and put an indicator what happened. Try to stringify the name, if fails, go with the expression.
726 if ( !expressionObj[u"name"_s].toString().isEmpty() )
727 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
728 else
729 skippedExpressionLabels.append( expressionObj[u"expression"_s].toString() );
730
731 continue;
732 }
733
734 // we want to import only items of type expression for now
735 if ( expressionObj[u"type"_s].toString() != "expression"_L1 )
736 {
737 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
738 continue;
739 }
740
741 // we want to import only items of type expression for now
742 if ( expressionObj[u"group"_s].toString() != "user"_L1 )
743 {
744 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
745 continue;
746 }
747
748 const QString label = expressionObj[u"name"_s].toString();
749 const QString expression = expressionObj[u"expression"_s].toString();
750 const QString helpText = expressionObj[u"description"_s].toString();
751
752 // make sure they have valid name
753 if ( label.contains( "\\"_L1 ) || label.contains( '/' ) )
754 {
755 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
756 continue;
757 }
758
759 settings.beginGroup( label );
760 const QString oldExpression = settings.value( u"expression"_s ).toString();
761 settings.endGroup();
762
763 // TODO would be nice to skip the cases when labels and expressions match
764 if ( mUserExpressionLabels.contains( label ) && expression != oldExpression )
765 {
766 if ( !isApplyToAll )
767 showMessageBoxConfirmExpressionOverwrite( isApplyToAll, isOkToOverwrite, label, oldExpression, expression );
768
769 if ( isOkToOverwrite )
770 saveToUserExpressions( label, expression, helpText );
771 else
772 {
773 skippedExpressionLabels.append( label );
774 continue;
775 }
776 }
777 else
778 {
779 saveToUserExpressions( label, expression, helpText );
780 }
781 }
782
784
785 if ( !skippedExpressionLabels.isEmpty() )
786 {
787 QStringList skippedExpressionLabelsQuoted;
788 skippedExpressionLabelsQuoted.reserve( skippedExpressionLabels.size() );
789 for ( const QString &skippedExpressionLabel : skippedExpressionLabels )
790 skippedExpressionLabelsQuoted.append( u"'%1'"_s.arg( skippedExpressionLabel ) );
791
792 QMessageBox::information( this, tr( "Skipped Expression Imports" ), u"%1\n%2"_s.arg( tr( "The following expressions have been skipped:" ), skippedExpressionLabelsQuoted.join( ", "_L1 ) ) );
793 }
794}
795
796const QList<QgsExpressionItem *> QgsExpressionTreeView::findExpressions( const QString &label )
797{
798 QList<QgsExpressionItem *> result;
799 const QList<QStandardItem *> found { mModel->findItems( label, Qt::MatchFlag::MatchRecursive ) };
800 result.reserve( found.size() );
801 std::transform( found.begin(), found.end(), std::back_inserter( result ), []( QStandardItem *item ) -> QgsExpressionItem * { return static_cast<QgsExpressionItem *>( item ); } );
802 return result;
803}
804
805void QgsExpressionTreeView::showMessageBoxConfirmExpressionOverwrite(
806 bool &isApplyToAll,
807 bool &isOkToOverwrite,
808 const QString &label,
809 const QString &oldExpression,
810 const QString &newExpression
811)
812{
813 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll;
814 switch ( QMessageBox::question( this, tr( "Expression Overwrite" ), tr( "The expression with label '%1' was already defined."
815 "The old expression \"%2\" will be overwritten by \"%3\"."
816 "Are you sure you want to overwrite the expression?" )
817 .arg( label, oldExpression, newExpression ),
818 buttons ) )
819 {
820 case QMessageBox::NoToAll:
821 isApplyToAll = true;
822 isOkToOverwrite = false;
823 break;
824
825 case QMessageBox::No:
826 isApplyToAll = false;
827 isOkToOverwrite = false;
828 break;
829
830 case QMessageBox::YesToAll:
831 isApplyToAll = true;
832 isOkToOverwrite = true;
833 break;
834
835 case QMessageBox::Yes:
836 isApplyToAll = false;
837 isOkToOverwrite = true;
838 break;
839
840 default:
841 break;
842 }
843}
844
845
846// ****************************
847// ****************************
848// QgsExpressionItemSearchProxy
849// ****************************
850
851
853{
854 setFilterCaseSensitivity( Qt::CaseInsensitive );
855}
856
857bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
858{
859 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
860 const QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
861
862 if ( itemType == QgsExpressionItem::Header )
863 {
864 // show header if any child item matches
865 int count = sourceModel()->rowCount( index );
866 bool matchchild = false;
867 for ( int i = 0; i < count; ++i )
868 {
869 if ( filterAcceptsRow( i, index ) )
870 {
871 matchchild = true;
872 break;
873 }
874 }
875 return matchchild;
876 }
877
878 // check match of item label or tags
879 const QString name = sourceModel()->data( index, Qt::DisplayRole ).toString();
880 if ( name.contains( mFilterString, Qt::CaseInsensitive ) )
881 {
882 return true;
883 }
884
885 const QStringList tags = sourceModel()->data( index, QgsExpressionItem::SEARCH_TAGS_ROLE ).toStringList();
886 return std::any_of( tags.begin(), tags.end(), [this]( const QString &tag ) {
887 return tag.contains( mFilterString, Qt::CaseInsensitive );
888 } );
889}
890
892{
893 mFilterString = string;
894 invalidate();
895}
896
897bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
898{
899 int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
900 int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
901 if ( leftSort != rightSort )
902 return leftSort < rightSort;
903
904 QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
905 QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
906
907 //ignore $ prefixes when sorting
908 if ( leftString.startsWith( '$' ) )
909 leftString = leftString.mid( 1 );
910 if ( rightString.startsWith( '$' ) )
911 rightString = rightString.mid( 1 );
912
913 return QString::localeAwareCompare( leftString, rightString ) < 0;
914}
static QString version()
Version string.
Definition qgis.cpp:682
static QString userFullName()
Returns the user's operating system login account full display name.
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...
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object.
int params() const
The number of parameters this function takes.
QStringList groups() const
Returns a list of the groups the function belongs to.
virtual bool isDeprecated() const
Returns true if the function is deprecated and should not be presented as a valid option to users in ...
QString name() const
The name of the function.
const QString helpText() const
The help text for the function.
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
void setFilterString(const QString &string)
Sets the search filter string.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
An expression item that can be used in the QgsExpressionBuilderWidget tree.
static const int LAYER_ID_ROLE
Layer ID role.
QString getExpressionText() const
static const int SEARCH_TAGS_ROLE
Search tags role.
static const int ITEM_TYPE_ROLE
Item type role.
static const int CUSTOM_SORT_ROLE
Custom sort order role.
QgsExpressionItem::ItemType getItemType() const
Gets the type of expression item, e.g., header, field, ExpressionNode.
static const int ITEM_NAME_ROLE
Item name role.
Implementation of this interface can be implemented to allow QgsExpressionTreeView instance to provid...
QgsProject * project()
Returns the project currently associated with the widget.
void refresh()
Refreshes the content of the tree.
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
void saveToUserExpressions(const QString &label, const QString &expression, const QString &helpText)
Stores the user expression with given label and helpText.
QgsExpressionItem * currentItem() const
Returns the current item or a nullptr.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
void setMenuProvider(MenuProvider *provider)
Sets the menu provider.
QStringList userExpressionLabels() const
Returns the user expression labels.
void expressionItemDoubleClicked(const QString &text)
Emitted when a expression item is double clicked.
void loadRecent(const QString &collection=u"generic"_s)
Loads the recent expressions from the given collection.
void currentExpressionItemChanged(QgsExpressionItem *item)
Emitter when the current expression item changed.
QJsonDocument exportUserExpressions()
Create the expressions JSON document storing all the user expressions to be exported.
QgsExpressionTreeView(QWidget *parent=nullptr)
Constructor.
void loadExpressionsFromJson(const QJsonDocument &expressionsDocument)
Load and permanently store the expressions from the expressions JSON document.
void saveToRecent(const QString &expressionText, const QString &collection="generic")
Adds the current expression to the given collection.
void setSearchText(const QString &text)
Sets the text to filter the expression tree.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the tree view.
const QList< QgsExpressionItem * > findExpressions(const QString &label)
Returns the list of expression items matching a label.
void removeFromUserExpressions(const QString &label)
Removes the expression label from the user stored expressions.
void loadUserExpressions()
Loads the user expressions.
Q_DECL_DEPRECATED QStandardItemModel * model()
Returns a pointer to the dialog's function item model.
void loadFieldNames(const QgsFields &fields)
This allows loading fields without specifying a layer.
static const QList< QgsExpressionFunction * > & Functions()
static int functionCount()
Returns the number of functions defined in the parser.
static QString variableHelpText(const QString &variableName)
Returns the help text for a specified variable.
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true, int maximumPreviewLength=60)
Formats an expression result for friendly display to the user.
static QStringList tags(const QString &name)
Returns a string list of search tags for a specified function.
static QString group(const QString &group)
Returns the translated name for a function group.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
QString name
Definition qgsfield.h:65
QString displayNameWithAlias() const
Returns the name to use when displaying this field and adds the alias in parenthesis if it is defined...
Definition qgsfield.cpp:104
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
static QIcon iconForLayer(const QgsMapLayer *layer)
Returns the icon corresponding to a specified map layer.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:86
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
QString name
Definition qgsrelation.h:52
QString id
Definition qgsrelation.h:45
Stores settings for use within QGIS.
Definition qgssettings.h:68
QStringList childGroups(Qgis::SettingsOrigin origin=Qgis::SettingsOrigin::Any) const
Returns a list of all key top-level groups that contain keys that can be read using the QSettings obj...
void endGroup()
Resets the group to what it was before the corresponding beginGroup() call.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Represents a vector layer which manages a vector based dataset.
QString formatLayerHelp(const QgsMapLayer *layer)
Returns a HTML formatted string for use as a layer item help.
QString formatUserExpressionHelp(const QString &label, const QString &expression, const QString &description)
Returns a HTML formatted string for use as a user expression item help.
QString formatVariableHelp(const QString &variable, const QString &description, bool showValue, const QVariant &value)
Returns a HTML formatted string for use as a variable item help.
QString formatRecentExpressionHelp(const QString &label, const QString &expression)
Returns a HTML formatted string for use as a recent expression item help.
QString formatRelationHelp(const QgsRelation &relation)
Returns a HTML formatted string for use as a relation item help.