QGIS API Documentation  2.99.0-Master (ae4d26a)
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  QString templatetxt;
238  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressions.template" ), templatetxt );
239  txtPython->setText( templatetxt );
240  cmbFileNames->insertItem( 0, fileName );
241  cmbFileNames->setCurrentRow( 0 );
242  saveFunctionFile( fileName );
243 }
244 
245 void QgsExpressionBuilderWidget::btnNewFile_pressed()
246 {
247  bool ok;
248  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
249  tr( "File name:" ), QLineEdit::Normal,
250  QLatin1String( "" ), &ok );
251  if ( ok && !text.isEmpty() )
252  {
253  newFunctionFile( text );
254  }
255 }
256 
257 void QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
258 {
259  if ( lastitem )
260  {
261  QString filename = lastitem->text();
262  saveFunctionFile( filename );
263  }
264  QString path = mFunctionsPath + QDir::separator() + item->text();
265  loadCodeFromFile( path );
266 }
267 
269 {
270  if ( !path.endsWith( QLatin1String( ".py" ) ) )
271  path.append( ".py" );
272 
273  txtPython->loadScript( path );
274 }
275 
277 {
278  txtPython->setText( code );
279 }
280 
281 void QgsExpressionBuilderWidget::expressionTree_doubleClicked( const QModelIndex &index )
282 {
283  QModelIndex idx = mProxyModel->mapToSource( index );
284  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
285  if ( !item )
286  return;
287 
288  // Don't handle the double-click if we are on a header node.
289  if ( item->getItemType() == QgsExpressionItem::Header )
290  return;
291 
292  // Insert the expression text or replace selected text
293  txtExpressionString->insertText( item->getExpressionText() );
294  txtExpressionString->setFocus();
295 }
296 
298 {
299  // TODO We should really return a error the user of the widget that
300  // the there is no layer set.
301  if ( !mLayer )
302  return;
303 
304  loadFieldNames( mLayer->fields() );
305 }
306 
308 {
309  if ( fields.isEmpty() )
310  return;
311 
312  QStringList fieldNames;
313  //Q_FOREACH ( const QgsField& field, fields )
314  fieldNames.reserve( fields.count() );
315  for ( int i = 0; i < fields.count(); ++i )
316  {
317  QString fieldName = fields.at( i ).name();
318  fieldNames << fieldName;
319  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QLatin1String( "" ), QgsExpressionItem::Field, false, i );
320  }
321 // highlighter->addFields( fieldNames );
322 }
323 
324 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
325 {
326  QgsFields fields;
327  for ( auto it = fieldValues.constBegin(); it != fieldValues.constEnd(); ++it )
328  {
329  fields.append( QgsField( it.key() ) );
330  }
331  loadFieldNames( fields );
332  mFieldValues = fieldValues;
333 }
334 
335 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit )
336 {
337  // TODO We should really return a error the user of the widget that
338  // the there is no layer set.
339  if ( !mLayer )
340  return;
341 
342  // TODO We should thread this so that we don't hold the user up if the layer is massive.
343 
344  int fieldIndex = mLayer->fields().lookupField( fieldName );
345 
346  if ( fieldIndex < 0 )
347  return;
348 
349  QStringList strValues;
350  QSet<QVariant> values = mLayer->uniqueValues( fieldIndex, countLimit );
351  Q_FOREACH ( const QVariant &value, values )
352  {
353  QString strValue;
354  if ( value.isNull() )
355  strValue = QStringLiteral( "NULL" );
356  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
357  strValue = value.toString();
358  else
359  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
360  strValues.append( strValue );
361  }
362  mValuesModel->setStringList( strValues );
363  mFieldValues[fieldName] = strValues;
364 }
365 
366 void QgsExpressionBuilderWidget::registerItem( const QString &group,
367  const QString &label,
368  const QString &expressionText,
369  const QString &helpText,
370  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
371 {
372  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
373  item->setData( label, Qt::UserRole );
374  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
375 
376  // Look up the group and insert the new function.
377  if ( mExpressionGroups.contains( group ) )
378  {
379  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
380  groupNode->appendRow( item );
381  }
382  else
383  {
384  // If the group doesn't exist yet we make it first.
385  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
386  newgroupNode->setData( group, Qt::UserRole );
387  //Recent group should always be last group
388  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
389  newgroupNode->appendRow( item );
390  newgroupNode->setBackground( QBrush( QColor( 238, 238, 238 ) ) );
391  mModel->appendRow( newgroupNode );
392  mExpressionGroups.insert( group, newgroupNode );
393  }
394 
395  if ( highlightedItem )
396  {
397  //insert a copy as a top level item
398  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
399  topLevelItem->setData( label, Qt::UserRole );
400  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
401  QFont font = topLevelItem->font();
402  font.setBold( true );
403  topLevelItem->setFont( font );
404  mModel->appendRow( topLevelItem );
405  }
406 
407 }
408 
410 {
411  return mExpressionValid;
412 }
413 
414 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
415 {
416  QgsSettings settings;
417  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
418  QStringList expressions = settings.value( location ).toStringList();
419  expressions.removeAll( this->expressionText() );
420 
421  expressions.prepend( this->expressionText() );
422 
423  while ( expressions.count() > 20 )
424  {
425  expressions.pop_back();
426  }
427 
428  settings.setValue( location, expressions );
429  this->loadRecent( collection );
430 }
431 
432 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
433 {
434  mRecentKey = collection;
435  QString name = tr( "Recent (%1)" ).arg( collection );
436  if ( mExpressionGroups.contains( name ) )
437  {
438  QgsExpressionItem *node = mExpressionGroups.value( name );
439  node->removeRows( 0, node->rowCount() );
440  }
441 
442  QgsSettings settings;
443  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
444  QStringList expressions = settings.value( location ).toStringList();
445  int i = 0;
446  Q_FOREACH ( const QString &expression, expressions )
447  {
448  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
449  i++;
450  }
451 }
452 
453 void QgsExpressionBuilderWidget::loadLayers()
454 {
455  if ( !mProject )
456  return;
457 
458  QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
459  QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
460  for ( ; layerIt != layers.constEnd(); ++layerIt )
461  {
462  registerItemForAllGroups( QStringList() << tr( "Map Layers" ), layerIt.value()->name(), QStringLiteral( "'%1'" ).arg( layerIt.key() ), formatLayerHelp( layerIt.value() ) );
463  }
464 }
465 
466 void QgsExpressionBuilderWidget::loadRelations()
467 {
468  if ( !mProject )
469  return;
470 
471  QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
472  QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
473  for ( ; relIt != relations.constEnd(); ++relIt )
474  {
475  registerItemForAllGroups( QStringList() << tr( "Relations" ), relIt->name(), QStringLiteral( "'%1'" ).arg( relIt->id() ), formatRelationHelp( relIt.value() ) );
476  }
477 }
478 
479 void QgsExpressionBuilderWidget::updateFunctionTree()
480 {
481  mModel->clear();
482  mExpressionGroups.clear();
483  // TODO Can we move this stuff to QgsExpression, like the functions?
484  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
485  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
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( "IN" ), QStringLiteral( " IN " ) );
499  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
500  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
501  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
502  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
503  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
504  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
505 
506  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
507  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
508 
509  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
510 
511  // Load the functions from the QgsExpression class
512  int count = QgsExpression::functionCount();
513  for ( int i = 0; i < count; i++ )
514  {
515  QgsExpressionFunction *func = QgsExpression::Functions()[i];
516  QString name = func->name();
517  if ( name.startsWith( '_' ) ) // do not display private functions
518  continue;
519  if ( func->isDeprecated() ) // don't show deprecated functions
520  continue;
521  if ( func->isContextual() )
522  {
523  //don't show contextual functions by default - it's up the the QgsExpressionContext
524  //object to provide them if supported
525  continue;
526  }
527  if ( func->params() != 0 )
528  name += '(';
529  else if ( !name.startsWith( '$' ) )
530  name += QLatin1String( "()" );
531  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
532  }
533 
534  // load relation names
535  loadRelations();
536 
537  // load layer IDs
538  loadLayers();
539 
540  loadExpressionContext();
541 }
542 
544 {
545  mDa = da;
546 }
547 
549 {
550  return txtExpressionString->text();
551 }
552 
553 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
554 {
555  txtExpressionString->setText( expression );
556 }
557 
559 {
560  mExpressionContext = context;
561  updateFunctionTree();
562  loadFieldNames();
563  loadRecent( mRecentKey );
564 }
565 
566 void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
567 {
568  QString text = expressionText();
569 
570  // If the string is empty the expression will still "fail" although
571  // we don't show the user an error as it will be confusing.
572  if ( text.isEmpty() )
573  {
574  lblPreview->clear();
575  lblPreview->setStyleSheet( QLatin1String( "" ) );
576  txtExpressionString->setToolTip( QLatin1String( "" ) );
577  lblPreview->setToolTip( QLatin1String( "" ) );
578  emit expressionParsed( false );
579  return;
580  }
581 
582  QgsExpression exp( text );
583 
584  if ( mLayer )
585  {
586  // Only set calculator if we have layer, else use default.
587  exp.setGeomCalculator( &mDa );
588 
589  if ( !mExpressionContext.feature().isValid() )
590  {
591  // no feature passed yet, try to get from layer
592  QgsFeature f;
593  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
594  mExpressionContext.setFeature( f );
595  }
596  }
597 
598  QVariant value = exp.evaluate( &mExpressionContext );
599  if ( !exp.hasEvalError() )
600  {
601  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
602  }
603 
604  if ( exp.hasParserError() || exp.hasEvalError() )
605  {
606  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
607  if ( exp.hasEvalError() )
608  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
609 
610  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
611  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
612  txtExpressionString->setToolTip( tooltip );
613  lblPreview->setToolTip( tooltip );
614  emit expressionParsed( false );
615  return;
616  }
617  else
618  {
619  lblPreview->setStyleSheet( QLatin1String( "" ) );
620  txtExpressionString->setToolTip( QLatin1String( "" ) );
621  lblPreview->setToolTip( QLatin1String( "" ) );
622  emit expressionParsed( true );
623  }
624 }
625 
626 void QgsExpressionBuilderWidget::loadExpressionContext()
627 {
628  QStringList variableNames = mExpressionContext.filteredVariableNames();
629  Q_FOREACH ( const QString &variable, variableNames )
630  {
631  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
632  QgsExpression::formatVariableHelp( mExpressionContext.description( variable ), true, mExpressionContext.variable( variable ) ),
634  mExpressionContext.isHighlightedVariable( variable ) );
635  }
636 
637  // Load the functions from the expression context
638  QStringList contextFunctions = mExpressionContext.functionNames();
639  Q_FOREACH ( const QString &functionName, contextFunctions )
640  {
641  QgsExpressionFunction *func = mExpressionContext.function( functionName );
642  QString name = func->name();
643  if ( name.startsWith( '_' ) ) // do not display private functions
644  continue;
645  if ( func->params() != 0 )
646  name += '(';
647  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
648  }
649 }
650 
651 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
652 {
653  Q_FOREACH ( const QString &group, groups )
654  {
655  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
656  }
657 }
658 
659 QString QgsExpressionBuilderWidget::formatRelationHelp( const QgsRelation &relation ) const
660 {
661  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the relation ID for the relation named '%1'." ).arg( relation.name() ) );
662  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( relation.id() ) ) );
663  return text;
664 }
665 
666 QString QgsExpressionBuilderWidget::formatLayerHelp( const QgsMapLayer *layer ) const
667 {
668  QString text = QStringLiteral( "<p>%1</p>" ).arg( tr( "Inserts the layer ID for the layer named '%1'." ).arg( layer->name() ) );
669  text.append( QStringLiteral( "<p>%1</p>" ).arg( tr( "Current value: '%1'" ).arg( layer->id() ) ) );
670  return text;
671 }
672 
674 {
675  return mModel;
676 }
677 
679 {
680  return mProject;
681 }
682 
684 {
685  mProject = project;
686  updateFunctionTree();
687 }
688 
690 {
691  QWidget::showEvent( e );
692  txtExpressionString->setFocus();
693 }
694 
695 void QgsExpressionBuilderWidget::txtSearchEdit_textChanged()
696 {
697  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
698  if ( txtSearchEdit->text().isEmpty() )
699  {
700  expressionTree->collapseAll();
701  }
702  else
703  {
704  expressionTree->expandAll();
705  QModelIndex index = mProxyModel->index( 0, 0 );
706  if ( mProxyModel->hasChildren( index ) )
707  {
708  QModelIndex child = mProxyModel->index( 0, 0, index );
709  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
710  }
711  }
712 }
713 
714 void QgsExpressionBuilderWidget::txtSearchEditValues_textChanged()
715 {
716  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
717  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
718 }
719 
720 void QgsExpressionBuilderWidget::lblPreview_linkActivated( const QString &link )
721 {
722  Q_UNUSED( link );
723  QgsMessageViewer *mv = new QgsMessageViewer( this );
724  mv->setWindowTitle( tr( "More Info on Expression Error" ) );
725  mv->setMessageAsHtml( txtExpressionString->toolTip() );
726  mv->exec();
727 }
728 
729 void QgsExpressionBuilderWidget::mValuesListView_doubleClicked( const QModelIndex &index )
730 {
731  // Insert the item text or replace selected text
732  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
733  txtExpressionString->setFocus();
734 }
735 
736 void QgsExpressionBuilderWidget::operatorButtonClicked()
737 {
738  QPushButton *button = dynamic_cast<QPushButton *>( sender() );
739 
740  // Insert the button text or replace selected text
741  txtExpressionString->insertText( ' ' + button->text() + ' ' );
742  txtExpressionString->setFocus();
743 }
744 
745 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
746 {
747  QModelIndex idx = expressionTree->indexAt( pt );
748  idx = mProxyModel->mapToSource( idx );
749  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
750  if ( !item )
751  return;
752 
753  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
754  {
755  QMenu *menu = new QMenu( this );
756  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
757  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
758  menu->popup( expressionTree->mapToGlobal( pt ) );
759  }
760 }
761 
763 {
764  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
765  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
766  // TODO We should really return a error the user of the widget that
767  // the there is no layer set.
768  if ( !mLayer || !item )
769  return;
770 
771  mValueGroupBox->show();
772  fillFieldValues( item->text(), 10 );
773 }
774 
776 {
777  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
778  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
779  // TODO We should really return a error the user of the widget that
780  // the there is no layer set.
781  if ( !mLayer || !item )
782  return;
783 
784  mValueGroupBox->show();
785  fillFieldValues( item->text(), -1 );
786 }
787 
788 void QgsExpressionBuilderWidget::txtPython_textChanged()
789 {
790  lblAutoSave->setText( QStringLiteral( "Saving..." ) );
791  if ( mAutoSave )
792  {
793  autosave();
794  }
795 }
796 
798 {
799  // Don't auto save if not on function editor that would be silly.
800  if ( tabWidget->currentIndex() != 1 )
801  return;
802 
803  QListWidgetItem *item = cmbFileNames->currentItem();
804  if ( !item )
805  return;
806 
807  QString file = item->text();
808  saveFunctionFile( file );
809  lblAutoSave->setText( QStringLiteral( "Saved" ) );
810  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
811  lblAutoSave->setGraphicsEffect( effect );
812  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
813  anim->setDuration( 2000 );
814  anim->setStartValue( 1.0 );
815  anim->setEndValue( 0.0 );
816  anim->setEasingCurve( QEasingCurve::OutQuad );
817  anim->start( QAbstractAnimation::DeleteWhenStopped );
818 }
819 
820 void QgsExpressionBuilderWidget::setExpressionState( bool state )
821 {
822  mExpressionValid = state;
823 }
824 
825 QString QgsExpressionBuilderWidget::helpStylesheet() const
826 {
827  //start with default QGIS report style
828  QString style = QgsApplication::reportStyleSheet();
829 
830  //add some tweaks
831  style += " .functionname {color: #0a6099; font-weight: bold;} "
832  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
833  " td.argument { padding-right: 10px; }";
834 
835  return style;
836 }
837 
838 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
839 {
840  if ( !expressionItem )
841  return QLatin1String( "" );
842 
843  QString helpContents = expressionItem->getHelpText();
844 
845  // Return the function help that is set for the function if there is one.
846  if ( helpContents.isEmpty() )
847  {
848  QString name = expressionItem->data( Qt::UserRole ).toString();
849 
850  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
851  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
852  else
853  helpContents = QgsExpression::helpText( name );
854  }
855 
856  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
857 }
858 
859 
860 
861 
862 
864 {
865  setFilterCaseSensitivity( Qt::CaseInsensitive );
866 }
867 
868 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
869 {
870  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
871  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
872 
873  int count = sourceModel()->rowCount( index );
874  bool matchchild = false;
875  for ( int i = 0; i < count; ++i )
876  {
877  if ( filterAcceptsRow( i, index ) )
878  {
879  matchchild = true;
880  break;
881  }
882  }
883 
884  if ( itemType == QgsExpressionItem::Header && matchchild )
885  return true;
886 
887  if ( itemType == QgsExpressionItem::Header )
888  return false;
889 
890  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
891 }
892 
893 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
894 {
895  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
896  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
897  if ( leftSort != rightSort )
898  return leftSort < rightSort;
899 
900  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
901  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
902 
903  //ignore $ prefixes when sorting
904  if ( leftString.startsWith( '$' ) )
905  leftString = leftString.mid( 1 );
906  if ( rightString.startsWith( '$' ) )
907  rightString = rightString.mid( 1 );
908 
909  return QString::localeAwareCompare( leftString, rightString ) < 0;
910 }
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.