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