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