QGIS API Documentation  3.17.0-Master (a035f434f4)
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 
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 }
void removeFromUserExpressions(const QString &label)
Removes the expression label from the user stored expressions.
QString name
Definition: qgsrelation.h:48
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true)
Formats an expression result for friendly display to the user.
QStringList childGroups() 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.
Definition: qgssettings.cpp:97
Base class for all map layer types.
Definition: qgsmaplayer.h:83
void expressionItemDoubleClicked(const QString &text)
Emitted when a expression item is double clicked.
bool isHighlightedFunction(const QString &name) const
Returns true if the specified function name is intended to be highlighted to the user.
QString formatRecentExpressionHelp(const QString &label, const QString &expression)
Returns a HTML formatted string for use as a recent expression item help.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the tree view.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
QString name
Definition: qgsfield.h:59
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
int params() const
The number of parameters this function takes.
QStringList groups() const
Returns a list of the groups the function belongs to.
void setMenuProvider(MenuProvider *provider)
Sets the menu provider.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QString formatRelationHelp(const QgsRelation &relation)
Returns a HTML formatted string for use as a relation item help.
static QString group(const QString &group)
Returns the translated name for a function group.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QgsExpressionItem * currentItem() const
Returns the current item or a nullptr.
QString id
Definition: qgsrelation.h:45
void loadUserExpressions()
Loads the user expressions.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
Container of fields for a vector layer.
Definition: qgsfields.h:44
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
static QStringList tags(const QString &name)
Returns a string list of search tags for a specified function.
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
static QString userFullName()
Returns the user&#39;s operating system login account full display name.
QString description(const QString &name) const
Returns a translated description string for the variable with specified name.
QJsonDocument exportUserExpressions()
Create the expressions JSON document storing all the user expressions to be exported.
Q_DECL_DEPRECATED QStandardItemModel * model()
Returns a pointer to the dialog&#39;s function item model.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QgsExpressionTreeView(QWidget *parent=nullptr)
Constructor.
static int functionCount()
Returns the number of functions defined in the parser.
QString formatUserExpressionHelp(const QString &label, const QString &expression, const QString &description)
Returns a HTML formatted string for use as a user expression item help.
static QString version()
Version string.
Definition: qgis.cpp:276
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QStringList userExpressionLabels() const
Returns the user expression labels.
QString formatLayerHelp(const QgsMapLayer *layer)
Returns a HTML formatted string for use as a layer item help.
bool isHighlightedVariable(const QString &name) const
Returns true if the specified variable name is intended to be highlighted to the user.
const QList< QgsExpressionItem * > findExpressions(const QString &label)
Returns the list of expression items matching a label.
static const int SEARCH_TAGS_ROLE
Search tags role.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field&#39;s type and source.
Definition: qgsfields.cpp:275
void loadRecent(const QString &collection=QStringLiteral("generic"))
Loads the recent expressions from the given collection.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:94
Implementation of this interface can be implemented to allow QgsExpressionTreeView instance to provid...
static const int ITEM_TYPE_ROLE
Item type role.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:49
void saveToUserExpressions(const QString &label, const QString expression, const QString &helpText)
Stores the user expression with given label and helpText.
static const QList< QgsExpressionFunction * > & Functions()
QgsExpressionItem::ItemType getItemType() const
Gets the type of expression item, e.g., header, field, ExpressionNode.
static const int CUSTOM_SORT_ROLE
Custom sort order role.
QString name() const
The name of the function.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
Definition: qgssettings.cpp:87
A abstract base class for defining QgsExpression functions.
An expression item that can be used in the QgsExpressionBuilderWidget tree.
virtual QMenu * createContextMenu(QgsExpressionItem *item)
Returns a newly created menu instance.
void loadExpressionsFromJson(const QJsonDocument &expressionsDocument)
Load and permanently store the expressions from the expressions JSON document.
QStringList functionNames() const
Retrieves a list of function names contained in the context.
const QString helpText() const
The help text for the function.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QString getExpressionText() const
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
void refresh()
Refreshes the content of the tree.
QString name
Definition: qgsmaplayer.h:87
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.
void currentExpressionItemChanged(QgsExpressionItem *item)
Emitter when the current expression item changed.
void loadFieldNames(const QgsFields &fields)
This allows loading fields without specifying a layer.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QgsProject * project()
Returns the project currently associated with the widget.
Represents a vector layer which manages a vector based data sets.
virtual bool isDeprecated() const
Returns true if the function is deprecated and should not be presented as a valid option to users in ...
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
QgsExpressionFunction * function(const QString &name) const
Fetches a matching function from the context.
const QgsField & field
Definition: qgsfield.h:471
void saveToRecent(const QString &expressionText, const QString &collection="generic")
Adds the current expression to the given collection.
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object...
static const int ITEM_NAME_ROLE
Item name role.
void setSearchText(const QString &text)
Sets the text to filter the expression tree.