QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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 )
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->setIcon( icon );
334 
335  // Look up the group and insert the new function.
336  if ( mExpressionGroups.contains( group ) )
337  {
338  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
339  groupNode->appendRow( item );
340  }
341  else
342  {
343  // If the group doesn't exist yet we make it first.
344  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QString(), QgsExpressionItem::Header );
345  newgroupNode->setData( group, Qt::UserRole );
346  //Recent group should always be last group
347  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
348  newgroupNode->appendRow( item );
349  newgroupNode->setBackground( QBrush( QColor( 150, 150, 150, 150 ) ) );
350  mModel->appendRow( newgroupNode );
351  mExpressionGroups.insert( group, newgroupNode );
352  }
353 
354  if ( highlightedItem )
355  {
356  //insert a copy as a top level item
357  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
358  topLevelItem->setData( label, Qt::UserRole );
359  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
360  QFont font = topLevelItem->font();
361  font.setBold( true );
362  topLevelItem->setFont( font );
363  mModel->appendRow( topLevelItem );
364  }
365 }
366 
367 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 )
368 {
369  const auto constGroups = groups;
370  for ( const QString &group : constGroups )
371  {
372  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder, QIcon(), tags );
373  }
374 }
375 
376 void QgsExpressionTreeView::loadExpressionContext()
377 {
378  QStringList variableNames = mExpressionContext.filteredVariableNames();
379  const auto constVariableNames = variableNames;
380  for ( const QString &variable : constVariableNames )
381  {
382  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
383  formatVariableHelp( variable, mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
385  mExpressionContext.isHighlightedVariable( variable ) );
386  }
387 
388  // Load the functions from the expression context
389  QStringList contextFunctions = mExpressionContext.functionNames();
390  const auto constContextFunctions = contextFunctions;
391  for ( const QString &functionName : constContextFunctions )
392  {
393  QgsExpressionFunction *func = mExpressionContext.function( functionName );
394  QString name = func->name();
395  if ( name.startsWith( '_' ) ) // do not display private functions
396  continue;
397  if ( func->params() != 0 )
398  name += '(';
399  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText(), QgsExpressionItem::ExpressionNode, mExpressionContext.isHighlightedFunction( func->name() ), 1, QgsExpression::tags( func->name() ) );
400  }
401 }
402 
403 void QgsExpressionTreeView::loadLayers()
404 {
405  if ( !mProject )
406  return;
407 
408  QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
409  QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
410  for ( ; layerIt != layers.constEnd(); ++layerIt )
411  {
412  registerItemForAllGroups( QStringList() << tr( "Map Layers" ), layerIt.value()->name(), QStringLiteral( "'%1'" ).arg( layerIt.key() ), formatLayerHelp( layerIt.value() ) );
413  }
414 }
415 
417 {
418  for ( int i = 0; i < fields.count(); ++i )
419  {
420  const QgsField field = fields.at( i );
421  QIcon icon = fields.iconForField( i );
422  registerItem( QStringLiteral( "Fields and Values" ), field.displayNameWithAlias(),
423  " \"" + field.name() + "\" ", QString(), QgsExpressionItem::Field, false, i, icon );
424  }
425 }
426 
427 void QgsExpressionTreeView::loadFieldNames()
428 {
429  // Cleanup
430  if ( mExpressionGroups.contains( QStringLiteral( "Fields and Values" ) ) )
431  {
432  QgsExpressionItem *node = mExpressionGroups.value( QStringLiteral( "Fields and Values" ) );
433  node->removeRows( 0, node->rowCount() );
434  // Re-add NULL
435  // use -1 as sort order here -- NULL should always show before the field list
436  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ), QString(), QgsExpressionItem::ExpressionNode, false, -1 );
437  }
438 
439  // this can happen if fields are manually set
440  if ( !mLayer )
441  return;
442 
443  const QgsFields &fields = mLayer->fields();
444 
445  loadFieldNames( fields );
446 }
447 
448 void QgsExpressionTreeView::loadRelations()
449 {
450  if ( !mProject )
451  return;
452 
453  QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
454  QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
455  for ( ; relIt != relations.constEnd(); ++relIt )
456  {
457  registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), QStringLiteral( "'%1'" ).arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
458  }
459 }
460 
461 void QgsExpressionTreeView::loadRecent( const QString &collection )
462 {
463  mRecentKey = collection;
464  QString name = tr( "Recent (%1)" ).arg( collection );
465  if ( mExpressionGroups.contains( name ) )
466  {
467  QgsExpressionItem *node = mExpressionGroups.value( name );
468  node->removeRows( 0, node->rowCount() );
469  }
470 
471  QgsSettings settings;
472  const QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
473  const QStringList expressions = settings.value( location ).toStringList();
474  int i = 0;
475  for ( const QString &expression : expressions )
476  {
477  QString help = formatRecentExpressionHelp( expression, expression );
478  registerItem( name, expression, expression, help, QgsExpressionItem::ExpressionNode, false, i );
479  i++;
480  }
481 }
482 
483 void QgsExpressionTreeView::saveToRecent( const QString &expressionText, const QString &collection )
484 {
485  QgsSettings settings;
486  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
487  QStringList expressions = settings.value( location ).toStringList();
488  expressions.removeAll( expressionText );
489 
490  expressions.prepend( expressionText );
491 
492  while ( expressions.count() > 20 )
493  {
494  expressions.pop_back();
495  }
496 
497  settings.setValue( location, expressions );
498  loadRecent( collection );
499 }
500 
501 void QgsExpressionTreeView::saveToUserExpressions( const QString &label, const QString expression, const QString &helpText )
502 {
503  QgsSettings settings;
504  const QString location = QStringLiteral( "user" );
505  settings.beginGroup( location, QgsSettings::Section::Expressions );
506  settings.beginGroup( label );
507  settings.setValue( QStringLiteral( "expression" ), expression );
508  settings.setValue( QStringLiteral( "helpText" ), helpText );
510  // Scroll
511  const QModelIndexList idxs { mModel->match( mModel->index( 0, 0 ),
512  Qt::DisplayRole, label, 1,
513  Qt::MatchFlag::MatchRecursive ) };
514  if ( ! idxs.isEmpty() )
515  {
516  scrollTo( idxs.first() );
517  }
518 }
519 
521 {
522  QgsSettings settings;
523  settings.remove( QStringLiteral( "user/%1" ).arg( label ), QgsSettings::Section::Expressions );
525 }
526 
527 // this is potentially very slow if there are thousands of user expressions, every time entire cleanup and load
529 {
530  // Cleanup
531  if ( mExpressionGroups.contains( QStringLiteral( "UserGroup" ) ) )
532  {
533  QgsExpressionItem *node = mExpressionGroups.value( QStringLiteral( "UserGroup" ) );
534  node->removeRows( 0, node->rowCount() );
535  }
536 
537  QgsSettings settings;
538  const QString location = QStringLiteral( "user" );
539  settings.beginGroup( location, QgsSettings::Section::Expressions );
540  QString label;
541  QString helpText;
542  QString expression;
543  int i = 0;
544  mUserExpressionLabels = settings.childGroups();
545  for ( const auto &label : qgis::as_const( mUserExpressionLabels ) )
546  {
547  settings.beginGroup( label );
548  expression = settings.value( QStringLiteral( "expression" ) ).toString();
549  helpText = formatUserExpressionHelp( label, expression, settings.value( QStringLiteral( "helpText" ) ).toString() );
550  registerItem( QStringLiteral( "UserGroup" ), label, expression, helpText, QgsExpressionItem::ExpressionNode, false, i++ );
551  settings.endGroup();
552  }
553 }
554 
556 {
557  return mUserExpressionLabels;
558 }
559 
561 {
562  const QString group = QStringLiteral( "user" );
563  QgsSettings settings;
564  QJsonArray exportList;
565  QJsonObject exportObject
566  {
567  {"qgis_version", Qgis::version()},
568  {"exported_at", QDateTime::currentDateTime().toString( Qt::ISODate )},
569  {"author", QgsApplication::userFullName()},
570  {"expressions", exportList}
571  };
572 
573  settings.beginGroup( group, QgsSettings::Section::Expressions );
574 
575  mUserExpressionLabels = settings.childGroups();
576 
577  for ( const QString &label : qgis::as_const( mUserExpressionLabels ) )
578  {
579  settings.beginGroup( label );
580 
581  const QString expression = settings.value( QStringLiteral( "expression" ) ).toString();
582  const QString helpText = settings.value( QStringLiteral( "helpText" ) ).toString();
583  const QJsonObject expressionObject
584  {
585  {"name", label},
586  {"type", "expression"},
587  {"expression", expression},
588  {"group", group},
589  {"description", helpText}
590  };
591  exportList.push_back( expressionObject );
592 
593  settings.endGroup();
594  }
595 
596  exportObject["expressions"] = exportList;
597  QJsonDocument exportJson = QJsonDocument( exportObject );
598 
599  return exportJson;
600 }
601 
602 void QgsExpressionTreeView::loadExpressionsFromJson( const QJsonDocument &expressionsDocument )
603 {
604  // if the root of the json document is not an object, it means it's a wrong file
605  if ( ! expressionsDocument.isObject() )
606  return;
607 
608  QJsonObject expressionsObject = expressionsDocument.object();
609 
610  // validate json for manadatory fields
611  if ( ! expressionsObject["qgis_version"].isString()
612  || ! expressionsObject["exported_at"].isString()
613  || ! expressionsObject["author"].isString()
614  || ! expressionsObject["expressions"].isArray() )
615  return;
616 
617  // validate versions
618  QVersionNumber qgisJsonVersion = QVersionNumber::fromString( expressionsObject["qgis_version"].toString() );
619  QVersionNumber qgisVersion = QVersionNumber::fromString( Qgis::version() );
620 
621  // if the expressions are from newer version of QGIS, we ask the user to confirm
622  // they want to proceed
623  if ( qgisJsonVersion > qgisVersion )
624  {
625  QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
626  switch ( QMessageBox::question( this,
627  tr( "QGIS Version Mismatch" ),
628  tr( "The imported expressions are from newer version of QGIS (%1) "
629  "and some of the expression might not work the current version (%2). "
630  "Are you sure you want to continue?" ).arg( qgisJsonVersion.toString(), qgisVersion.toString() ), buttons ) )
631  {
632  case QMessageBox::No:
633  return;
634 
635  case QMessageBox::Yes:
636  break;
637 
638  default:
639  break;
640  }
641  }
642 
643  // we store the number of
644  QStringList skippedExpressionLabels;
645  bool isApplyToAll = false;
646  bool isOkToOverwrite = false;
647 
648  QgsSettings settings;
649  settings.beginGroup( QStringLiteral( "user" ), QgsSettings::Section::Expressions );
650  mUserExpressionLabels = settings.childGroups();
651 
652  for ( const QJsonValue &expressionValue : expressionsObject["expressions"].toArray() )
653  {
654  // validate the type of the array element, can be anything
655  if ( ! expressionValue.isObject() )
656  {
657  // try to stringify and put and indicator what happened
658  skippedExpressionLabels.append( expressionValue.toString() );
659  continue;
660  }
661 
662  QJsonObject expressionObj = expressionValue.toObject();
663 
664  // make sure the required keys are the correct types
665  if ( ! expressionObj["name"].isString()
666  || ! expressionObj["type"].isString()
667  || ! expressionObj["expression"].isString()
668  || ! expressionObj["group"].isString()
669  || ! expressionObj["description"].isString() )
670  {
671  // try to stringify and put an indicator what happened. Try to stringify the name, if fails, go with the expression.
672  if ( ! expressionObj["name"].toString().isEmpty() )
673  skippedExpressionLabels.append( expressionObj["name"].toString() );
674  else
675  skippedExpressionLabels.append( expressionObj["expression"].toString() );
676 
677  continue;
678  }
679 
680  // we want to import only items of type expression for now
681  if ( expressionObj["type"].toString() != QStringLiteral( "expression" ) )
682  {
683  skippedExpressionLabels.append( expressionObj["name"].toString() );
684  continue;
685  }
686 
687  // we want to import only items of type expression for now
688  if ( expressionObj["group"].toString() != QStringLiteral( "user" ) )
689  {
690  skippedExpressionLabels.append( expressionObj["name"].toString() );
691  continue;
692  }
693 
694  const QString label = expressionObj["name"].toString();
695  const QString expression = expressionObj["expression"].toString();
696  const QString helpText = expressionObj["description"].toString();
697 
698  // make sure they have valid name
699  if ( label.contains( "\\" ) || label.contains( '/' ) )
700  {
701  skippedExpressionLabels.append( expressionObj["name"].toString() );
702  continue;
703  }
704 
705  settings.beginGroup( label );
706  const QString oldExpression = settings.value( QStringLiteral( "expression" ) ).toString();
707  settings.endGroup();
708 
709  // TODO would be nice to skip the cases when labels and expressions match
710  if ( mUserExpressionLabels.contains( label ) && expression != oldExpression )
711  {
712  if ( ! isApplyToAll )
713  showMessageBoxConfirmExpressionOverwrite( isApplyToAll, isOkToOverwrite, label, oldExpression, expression );
714 
715  if ( isOkToOverwrite )
716  saveToUserExpressions( label, expression, helpText );
717  else
718  {
719  skippedExpressionLabels.append( label );
720  continue;
721  }
722  }
723  else
724  {
725  saveToUserExpressions( label, expression, helpText );
726  }
727  }
728 
730 
731  if ( ! skippedExpressionLabels.isEmpty() )
732  {
733  QStringList skippedExpressionLabelsQuoted;
734  for ( const QString &skippedExpressionLabel : skippedExpressionLabels )
735  skippedExpressionLabelsQuoted.append( QStringLiteral( "'%1'" ).arg( skippedExpressionLabel ) );
736 
737  QMessageBox::information( this,
738  tr( "Skipped Expression Imports" ),
739  QStringLiteral( "%1\n%2" ).arg( tr( "The following expressions have been skipped:" ),
740  skippedExpressionLabelsQuoted.join( ", " ) ) );
741  }
742 }
743 
744 const QList<QgsExpressionItem *> QgsExpressionTreeView::findExpressions( const QString &label )
745 {
746  QList<QgsExpressionItem *> result;
747  const QList<QStandardItem *> found { mModel->findItems( label, Qt::MatchFlag::MatchRecursive ) };
748  for ( const auto &item : qgis::as_const( found ) )
749  {
750  result.push_back( static_cast<QgsExpressionItem *>( item ) );
751  }
752  return result;
753 }
754 
755 void QgsExpressionTreeView::showMessageBoxConfirmExpressionOverwrite(
756  bool &isApplyToAll,
757  bool &isOkToOverwrite,
758  const QString &label,
759  const QString &oldExpression,
760  const QString &newExpression )
761 {
762  QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll;
763  switch ( QMessageBox::question( this,
764  tr( "Expression Overwrite" ),
765  tr( "The expression with label '%1' was already defined."
766  "The old expression \"%2\" will be overwritten by \"%3\"."
767  "Are you sure you want to overwrite the expression?" ).arg( label, oldExpression, newExpression ), buttons ) )
768  {
769  case QMessageBox::NoToAll:
770  isApplyToAll = true;
771  isOkToOverwrite = false;
772  break;
773 
774  case QMessageBox::No:
775  isApplyToAll = false;
776  isOkToOverwrite = false;
777  break;
778 
779  case QMessageBox::YesToAll:
780  isApplyToAll = true;
781  isOkToOverwrite = true;
782  break;
783 
784  case QMessageBox::Yes:
785  isApplyToAll = false;
786  isOkToOverwrite = true;
787  break;
788 
789  default:
790  break;
791  }
792 }
793 
794 
795 // ****************************
796 // ****************************
797 // QgsExpressionItemSearchProxy
798 // ****************************
799 
800 
802 {
803  setFilterCaseSensitivity( Qt::CaseInsensitive );
804 }
805 
806 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
807 {
808  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
809  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
810 
811  int count = sourceModel()->rowCount( index );
812  bool matchchild = false;
813  for ( int i = 0; i < count; ++i )
814  {
815  if ( filterAcceptsRow( i, index ) )
816  {
817  matchchild = true;
818  break;
819  }
820  }
821 
822  if ( itemType == QgsExpressionItem::Header && matchchild )
823  return true;
824 
825  if ( itemType == QgsExpressionItem::Header )
826  return false;
827 
828  // check match of item label or tags
829  if ( QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ) )
830  {
831  return true;
832  }
833  else
834  {
835  const QStringList tags = sourceModel()->data( index, QgsExpressionItem::SEARCH_TAGS_ROLE ).toStringList();
836  for ( const QString &tag : tags )
837  {
838  if ( tag.contains( filterRegExp() ) )
839  return true;
840  }
841  }
842  return false;
843 }
844 
845 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
846 {
847  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
848  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
849  if ( leftSort != rightSort )
850  return leftSort < rightSort;
851 
852  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
853  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
854 
855  //ignore $ prefixes when sorting
856  if ( leftString.startsWith( '$' ) )
857  leftString = leftString.mid( 1 );
858  if ( rightString.startsWith( '$' ) )
859  rightString = rightString.mid( 1 );
860 
861  return QString::localeAwareCompare( leftString, rightString ) < 0;
862 }
QgsExpressionContext
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Definition: qgsexpressioncontext.h:369
qgsexpressioncontextutils.h
QgsExpressionTreeView::loadRecent
void loadRecent(const QString &collection=QStringLiteral("generic"))
Loads the recent expressions from the given collection.
Definition: qgsexpressiontreeview.cpp:461
QgsExpressionFunction::helpText
const QString helpText() const
The help text for the function.
Definition: qgsexpressionfunction.cpp:68
QgsExpressionItemSearchProxy::filterAcceptsRow
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
Definition: qgsexpressiontreeview.cpp:806
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
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:97
QgsRelation::name
QString name
Definition: qgsrelation.h:48
QgsFields::count
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsFields
Definition: qgsfields.h:44
QgsExpressionContextUtils::layerScope
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Definition: qgsexpressioncontextutils.cpp:264
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
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
Definition: qgssettings.h:61
QgsExpressionItem::ItemType
ItemType
Definition: qgsexpressiontreeview.h:40
QgsExpressionContext::description
QString description(const QString &name) const
Returns a translated description string for the variable with specified name.
Definition: qgsexpressioncontext.cpp:442
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
Definition: qgsproject.h:92
QgsRelation::id
QString id
Definition: qgsrelation.h:45
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:941
QgsExpressionTreeView::loadUserExpressions
void loadUserExpressions()
Loads the user expressions.
Definition: qgsexpressiontreeview.cpp:528
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3280
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:602
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
Definition: qgsexpressiontreeview.h:146
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:908
QgsExpressionTreeView::userExpressionLabels
QStringList userExpressionLabels() const
Returns the user expression labels.
Definition: qgsexpressiontreeview.cpp:555
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:154
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:139
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:5649
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
QgsFields::iconForField
QIcon iconForField(int fieldIdx) const
Returns an icon corresponding to a field index, based on the field's type and source.
Definition: qgsfields.cpp:275
QgsExpressionTreeView::removeFromUserExpressions
void removeFromUserExpressions(const QString &label)
Removes the expression label from the user stored expressions.
Definition: qgsexpressiontreeview.cpp:520
QgsSettings::beginGroup
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
Definition: qgssettings.cpp:87
QgsExpressionFunction
Definition: qgsexpressionfunction.h:40
QgsVectorLayer
Definition: qgsvectorlayer.h:385
QgsExpressionTreeView::QgsExpressionTreeView
QgsExpressionTreeView(QWidget *parent=nullptr)
Constructor.
Definition: qgsexpressiontreeview.cpp:107
QgsMapLayer
Definition: qgsmaplayer.h:81
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:744
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:85
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
Definition: qgsexpressiontreeview.h:37
QgsRelation
Definition: qgsrelation.h:41
QgsExpressionItemSearchProxy::QgsExpressionItemSearchProxy
QgsExpressionItemSearchProxy()
Definition: qgsexpressiontreeview.cpp:801
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:483
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:560
QgsExpressionItemSearchProxy::lessThan
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
Definition: qgsexpressiontreeview.cpp:845
QgsExpressionTreeView::loadFieldNames
void loadFieldNames(const QgsFields &fields)
This allows loading fields without specifying a layer.
Definition: qgsexpressiontreeview.cpp:416
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:1112
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:501
QgsField
Definition: qgsfield.h:49
QgsExpressionTreeView::setProject
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
Definition: qgsexpressiontreeview.cpp:187