QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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.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
299 int count = QgsExpression::functionCount();
300 for ( int i = 0; i < count; i++ )
301 {
302 QgsExpressionFunction *func = QgsExpression::Functions()[i];
303 QString name = func->name();
304 if ( name.startsWith( '_' ) ) // do not display private functions
305 continue;
306 if ( func->isDeprecated() ) // don't show deprecated functions
307 continue;
308 if ( func->isContextual() )
309 {
310 //don't show contextual functions by default - it's up the the QgsExpressionContext
311 //object to provide them if supported
312 continue;
313 }
314 if ( func->params() != 0 )
315 name += '(';
316 else if ( !name.startsWith( '$' ) )
317 name += "()"_L1;
318 // this is where the functions are being registered, including functions under "Custom"
319 registerItemForAllGroups(
320 func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ), 1, QgsExpression::tags( func->name() )
321 );
322 }
323
324 // load relation names
325 loadRelations();
326
327 // load layer IDs
328 loadLayers();
329
330 loadExpressionContext();
331}
332
333QgsExpressionItem *QgsExpressionTreeView::registerItem(
334 const QString &group,
335 const QString &label,
336 const QString &expressionText,
337 const QString &helpText,
339 bool highlightedItem,
340 int sortOrder,
341 const QIcon &icon,
342 const QStringList &tags,
343 const QString &name
344)
345{
346 QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
347 item->setData( label, Qt::UserRole );
348 item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
349 item->setData( tags, QgsExpressionItem::SEARCH_TAGS_ROLE );
350 item->setData( name, QgsExpressionItem::ITEM_NAME_ROLE );
351 item->setIcon( icon );
352
353 // Look up the group and insert the new function.
354 if ( mExpressionGroups.contains( group ) )
355 {
356 QgsExpressionItem *groupNode = mExpressionGroups.value( group );
357 groupNode->appendRow( item );
358 }
359 else
360 {
361 // If the group doesn't exist yet we make it first.
362 QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QString(), QgsExpressionItem::Header );
363 newgroupNode->setData( group, Qt::UserRole );
364 //Recent group should always be last group
365 newgroupNode->setData( group.startsWith( "Recent ("_L1 ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
366 newgroupNode->appendRow( item );
367 newgroupNode->setBackground( QBrush( QColor( 150, 150, 150, 150 ) ) );
368 mModel->appendRow( newgroupNode );
369 mExpressionGroups.insert( group, newgroupNode );
370 }
371
372 if ( highlightedItem )
373 {
374 //insert a copy as a top level item
375 QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
376 topLevelItem->setData( label, Qt::UserRole );
377 item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
378 QFont font = topLevelItem->font();
379 font.setBold( true );
380 topLevelItem->setFont( font );
381 mModel->appendRow( topLevelItem );
382 }
383 return item;
384}
385
386void QgsExpressionTreeView::registerItemForAllGroups(
387 const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder, const QStringList &tags
388)
389{
390 const auto constGroups = groups;
391 for ( const QString &group : constGroups )
392 {
393 registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder, QIcon(), tags );
394 }
395}
396
397void QgsExpressionTreeView::loadExpressionContext()
398{
399 QStringList variableNames = mExpressionContext.filteredVariableNames();
400 const auto constVariableNames = variableNames;
401 for ( const QString &variable : constVariableNames )
402 {
403 registerItem(
404 u"Variables"_s,
405 variable,
406 " @" + variable + ' ',
407 formatVariableHelp( variable, mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
409 mExpressionContext.isHighlightedVariable( variable )
410 );
411 }
412
413 // Load the functions from the expression context
414 QStringList contextFunctions = mExpressionContext.functionNames();
415 const auto constContextFunctions = contextFunctions;
416 for ( const QString &functionName : constContextFunctions )
417 {
418 QgsExpressionFunction *func = mExpressionContext.function( functionName );
419 QString name = func->name();
420 if ( name.startsWith( '_' ) ) // do not display private functions
421 continue;
422 if ( func->params() != 0 )
423 name += '(';
424 registerItemForAllGroups(
425 func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ), 1, QgsExpression::tags( func->name() )
426 );
427 }
428}
429
430void QgsExpressionTreeView::loadLayers()
431{
432 if ( !mProject )
433 return;
434
435 QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
436 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
437 for ( ; layerIt != layers.constEnd(); ++layerIt )
438 {
439 QIcon icon = QgsIconUtils::iconForLayer( layerIt.value() );
440 QgsExpressionItem *parentItem
441 = registerItem( u"Map Layers"_s, layerIt.value()->name(), u"'%1'"_s.arg( layerIt.key() ), formatLayerHelp( layerIt.value() ), QgsExpressionItem::ExpressionNode, false, 99, icon );
442 loadLayerFields( qobject_cast<QgsVectorLayer *>( layerIt.value() ), parentItem );
443 }
444}
445
446void QgsExpressionTreeView::loadLayerFields( QgsVectorLayer *layer, QgsExpressionItem *parentItem )
447{
448 if ( !layer )
449 return;
450
451 const QgsFields fields = layer->fields();
452 for ( int fieldIdx = 0; fieldIdx < fields.count(); ++fieldIdx )
453 {
454 const QgsField field = fields.at( fieldIdx );
455 QIcon icon = fields.iconForField( fieldIdx );
456 const QString label { field.displayNameWithAlias() };
457 QgsExpressionItem *item = new QgsExpressionItem( label, " '" + field.name() + "' ", QString(), QgsExpressionItem::Field );
458 item->setData( label, Qt::UserRole );
459 item->setData( 99, QgsExpressionItem::CUSTOM_SORT_ROLE );
460 item->setData( QStringList(), QgsExpressionItem::SEARCH_TAGS_ROLE );
461 item->setData( field.name(), QgsExpressionItem::ITEM_NAME_ROLE );
462 item->setData( layer->id(), QgsExpressionItem::LAYER_ID_ROLE );
463 item->setIcon( icon );
464 parentItem->appendRow( item );
465 }
466}
467
469{
470 for ( int i = 0; i < fields.count(); ++i )
471 {
472 const QgsField field = fields.at( i );
473 QIcon icon = fields.iconForField( i );
474 registerItem( u"Fields and Values"_s, field.displayNameWithAlias(), " \"" + field.name() + "\" ", QString(), QgsExpressionItem::Field, false, i, icon, QStringList(), field.name() );
475 }
476}
477
479{
480 // Cleanup
481 if ( mExpressionGroups.contains( u"Fields and Values"_s ) )
482 {
483 QgsExpressionItem *node = mExpressionGroups.value( u"Fields and Values"_s );
484 node->removeRows( 0, node->rowCount() );
485 // Re-add NULL and feature variables
486 // use -1 as sort order here -- NULL and feature variables should always show before the field list
487 registerItem( u"Fields and Values"_s, u"NULL"_s, u"NULL"_s, QString(), QgsExpressionItem::ExpressionNode, false, -1 );
488 }
489
490 if ( mLayer )
491 {
492 // Add feature variables to record and attributes group (and highlighted items)
493
494 const QString currentFeatureHelp = formatVariableHelp( u"feature"_s, QgsExpression::variableHelpText( u"feature"_s ), false, QVariant() );
495 const QString currentFeatureIdHelp = formatVariableHelp( u"id"_s, QgsExpression::variableHelpText( u"id"_s ), false, QVariant() );
496 const QString currentGeometryHelp = formatVariableHelp( u"geometry"_s, QgsExpression::variableHelpText( u"geometry"_s ), false, QVariant() );
497
498 registerItem( u"Fields and Values"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode, false, -1 );
499 registerItem( u"Fields and Values"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode, false, -1 );
500 registerItem( u"Fields and Values"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, false, -1 );
501
502 registerItem( u"Variables"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode );
503 registerItem( u"Variables"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode );
504 registerItem( u"Variables"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, false );
505
506 registerItem( u"Record and Attributes"_s, u"feature"_s, u"@feature"_s, currentFeatureHelp, QgsExpressionItem::ExpressionNode, true, -1 );
507 registerItem( u"Record and Attributes"_s, u"id"_s, u"@id"_s, currentFeatureIdHelp, QgsExpressionItem::ExpressionNode, true, -1 );
508 registerItem( u"Record and Attributes"_s, u"geometry"_s, u"@geometry"_s, currentGeometryHelp, QgsExpressionItem::ExpressionNode, true, -1 );
509 }
510
511 // this can happen if fields are manually set
512 if ( !mLayer )
513 return;
514
515 const QgsFields &fields = mLayer->fields();
516
517 loadFieldNames( fields );
518}
519
520void QgsExpressionTreeView::loadRelations()
521{
522 if ( !mProject )
523 return;
524
525 QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
526 QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
527 for ( ; relIt != relations.constEnd(); ++relIt )
528 {
529 registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), u"'%1'"_s.arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
530 }
531}
532
533void QgsExpressionTreeView::loadRecent( const QString &collection )
534{
535 mRecentKey = collection;
536 QString name = tr( "Recent (%1)" ).arg( collection );
537 if ( mExpressionGroups.contains( name ) )
538 {
539 QgsExpressionItem *node = mExpressionGroups.value( name );
540 node->removeRows( 0, node->rowCount() );
541 }
542
543 QgsSettings settings;
544 const QString location = u"/expressions/recent/%1"_s.arg( collection );
545 const QStringList expressions = settings.value( location ).toStringList();
546 int i = 0;
547 for ( const QString &expression : expressions )
548 {
549 QString help = formatRecentExpressionHelp( expression, expression );
550 QString label = expression;
551 label.replace( '\n', ' ' );
552 registerItem( name, label, expression, help, QgsExpressionItem::ExpressionNode, false, i );
553 i++;
554 }
555}
556
557void QgsExpressionTreeView::saveToRecent( const QString &expressionText, const QString &collection )
558{
559 QgsSettings settings;
560 QString location = u"/expressions/recent/%1"_s.arg( collection );
561 QStringList expressions = settings.value( location ).toStringList();
562 expressions.removeAll( expressionText );
563
564 expressions.prepend( expressionText );
565
566 while ( expressions.count() > 20 )
567 {
568 expressions.pop_back();
569 }
570
571 settings.setValue( location, expressions );
572 loadRecent( collection );
573}
574
575void QgsExpressionTreeView::saveToUserExpressions( const QString &label, const QString &expression, const QString &helpText )
576{
577 QgsSettings settings;
578 const QString location = u"user"_s;
579 settings.beginGroup( location, QgsSettings::Section::Expressions );
580 settings.beginGroup( label );
581 settings.setValue( u"expression"_s, expression );
582 settings.setValue( u"helpText"_s, helpText );
584 // Scroll
585 const QModelIndexList idxs { mModel->match( mModel->index( 0, 0 ), Qt::DisplayRole, label, 1, Qt::MatchFlag::MatchRecursive ) };
586 if ( !idxs.isEmpty() )
587 {
588 scrollTo( idxs.first() );
589 }
590}
591
593{
594 QgsSettings settings;
595 settings.remove( u"user/%1"_s.arg( label ), QgsSettings::Section::Expressions );
597}
598
599// this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
601{
602 // Cleanup
603 if ( mExpressionGroups.contains( u"UserGroup"_s ) )
604 {
605 QgsExpressionItem *node = mExpressionGroups.value( u"UserGroup"_s );
606 node->removeRows( 0, node->rowCount() );
607 }
608
609 QgsSettings settings;
610 const QString location = u"user"_s;
611 settings.beginGroup( location, QgsSettings::Section::Expressions );
612 QString helpText;
613 QString expression;
614 int i = 0;
615 mUserExpressionLabels = settings.childGroups();
616 for ( const auto &label : std::as_const( mUserExpressionLabels ) )
617 {
618 settings.beginGroup( label );
619 expression = settings.value( u"expression"_s ).toString();
620 helpText = formatUserExpressionHelp( label, expression, settings.value( u"helpText"_s ).toString() );
621 registerItem( u"UserGroup"_s, label, expression, helpText, QgsExpressionItem::ExpressionNode, false, i++ );
622 settings.endGroup();
623 }
624}
625
627{
628 return mUserExpressionLabels;
629}
630
632{
633 const QString group = u"user"_s;
634 QgsSettings settings;
635 QJsonArray exportList;
636 QJsonObject
637 exportObject { { "qgis_version", Qgis::version() }, { "exported_at", QDateTime::currentDateTime().toString( Qt::ISODate ) }, { "author", QgsApplication::userFullName() }, { "expressions", exportList } };
638
640
641 mUserExpressionLabels = settings.childGroups();
642
643 for ( const QString &label : std::as_const( mUserExpressionLabels ) )
644 {
645 settings.beginGroup( label );
646
647 const QString expression = settings.value( u"expression"_s ).toString();
648 const QString helpText = settings.value( u"helpText"_s ).toString();
649 const QJsonObject expressionObject { { "name", label }, { "type", "expression" }, { "expression", expression }, { "group", group }, { "description", helpText } };
650 exportList.push_back( expressionObject );
651
652 settings.endGroup();
653 }
654
655 exportObject[u"expressions"_s] = exportList;
656 QJsonDocument exportJson = QJsonDocument( exportObject );
657
658 return exportJson;
659}
660
661void QgsExpressionTreeView::loadExpressionsFromJson( const QJsonDocument &expressionsDocument )
662{
663 // if the root of the json document is not an object, it means it's a wrong file
664 if ( !expressionsDocument.isObject() )
665 return;
666
667 QJsonObject expressionsObject = expressionsDocument.object();
668
669 // validate json for manadatory fields
670 if ( !expressionsObject[u"qgis_version"_s].isString() || !expressionsObject[u"exported_at"_s].isString() || !expressionsObject[u"author"_s].isString() || !expressionsObject[u"expressions"_s].isArray() )
671 return;
672
673 // validate versions
674 QVersionNumber qgisJsonVersion = QVersionNumber::fromString( expressionsObject[u"qgis_version"_s].toString() );
675 QVersionNumber qgisVersion = QVersionNumber::fromString( Qgis::version() );
676
677 // if the expressions are from newer version of QGIS, we ask the user to confirm
678 // they want to proceed
679 if ( qgisJsonVersion > qgisVersion )
680 {
681 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
682 switch ( QMessageBox::question(
683 this,
684 tr( "QGIS Version Mismatch" ),
685 tr(
686 "The imported expressions are from newer version of QGIS (%1) "
687 "and some of the expression might not work the current version (%2). "
688 "Are you sure you want to continue?"
689 )
690 .arg( qgisJsonVersion.toString(), qgisVersion.toString() ),
691 buttons
692 ) )
693 {
694 case QMessageBox::No:
695 return;
696
697 case QMessageBox::Yes:
698 break;
699
700 default:
701 break;
702 }
703 }
704
705 // we store the number of
706 QStringList skippedExpressionLabels;
707 bool isApplyToAll = false;
708 bool isOkToOverwrite = false;
709
710 QgsSettings settings;
711 settings.beginGroup( u"user"_s, QgsSettings::Section::Expressions );
712 mUserExpressionLabels = settings.childGroups();
713
714 const QJsonArray expressions = expressionsObject[u"expressions"_s].toArray();
715 for ( const QJsonValue &&expressionValue : expressions )
716 {
717 // validate the type of the array element, can be anything
718 if ( !expressionValue.isObject() )
719 {
720 // try to stringify and put and indicator what happened
721 skippedExpressionLabels.append( expressionValue.toString() );
722 continue;
723 }
724
725 QJsonObject expressionObj = expressionValue.toObject();
726
727 // make sure the required keys are the correct types
728 if ( !expressionObj[u"name"_s].isString()
729 || !expressionObj[u"type"_s].isString()
730 || !expressionObj[u"expression"_s].isString()
731 || !expressionObj[u"group"_s].isString()
732 || !expressionObj[u"description"_s].isString() )
733 {
734 // try to stringify and put an indicator what happened. Try to stringify the name, if fails, go with the expression.
735 if ( !expressionObj[u"name"_s].toString().isEmpty() )
736 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
737 else
738 skippedExpressionLabels.append( expressionObj[u"expression"_s].toString() );
739
740 continue;
741 }
742
743 // we want to import only items of type expression for now
744 if ( expressionObj[u"type"_s].toString() != "expression"_L1 )
745 {
746 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
747 continue;
748 }
749
750 // we want to import only items of type expression for now
751 if ( expressionObj[u"group"_s].toString() != "user"_L1 )
752 {
753 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
754 continue;
755 }
756
757 const QString label = expressionObj[u"name"_s].toString();
758 const QString expression = expressionObj[u"expression"_s].toString();
759 const QString helpText = expressionObj[u"description"_s].toString();
760
761 // make sure they have valid name
762 if ( label.contains( "\\"_L1 ) || label.contains( '/' ) )
763 {
764 skippedExpressionLabels.append( expressionObj[u"name"_s].toString() );
765 continue;
766 }
767
768 settings.beginGroup( label );
769 const QString oldExpression = settings.value( u"expression"_s ).toString();
770 settings.endGroup();
771
772 // TODO would be nice to skip the cases when labels and expressions match
773 if ( mUserExpressionLabels.contains( label ) && expression != oldExpression )
774 {
775 if ( !isApplyToAll )
776 showMessageBoxConfirmExpressionOverwrite( isApplyToAll, isOkToOverwrite, label, oldExpression, expression );
777
778 if ( isOkToOverwrite )
779 saveToUserExpressions( label, expression, helpText );
780 else
781 {
782 skippedExpressionLabels.append( label );
783 continue;
784 }
785 }
786 else
787 {
788 saveToUserExpressions( label, expression, helpText );
789 }
790 }
791
793
794 if ( !skippedExpressionLabels.isEmpty() )
795 {
796 QStringList skippedExpressionLabelsQuoted;
797 skippedExpressionLabelsQuoted.reserve( skippedExpressionLabels.size() );
798 for ( const QString &skippedExpressionLabel : skippedExpressionLabels )
799 skippedExpressionLabelsQuoted.append( u"'%1'"_s.arg( skippedExpressionLabel ) );
800
801 QMessageBox::information( this, tr( "Skipped Expression Imports" ), u"%1\n%2"_s.arg( tr( "The following expressions have been skipped:" ), skippedExpressionLabelsQuoted.join( ", "_L1 ) ) );
802 }
803}
804
805const QList<QgsExpressionItem *> QgsExpressionTreeView::findExpressions( const QString &label )
806{
807 QList<QgsExpressionItem *> result;
808 const QList<QStandardItem *> found { mModel->findItems( label, Qt::MatchFlag::MatchRecursive ) };
809 result.reserve( found.size() );
810 std::transform( found.begin(), found.end(), std::back_inserter( result ), []( QStandardItem *item ) -> QgsExpressionItem * { return static_cast<QgsExpressionItem *>( item ); } );
811 return result;
812}
813
814void QgsExpressionTreeView::showMessageBoxConfirmExpressionOverwrite( bool &isApplyToAll, bool &isOkToOverwrite, const QString &label, const QString &oldExpression, const QString &newExpression )
815{
816 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll;
817 switch ( QMessageBox::question(
818 this,
819 tr( "Expression Overwrite" ),
820 tr(
821 "The expression with label '%1' was already defined."
822 "The old expression \"%2\" will be overwritten by \"%3\"."
823 "Are you sure you want to overwrite the expression?"
824 )
825 .arg( label, oldExpression, newExpression ),
826 buttons
827 ) )
828 {
829 case QMessageBox::NoToAll:
830 isApplyToAll = true;
831 isOkToOverwrite = false;
832 break;
833
834 case QMessageBox::No:
835 isApplyToAll = false;
836 isOkToOverwrite = false;
837 break;
838
839 case QMessageBox::YesToAll:
840 isApplyToAll = true;
841 isOkToOverwrite = true;
842 break;
843
844 case QMessageBox::Yes:
845 isApplyToAll = false;
846 isOkToOverwrite = true;
847 break;
848
849 default:
850 break;
851 }
852}
853
854
855// ****************************
856// ****************************
857// QgsExpressionItemSearchProxy
858// ****************************
859
860
862{
863 setFilterCaseSensitivity( Qt::CaseInsensitive );
864}
865
866bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
867{
868 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
869 const QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
870
871 if ( itemType == QgsExpressionItem::Header )
872 {
873 // show header if any child item matches
874 int count = sourceModel()->rowCount( index );
875 bool matchchild = false;
876 for ( int i = 0; i < count; ++i )
877 {
878 if ( filterAcceptsRow( i, index ) )
879 {
880 matchchild = true;
881 break;
882 }
883 }
884 return matchchild;
885 }
886
887 // check match of item label or tags
888 const QString name = sourceModel()->data( index, Qt::DisplayRole ).toString();
889 if ( name.contains( mFilterString, Qt::CaseInsensitive ) )
890 {
891 return true;
892 }
893
894 const QStringList tags = sourceModel()->data( index, QgsExpressionItem::SEARCH_TAGS_ROLE ).toStringList();
895 return std::any_of( tags.begin(), tags.end(), [this]( const QString &tag ) { return tag.contains( mFilterString, Qt::CaseInsensitive ); } );
896}
897
899{
900 mFilterString = string;
901 invalidate();
902}
903
904bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
905{
906 int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
907 int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
908 if ( leftSort != rightSort )
909 return leftSort < rightSort;
910
911 QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
912 QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
913
914 //ignore $ prefixes when sorting
915 if ( leftString.startsWith( '$' ) )
916 leftString = leftString.mid( 1 );
917 if ( rightString.startsWith( '$' ) )
918 rightString = rightString.mid( 1 );
919
920 return QString::localeAwareCompare( leftString, rightString ) < 0;
921}
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.