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