QGIS API Documentation  2.99.0-Master (19b062c)
qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgisexpressionbuilderwidget.cpp - A generic expression string builder widget.
3  --------------------------------------
4  Date : 29-May-2011
5  Copyright : (C) 2011 by Nathan Woodrow
6  Email : woodrow.nathan at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 #include "qgslogger.h"
18 #include "qgsexpression.h"
19 #include "qgsexpressionfunction.h"
20 #include "qgsmessageviewer.h"
21 #include "qgsapplication.h"
22 #include "qgspythonrunner.h"
23 #include "qgsgeometry.h"
24 #include "qgsfeature.h"
25 #include "qgsfeatureiterator.h"
26 #include "qgsvectorlayer.h"
27 #include "qgssettings.h"
28 #include "qgsproject.h"
29 #include "qgsrelationmanager.h"
30 #include "qgsrelation.h"
31 
32 #include <QMenu>
33 #include <QFile>
34 #include <QTextStream>
35 #include <QDir>
36 #include <QInputDialog>
37 #include <QComboBox>
38 #include <QGraphicsOpacityEffect>
39 #include <QPropertyAnimation>
40 
41 
43  : QWidget( parent )
44  , mProject( QgsProject::instance() )
45 {
46  setupUi( this );
47  connect( btnRun, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnRun_pressed );
48  connect( btnNewFile, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
49  connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
50  connect( expressionTree, &QTreeView::doubleClicked, this, &QgsExpressionBuilderWidget::expressionTree_doubleClicked );
51  connect( txtExpressionString, &QgsCodeEditorSQL::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
52  connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
53  connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
54  connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEdit_textChanged );
55  connect( lblPreview, &QLabel::linkActivated, this, &QgsExpressionBuilderWidget::lblPreview_linkActivated );
56  connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked );
57 
58  mValueGroupBox->hide();
59  mLoadGroupBox->hide();
60 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
61 
62  mModel = new QStandardItemModel();
63  mProxyModel = new QgsExpressionItemSearchProxy();
64  mProxyModel->setDynamicSortFilter( true );
65  mProxyModel->setSourceModel( mModel );
66  expressionTree->setModel( mProxyModel );
67  expressionTree->setSortingEnabled( true );
68  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
69 
70  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
71  connect( this, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsExpressionBuilderWidget::setExpressionState );
72  connect( expressionTree, &QWidget::customContextMenuRequested, this, &QgsExpressionBuilderWidget::showContextMenu );
73  connect( expressionTree->selectionModel(), &QItemSelectionModel::currentChanged,
74  this, &QgsExpressionBuilderWidget::currentChanged );
75 
76  connect( btnLoadAll, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadAllValues );
77  connect( btnLoadSample, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadSampleValues );
78 
79  Q_FOREACH ( QPushButton *button, mOperatorsGroupBox->findChildren<QPushButton *>() )
80  {
81  connect( button, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
82  }
83 
84  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
85 
86  mValuesModel = new QStringListModel();
87  mProxyValues = new QSortFilterProxyModel();
88  mProxyValues->setSourceModel( mValuesModel );
89  mValuesListView->setModel( mProxyValues );
90  txtSearchEditValues->setPlaceholderText( tr( "Search" ) );
91 
92  QgsSettings settings;
93  splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
94  editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
95  functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
96 
97  txtExpressionString->setFoldingVisible( false );
98 
99  updateFunctionTree();
100 
101  if ( QgsPythonRunner::isValid() )
102  {
103  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
104  updateFunctionFileList( mFunctionsPath );
105  }
106  else
107  {
108  tab_2->hide();
109  }
110 
111  // select the first item in the function list
112  // in order to avoid a blank help widget
113  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
114  expressionTree->setCurrentIndex( firstItem );
115 
116  lblAutoSave->clear();
117  txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
118 }
119 
120 
122 {
123  QgsSettings settings;
124  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
125  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
126  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
127 
128  delete mModel;
129  delete mProxyModel;
130  delete mValuesModel;
131  delete mProxyValues;
132 }
133 
135 {
136  mLayer = layer;
137 
138  //TODO - remove existing layer scope from context
139 
140  if ( mLayer )
141  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
142 }
143 
144 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
145 {
146  txtSearchEditValues->clear();
147 
148  // Get the item
149  QModelIndex idx = mProxyModel->mapToSource( index );
150  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
151  if ( !item )
152  return;
153 
154  if ( item->getItemType() == QgsExpressionItem::Field && mFieldValues.contains( item->text() ) )
155  {
156  const QStringList &values = mFieldValues[item->text()];
157  mValuesModel->setStringList( values );
158  }
159 
160  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
161  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
162 
163  // Show the help for the current item.
164  QString help = loadFunctionHelp( item );
165  txtHelpText->setText( help );
166 }
167 
168 void QgsExpressionBuilderWidget::btnRun_pressed()
169 {
170  if ( !cmbFileNames->currentItem() )
171  return;
172 
173  QString file = cmbFileNames->currentItem()->text();
174  saveFunctionFile( file );
175  runPythonCode( txtPython->text() );
176 }
177 
178 void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
179 {
180  if ( QgsPythonRunner::isValid() )
181  {
182  QString pythontext = code;
183  QgsPythonRunner::run( pythontext );
184  }
185  updateFunctionTree();
186  loadFieldNames();
187  loadRecent( mRecentKey );
188 }
189 
191 {
192  QDir myDir( mFunctionsPath );
193  if ( !myDir.exists() )
194  {
195  myDir.mkpath( mFunctionsPath );
196  }
197 
198  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
199  {
200  fileName.append( ".py" );
201  }
202 
203  fileName = mFunctionsPath + QDir::separator() + fileName;
204  QFile myFile( fileName );
205  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
206  {
207  QTextStream myFileStream( &myFile );
208  myFileStream << txtPython->text() << endl;
209  myFile.close();
210  }
211 }
212 
214 {
215  mFunctionsPath = path;
216  QDir dir( path );
217  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
218  QStringList files = dir.entryList( QDir::Files );
219  cmbFileNames->clear();
220  Q_FOREACH ( const QString &name, files )
221  {
222  QFileInfo info( mFunctionsPath + QDir::separator() + name );
223  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
224  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), info.baseName() );
225  cmbFileNames->addItem( item );
226  }
227  if ( !cmbFileNames->currentItem() )
228  cmbFileNames->setCurrentRow( 0 );
229 }
230 
231 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
232 {
233  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
234  if ( !items.isEmpty() )
235  return;
236 
237  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), fileName );
238  cmbFileNames->insertItem( 0, item );
239  cmbFileNames->setCurrentRow( 0 );
240 
241  QString templatetxt;
242  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressions.template" ), templatetxt );
243  txtPython->setText( templatetxt );
244  saveFunctionFile( fileName );
245 }
246 
247 void QgsExpressionBuilderWidget::btnNewFile_pressed()
248 {
249  bool ok;
250  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
251  tr( "File name:" ), QLineEdit::Normal,
252  QLatin1String( "" ), &ok );
253  if ( ok && !text.isEmpty() )
254  {
255  newFunctionFile( text );
256  }
257 }
258 
259 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
260 {
261  if ( lastitem )
262  {
263  QString filename = lastitem->text();
264  saveFunctionFile( filename );
265  }
266  QString path = mFunctionsPath + QDir::separator() + item->text();
267  loadCodeFromFile( path );
268 }
269 
271 {
272  if ( !path.endsWith( QLatin1String( ".py" ) ) )
273  path.append( ".py" );
274 
275  txtPython->loadScript( path );
276 }
277 
279 {
280  txtPython->setText( code );
281 }
282 
283 void QgsExpressionBuilderWidget::expressionTree_doubleClicked( const QModelIndex &index )
284 {
285  QModelIndex idx = mProxyModel->mapToSource( index );
286  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
287  if ( !item )
288  return;
289 
290  // Don't handle the double-click if we are on a header node.
291  if ( item->getItemType() == QgsExpressionItem::Header )
292  return;
293 
294  // Insert the expression text or replace selected text
295  txtExpressionString->insertText( item->getExpressionText() );
296  txtExpressionString->setFocus();
297 }
298 
300 {
301  // TODO We should really return a error the user of the widget that
302  // the there is no layer set.
303  if ( !mLayer )
304  return;
305 
306  loadFieldNames( mLayer->fields() );
307 }
308 
310 {
311  if ( fields.isEmpty() )
312  return;
313 
314  QStringList fieldNames;
315  //Q_FOREACH ( const QgsField& field, fields )
316  fieldNames.reserve( fields.count() );
317  for ( int i = 0; i < fields.count(); ++i )
318  {
319  QString fieldName = fields.at( i ).name();
320  fieldNames << fieldName;
321  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QLatin1String( "" ), QgsExpressionItem::Field, false, i );
322  }
323 // highlighter->addFields( fieldNames );
324 }
325 
326 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
327 {
328  QgsFields fields;
329  for ( auto it = fieldValues.constBegin(); it != fieldValues.constEnd(); ++it )
330  {
331  fields.append( QgsField( it.key() ) );
332  }
333  loadFieldNames( fields );
334  mFieldValues = fieldValues;
335 }
336 
337 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit )
338 {
339  // TODO We should really return a error the user of the widget that
340  // the there is no layer set.
341  if ( !mLayer )
342  return;
343 
344  // TODO We should thread this so that we don't hold the user up if the layer is massive.
345 
346  int fieldIndex = mLayer->fields().lookupField( fieldName );
347 
348  if ( fieldIndex < 0 )
349  return;
350 
351  QStringList strValues;
352  QSet<QVariant> values = mLayer->uniqueValues( fieldIndex, countLimit );
353  Q_FOREACH ( const QVariant &value, values )
354  {
355  QString strValue;
356  if ( value.isNull() )
357  strValue = QStringLiteral( "NULL" );
358  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
359  strValue = value.toString();
360  else
361  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
362  strValues.append( strValue );
363  }
364  mValuesModel->setStringList( strValues );
365  mFieldValues[fieldName] = strValues;
366 }
367 
368 void QgsExpressionBuilderWidget::registerItem( const QString &group,
369  const QString &label,
370  const QString &expressionText,
371  const QString &helpText,
372  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
373 {
374  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
375  item->setData( label, Qt::UserRole );
376  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
377 
378  // Look up the group and insert the new function.
379  if ( mExpressionGroups.contains( group ) )
380  {
381  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
382  groupNode->appendRow( item );
383  }
384  else
385  {
386  // If the group doesn't exist yet we make it first.
387  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
388  newgroupNode->setData( group, Qt::UserRole );
389  //Recent group should always be last group
390  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
391  newgroupNode->appendRow( item );
392  newgroupNode->setBackground( QBrush( QColor( 238, 238, 238 ) ) );
393  mModel->appendRow( newgroupNode );
394  mExpressionGroups.insert( group, newgroupNode );
395  }
396 
397  if ( highlightedItem )
398  {
399  //insert a copy as a top level item
400  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
401  topLevelItem->setData( label, Qt::UserRole );
402  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
403  QFont font = topLevelItem->font();
404  font.setBold( true );
405  topLevelItem->setFont( font );
406  mModel->appendRow( topLevelItem );
407  }
408 
409 }
410 
412 {
413  return mExpressionValid;
414 }
415 
416 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
417 {
418  QgsSettings settings;
419  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
420  QStringList expressions = settings.value( location ).toStringList();
421  expressions.removeAll( this->expressionText() );
422 
423  expressions.prepend( this->expressionText() );
424 
425  while ( expressions.count() > 20 )
426  {
427  expressions.pop_back();
428  }
429 
430  settings.setValue( location, expressions );
431  this->loadRecent( collection );
432 }
433 
434 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
435 {
436  mRecentKey = collection;
437  QString name = tr( "Recent (%1)" ).arg( collection );
438  if ( mExpressionGroups.contains( name ) )
439  {
440  QgsExpressionItem *node = mExpressionGroups.value( name );
441  node->removeRows( 0, node->rowCount() );
442  }
443 
444  QgsSettings settings;
445  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
446  QStringList expressions = settings.value( location ).toStringList();
447  int i = 0;
448  Q_FOREACH ( const QString &expression, expressions )
449  {
450  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
451  i++;
452  }
453 }
454 
455 void QgsExpressionBuilderWidget::loadLayers()
456 {
457  if ( !mProject )
458  return;
459 
460  QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
461  QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
462  for ( ; layerIt != layers.constEnd(); ++layerIt )
463  {
464  registerItemForAllGroups( QStringList() << tr( "Map Layers" ), layerIt.value()->name(), QStringLiteral( "'%1'" ).arg( layerIt.key() ), formatLayerHelp( layerIt.value() ) );
465  }
466 }
467 
468 void QgsExpressionBuilderWidget::loadRelations()
469 {
470  if ( !mProject )
471  return;
472 
473  QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
474  QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
475  for ( ; relIt != relations.constEnd(); ++relIt )
476  {
477  registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), QStringLiteral( "'%1'" ).arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
478  }
479 }
480 
481 void QgsExpressionBuilderWidget::updateFunctionTree()
482 {
483  mModel->clear();
484  mExpressionGroups.clear();
485  // TODO Can we move this stuff to QgsExpression, like the functions?
486  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
487  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
488  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "*" ), QStringLiteral( " * " ) );
489  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "/" ), QStringLiteral( " / " ) );
490  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "%" ), QStringLiteral( " % " ) );
491  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "^" ), QStringLiteral( " ^ " ) );
492  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "=" ), QStringLiteral( " = " ) );
493  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "~" ), QStringLiteral( " ~ " ) );
494  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">" ), QStringLiteral( " > " ) );
495  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<" ), QStringLiteral( " < " ) );
496  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
497  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
498  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
499  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
500  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
501  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
502  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
503  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
504  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
505  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
506  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
507 
508  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
509  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
510 
511  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
512 
513  // Load the functions from the QgsExpression class
514  int count = QgsExpression::functionCount();
515  for ( int i = 0; i < count; i++ )
516  {
517  QgsExpressionFunction *func = QgsExpression::Functions()[i];
518  QString name = func->name();
519  if ( name.startsWith( '_' ) ) // do not display private functions
520  continue;
521  if ( func->isDeprecated() ) // don't show deprecated functions
522  continue;
523  if ( func->isContextual() )
524  {
525  //don't show contextual functions by default - it's up the the QgsExpressionContext
526  //object to provide them if supported
527  continue;
528  }
529  if ( func->params() != 0 )
530  name += '(';
531  else if ( !name.startsWith( '$' ) )
532  name += QLatin1String( "()" );
533  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
534  }
535 
536  // load relation names
537  loadRelations();
538 
539  // load layer IDs
540  loadLayers();
541 
542  loadExpressionContext();
543 }
544 
546 {
547  mDa = da;
548 }
549 
551 {
552  return txtExpressionString->text();
553 }
554 
555 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
556 {
557  txtExpressionString->setText( expression );
558 }
559 
561 {
562  mExpressionContext = context;
563  updateFunctionTree();
564  loadFieldNames();
565  loadRecent( mRecentKey );
566 }
567 
568 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
569 {
570  QString text = expressionText();
571 
572  // If the string is empty the expression will still "fail" although
573  // we don't show the user an error as it will be confusing.
574  if ( text.isEmpty() )
575  {
576  lblPreview->clear();
577  lblPreview->setStyleSheet( QLatin1String( "" ) );
578  txtExpressionString->setToolTip( QLatin1String( "" ) );
579  lblPreview->setToolTip( QLatin1String( "" ) );
580  emit expressionParsed( false );
581  return;
582  }
583 
584  QgsExpression exp( text );
585 
586  if ( mLayer )
587  {
588  // Only set calculator if we have layer, else use default.
589  exp.setGeomCalculator( &mDa );
590 
591  if ( !mExpressionContext.feature().isValid() )
592  {
593  // no feature passed yet, try to get from layer
594  QgsFeature f;
595  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
596  mExpressionContext.setFeature( f );
597  }
598  }
599 
600  QVariant value = exp.evaluate( &mExpressionContext );
601  if ( !exp.hasEvalError() )
602  {
603  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
604  }
605 
606  if ( exp.hasParserError() || exp.hasEvalError() )
607  {
608  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
609  if ( exp.hasEvalError() )
610  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
611 
612  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
613  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
614  txtExpressionString->setToolTip( tooltip );
615  lblPreview->setToolTip( tooltip );
616  emit expressionParsed( false );
617  return;
618  }
619  else
620  {
621  lblPreview->setStyleSheet( QLatin1String( "" ) );
622  txtExpressionString->setToolTip( QLatin1String( "" ) );
623  lblPreview->setToolTip( QLatin1String( "" ) );
624  emit expressionParsed( true );
625  }
626 }
627 
628 void QgsExpressionBuilderWidget::loadExpressionContext()
629 {
630  QStringList variableNames = mExpressionContext.filteredVariableNames();
631  Q_FOREACH ( const QString &variable, variableNames )
632  {
633  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
634  QgsExpression::formatVariableHelp( mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
636  mExpressionContext.isHighlightedVariable( variable ) );
637  }
638 
639  // Load the functions from the expression context
640  QStringList contextFunctions = mExpressionContext.functionNames();
641  Q_FOREACH ( const QString &functionName, contextFunctions )
642  {
643  QgsExpressionFunction *func = mExpressionContext.function( functionName );
644  QString name = func->name();
645  if ( name.startsWith( '_' ) ) // do not display private functions
646  continue;
647  if ( func->params() != 0 )
648  name += '(';
649  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
650  }
651 }
652 
653 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
654 {
655  Q_FOREACH ( const QString &group, groups )
656  {
657  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
658  }
659 }
660 
661 QString QgsExpressionBuilderWidget::formatRelationHelp( const QgsRelation &relation ) const
662 {
663  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the relation ID for the relation named '%1'." ).arg( relation.name() ) );
664  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( relation.id() ) ) );
665  return text;
666 }
667 
668 QString QgsExpressionBuilderWidget::formatLayerHelp( const QgsMapLayer *layer ) const
669 {
670  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the layer ID for the layer named '%1'." ).arg( layer->name() ) );
671  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( layer->id() ) ) );
672  return text;
673 }
674 
676 {
677  return mModel;
678 }
679 
681 {
682  return mProject;
683 }
684 
686 {
687  mProject = project;
688  updateFunctionTree();
689 }
690 
692 {
693  QWidget::showEvent( e );
694  txtExpressionString->setFocus();
695 }
696 
697 void QgsExpressionBuilderWidget::txtSearchEdit_textChanged()
698 {
699  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
700  if ( txtSearchEdit->text().isEmpty() )
701  {
702  expressionTree->collapseAll();
703  }
704  else
705  {
706  expressionTree->expandAll();
707  QModelIndex index = mProxyModel->index( 0, 0 );
708  if ( mProxyModel->hasChildren( index ) )
709  {
710  QModelIndex child = mProxyModel->index( 0, 0, index );
711  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
712  }
713  }
714 }
715 
716 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
717 {
718  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
719  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
720 }
721 
722 void QgsExpressionBuilderWidget::lblPreview_linkActivated( const QString &link )
723 {
724  Q_UNUSED( link );
725  QgsMessageViewer *mv = new QgsMessageViewer( this );
726  mv->setWindowTitle( tr( "More Info on Expression Error" ) );
727  mv->setMessageAsHtml( txtExpressionString->toolTip() );
728  mv->exec();
729 }
730 
731 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
732 {
733  // Insert the item text or replace selected text
734  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
735  txtExpressionString->setFocus();
736 }
737 
738 void QgsExpressionBuilderWidget::operatorButtonClicked()
739 {
740  QPushButton *button = dynamic_cast<QPushButton *>( sender() );
741 
742  // Insert the button text or replace selected text
743  txtExpressionString->insertText( ' ' + button->text() + ' ' );
744  txtExpressionString->setFocus();
745 }
746 
747 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
748 {
749  QModelIndex idx = expressionTree->indexAt( pt );
750  idx = mProxyModel->mapToSource( idx );
751  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
752  if ( !item )
753  return;
754 
755  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
756  {
757  QMenu *menu = new QMenu( this );
758  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
759  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
760  menu->popup( expressionTree->mapToGlobal( pt ) );
761  }
762 }
763 
765 {
766  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
767  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
768  // TODO We should really return a error the user of the widget that
769  // the there is no layer set.
770  if ( !mLayer || !item )
771  return;
772 
773  mValueGroupBox->show();
774  fillFieldValues( item->text(), 10 );
775 }
776 
778 {
779  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
780  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
781  // TODO We should really return a error the user of the widget that
782  // the there is no layer set.
783  if ( !mLayer || !item )
784  return;
785 
786  mValueGroupBox->show();
787  fillFieldValues( item->text(), -1 );
788 }
789 
790 void QgsExpressionBuilderWidget::txtPython_textChanged()
791 {
792  lblAutoSave->setText( QStringLiteral( "Saving..." ) );
793  if ( mAutoSave )
794  {
795  autosave();
796  }
797 }
798 
800 {
801  // Don't auto save if not on function editor that would be silly.
802  if ( tabWidget->currentIndex() != 1 )
803  return;
804 
805  QListWidgetItem *item = cmbFileNames->currentItem();
806  if ( !item )
807  return;
808 
809  QString file = item->text();
810  saveFunctionFile( file );
811  lblAutoSave->setText( QStringLiteral( "Saved" ) );
812  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
813  lblAutoSave->setGraphicsEffect( effect );
814  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
815  anim->setDuration( 2000 );
816  anim->setStartValue( 1.0 );
817  anim->setEndValue( 0.0 );
818  anim->setEasingCurve( QEasingCurve::OutQuad );
819  anim->start( QAbstractAnimation::DeleteWhenStopped );
820 }
821 
822 void QgsExpressionBuilderWidget::setExpressionState( bool state )
823 {
824  mExpressionValid = state;
825 }
826 
827 QString QgsExpressionBuilderWidget::helpStylesheet() const
828 {
829  //start with default QGIS report style
830  QString style = QgsApplication::reportStyleSheet();
831 
832  //add some tweaks
833  style += " .functionname {color: #0a6099; font-weight: bold;} "
834  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
835  " td.argument { padding-right: 10px; }";
836 
837  return style;
838 }
839 
840 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
841 {
842  if ( !expressionItem )
843  return QLatin1String( "" );
844 
845  QString helpContents = expressionItem->getHelpText();
846 
847  // Return the function help that is set for the function if there is one.
848  if ( helpContents.isEmpty() )
849  {
850  QString name = expressionItem->data( Qt::UserRole ).toString();
851 
852  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
853  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
854  else
855  helpContents = QgsExpression::helpText( name );
856  }
857 
858  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
859 }
860 
861 
862 
863 
864 
866 {
867  setFilterCaseSensitivity( Qt::CaseInsensitive );
868 }
869 
870 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
871 {
872  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
873  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
874 
875  int count = sourceModel()->rowCount( index );
876  bool matchchild = false;
877  for ( int i = 0; i < count; ++i )
878  {
879  if ( filterAcceptsRow( i, index ) )
880  {
881  matchchild = true;
882  break;
883  }
884  }
885 
886  if ( itemType == QgsExpressionItem::Header && matchchild )
887  return true;
888 
889  if ( itemType == QgsExpressionItem::Header )
890  return false;
891 
892  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
893 }
894 
895 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
896 {
897  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
898  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
899  if ( leftSort != rightSort )
900  return leftSort < rightSort;
901 
902  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
903  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
904 
905  //ignore $ prefixes when sorting
906  if ( leftString.startsWith( '$' ) )
907  leftString = leftString.mid( 1 );
908  if ( rightString.startsWith( '$' ) )
909  rightString = rightString.mid( 1 );
910 
911  return QString::localeAwareCompare( leftString, rightString ) < 0;
912 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:176
QgsProject * project()
Returns the project currently associated with the widget.
QString name
Definition: qgsrelation.h:45
void setProject(QgsProject *project)
Sets the project currently associated with the widget.
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
Base class for all map layer types.
Definition: qgsmaplayer.h:56
QgsExpressionBuilderWidget(QWidget *parent=0)
Create a new expression builder widget with an optional parent.
QStandardItemModel * model()
Returns a pointer to the dialog&#39;s function item model.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
QString name
Definition: qgsfield.h:56
This class is a composition of two QSettings instances:
Definition: qgssettings.h:55
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
void loadSampleValues()
Load sample values into the sample value area.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
QString id
Definition: qgsrelation.h:42
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
Container of fields for a vector layer.
Definition: qgsfields.h:42
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const override
Calculates a list of unique values contained within an attribute in the layer.
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
static QString reportStyleSheet()
get a standard css style sheet for reports.
int count() const
Return number of items.
Definition: qgsfields.cpp:115
void loadFunctionCode(const QString &code)
Load code into the function editor.
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
void loadCodeFromFile(QString path)
Load code from the given file into the function editor.
void newFunctionFile(const QString &fileName="scratch")
Create a new file in the function editor.
QString description(const QString &name) const
Returns a translated description string for the variable with specified name.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:145
void updateFunctionFileList(const QString &path)
Update the list of function files found at the given path.
void setMessageAsHtml(const QString &msg)
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Search proxy used to filter the QgsExpressionBuilderWidget tree.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsFields fields() const override
Returns the list of fields of this layer.
bool isHighlightedVariable(const QString &name) const
Returns true if the specified variable name is intended to be highlighted to the user.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
This class wraps a request for features to a vector layer (or directly its vector data provider)...
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Append a field. The field must have unique name, otherwise it is rejected (returns false) ...
Definition: qgsfields.cpp:59
Reads and writes project states.
Definition: qgsproject.h:81
void loadFieldNames()
Loads all the field names from the layer.
static const int ITEM_TYPE_ROLE
Item type role.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
void autosave()
Auto save the current Python function code.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context for the widget.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
QgsExpressionItem::ItemType getItemType() const
Get the type of expression item, e.g., header, field, ExpressionNode.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
static const int CUSTOM_SORT_ROLE
Custom sort order role.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations...
An expression item that can be used in the QgsExpressionBuilderWidget tree.
QStringList functionNames() const
Retrieves a list of function names contained in the context.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
QString getExpressionText() const
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
A generic message view for displaying QGIS messages.
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfields.cpp:110
QString expressionText()
Gets the expression string that has been set in the expression area.
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
QString getHelpText() const
Get the help text that is associated with this expression item.
void registerItem(const QString &group, const QString &label, const QString &expressionText, const QString &helpText=QString(), QgsExpressionItem::ItemType type=QgsExpressionItem::ExpressionNode, bool highlightedItem=false, int sortOrder=1)
Registers a node item for the expression builder.
QString name
Definition: qgsmaplayer.h:60
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
Represents a vector layer which manages a vector based data sets.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
void saveToRecent(const QString &collection="generic")
Adds the current expression to the given collection.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands)
void loadAllValues()
Load all unique values from the set layer into the sample area.
QgsExpressionFunction * function(const QString &name) const
Fetches a matching function from the context.