QGIS API Documentation  2.99.0-Master (d55fa22)
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 
29 #include <QMenu>
30 #include <QFile>
31 #include <QTextStream>
32 #include <QDir>
33 #include <QInputDialog>
34 #include <QComboBox>
35 #include <QGraphicsOpacityEffect>
36 #include <QPropertyAnimation>
37 
38 
40  : QWidget( parent )
41  , mAutoSave( true )
42  , mLayer( nullptr )
43  , highlighter( nullptr )
44  , mExpressionValid( false )
45 {
46  setupUi( this );
47 
48  mValueGroupBox->hide();
49  mLoadGroupBox->hide();
50 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
51 
52  mModel = new QStandardItemModel();
53  mProxyModel = new QgsExpressionItemSearchProxy();
54  mProxyModel->setDynamicSortFilter( true );
55  mProxyModel->setSourceModel( mModel );
56  expressionTree->setModel( mProxyModel );
57  expressionTree->setSortingEnabled( true );
58  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
59 
60  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
61  connect( this, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsExpressionBuilderWidget::setExpressionState );
62  connect( expressionTree, &QWidget::customContextMenuRequested, this, &QgsExpressionBuilderWidget::showContextMenu );
63  connect( expressionTree->selectionModel(), &QItemSelectionModel::currentChanged,
64  this, &QgsExpressionBuilderWidget::currentChanged );
65 
66  connect( btnLoadAll, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadAllValues );
67  connect( btnLoadSample, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::loadSampleValues );
68 
69  Q_FOREACH ( QPushButton *button, mOperatorsGroupBox->findChildren<QPushButton *>() )
70  {
71  connect( button, &QAbstractButton::pressed, this, &QgsExpressionBuilderWidget::operatorButtonClicked );
72  }
73 
74  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
75 
76  mValuesModel = new QStringListModel();
77  mProxyValues = new QSortFilterProxyModel();
78  mProxyValues->setSourceModel( mValuesModel );
79  mValuesListView->setModel( mProxyValues );
80  txtSearchEditValues->setPlaceholderText( tr( "Search" ) );
81 
82  QgsSettings settings;
83  splitter->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ) ).toByteArray() );
84  editorSplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ) ).toByteArray() );
85  functionsplit->restoreState( settings.value( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ) ).toByteArray() );
86 
87  txtExpressionString->setFoldingVisible( false );
88 
89  updateFunctionTree();
90 
92  {
93  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressionspath" ), mFunctionsPath );
94  updateFunctionFileList( mFunctionsPath );
95  }
96  else
97  {
98  tab_2->hide();
99  }
100 
101  // select the first item in the function list
102  // in order to avoid a blank help widget
103  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
104  expressionTree->setCurrentIndex( firstItem );
105 
106  lblAutoSave->setText( QLatin1String( "" ) );
107  txtExpressionString->setWrapMode( QsciScintilla::WrapWord );
108 }
109 
110 
112 {
113  QgsSettings settings;
114  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/splitter" ), splitter->saveState() );
115  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/editorsplitter" ), editorSplit->saveState() );
116  settings.setValue( QStringLiteral( "Windows/QgsExpressionBuilderWidget/functionsplitter" ), functionsplit->saveState() );
117 
118  delete mModel;
119  delete mProxyModel;
120  delete mValuesModel;
121  delete mProxyValues;
122 }
123 
125 {
126  mLayer = layer;
127 
128  //TODO - remove existing layer scope from context
129 
130  if ( mLayer )
131  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
132 }
133 
134 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
135 {
136  txtSearchEditValues->setText( QLatin1String( "" ) );
137 
138  // Get the item
139  QModelIndex idx = mProxyModel->mapToSource( index );
140  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
141  if ( !item )
142  return;
143 
144  if ( item->getItemType() == QgsExpressionItem::Field && mFieldValues.contains( item->text() ) )
145  {
146  const QStringList &values = mFieldValues[item->text()];
147  mValuesModel->setStringList( values );
148  }
149 
150  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
151  mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
152 
153  // Show the help for the current item.
154  QString help = loadFunctionHelp( item );
155  txtHelpText->setText( help );
156 }
157 
158 void QgsExpressionBuilderWidget::on_btnRun_pressed()
159 {
160  if ( !cmbFileNames->currentItem() )
161  return;
162 
163  QString file = cmbFileNames->currentItem()->text();
164  saveFunctionFile( file );
165  runPythonCode( txtPython->text() );
166 }
167 
168 void QgsExpressionBuilderWidget::runPythonCode( const QString &code )
169 {
170  if ( QgsPythonRunner::isValid() )
171  {
172  QString pythontext = code;
173  QgsPythonRunner::run( pythontext );
174  }
175  updateFunctionTree();
176  loadFieldNames();
177  loadRecent( mRecentKey );
178 }
179 
181 {
182  QDir myDir( mFunctionsPath );
183  if ( !myDir.exists() )
184  {
185  myDir.mkpath( mFunctionsPath );
186  }
187 
188  if ( !fileName.endsWith( QLatin1String( ".py" ) ) )
189  {
190  fileName.append( ".py" );
191  }
192 
193  fileName = mFunctionsPath + QDir::separator() + fileName;
194  QFile myFile( fileName );
195  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
196  {
197  QTextStream myFileStream( &myFile );
198  myFileStream << txtPython->text() << endl;
199  myFile.close();
200  }
201 }
202 
204 {
205  mFunctionsPath = path;
206  QDir dir( path );
207  dir.setNameFilters( QStringList() << QStringLiteral( "*.py" ) );
208  QStringList files = dir.entryList( QDir::Files );
209  cmbFileNames->clear();
210  Q_FOREACH ( const QString &name, files )
211  {
212  QFileInfo info( mFunctionsPath + QDir::separator() + name );
213  if ( info.baseName() == QLatin1String( "__init__" ) ) continue;
214  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "console/iconTabEditorConsole.png" ) ), info.baseName() );
215  cmbFileNames->addItem( item );
216  }
217  if ( !cmbFileNames->currentItem() )
218  cmbFileNames->setCurrentRow( 0 );
219 }
220 
221 void QgsExpressionBuilderWidget::newFunctionFile( const QString &fileName )
222 {
223  QList<QListWidgetItem *> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
224  if ( !items.isEmpty() )
225  return;
226 
227  QString templatetxt;
228  QgsPythonRunner::eval( QStringLiteral( "qgis.user.expressions.template" ), templatetxt );
229  txtPython->setText( templatetxt );
230  cmbFileNames->insertItem( 0, fileName );
231  cmbFileNames->setCurrentRow( 0 );
232  saveFunctionFile( fileName );
233 }
234 
235 void QgsExpressionBuilderWidget::on_btnNewFile_pressed()
236 {
237  bool ok;
238  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
239  tr( "File name:" ), QLineEdit::Normal,
240  QLatin1String( "" ), &ok );
241  if ( ok && !text.isEmpty() )
242  {
243  newFunctionFile( text );
244  }
245 }
246 
247 void QgsExpressionBuilderWidget::on_cmbFileNames_currentItemChanged( QListWidgetItem *item, QListWidgetItem *lastitem )
248 {
249  if ( lastitem )
250  {
251  QString filename = lastitem->text();
252  saveFunctionFile( filename );
253  }
254  QString path = mFunctionsPath + QDir::separator() + item->text();
255  loadCodeFromFile( path );
256 }
257 
259 {
260  if ( !path.endsWith( QLatin1String( ".py" ) ) )
261  path.append( ".py" );
262 
263  txtPython->loadScript( path );
264 }
265 
267 {
268  txtPython->setText( code );
269 }
270 
271 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
272 {
273  QModelIndex idx = mProxyModel->mapToSource( index );
274  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
275  if ( !item )
276  return;
277 
278  // Don't handle the double click it we are on a header node.
279  if ( item->getItemType() == QgsExpressionItem::Header )
280  return;
281 
282  // Insert the expression text or replace selected text
283  txtExpressionString->insertText( item->getExpressionText() );
284  txtExpressionString->setFocus();
285 }
286 
288 {
289  // TODO We should really return a error the user of the widget that
290  // the there is no layer set.
291  if ( !mLayer )
292  return;
293 
294  loadFieldNames( mLayer->fields() );
295 }
296 
298 {
299  if ( fields.isEmpty() )
300  return;
301 
302  QStringList fieldNames;
303  //Q_FOREACH ( const QgsField& field, fields )
304  fieldNames.reserve( fields.count() );
305  for ( int i = 0; i < fields.count(); ++i )
306  {
307  QString fieldName = fields.at( i ).name();
308  fieldNames << fieldName;
309  registerItem( QStringLiteral( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", QLatin1String( "" ), QgsExpressionItem::Field, false, i );
310  }
311 // highlighter->addFields( fieldNames );
312 }
313 
314 void QgsExpressionBuilderWidget::loadFieldsAndValues( const QMap<QString, QStringList> &fieldValues )
315 {
316  QgsFields fields;
317  Q_FOREACH ( const QString &fieldName, fieldValues.keys() )
318  {
319  fields.append( QgsField( fieldName ) );
320  }
321  loadFieldNames( fields );
322  mFieldValues = fieldValues;
323 }
324 
325 void QgsExpressionBuilderWidget::fillFieldValues( const QString &fieldName, int countLimit )
326 {
327  // TODO We should really return a error the user of the widget that
328  // the there is no layer set.
329  if ( !mLayer )
330  return;
331 
332  // TODO We should thread this so that we don't hold the user up if the layer is massive.
333 
334  int fieldIndex = mLayer->fields().lookupField( fieldName );
335 
336  if ( fieldIndex < 0 )
337  return;
338 
339  QList<QVariant> values;
340  QStringList strValues;
341  mLayer->uniqueValues( fieldIndex, values, countLimit );
342  Q_FOREACH ( const QVariant &value, values )
343  {
344  QString strValue;
345  if ( value.isNull() )
346  strValue = QStringLiteral( "NULL" );
347  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
348  strValue = value.toString();
349  else
350  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
351  strValues.append( strValue );
352  }
353  mValuesModel->setStringList( strValues );
354  mFieldValues[fieldName] = strValues;
355 }
356 
357 void QgsExpressionBuilderWidget::registerItem( const QString &group,
358  const QString &label,
359  const QString &expressionText,
360  const QString &helpText,
361  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
362 {
363  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
364  item->setData( label, Qt::UserRole );
365  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
366 
367  // Look up the group and insert the new function.
368  if ( mExpressionGroups.contains( group ) )
369  {
370  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
371  groupNode->appendRow( item );
372  }
373  else
374  {
375  // If the group doesn't exist yet we make it first.
376  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
377  newgroupNode->setData( group, Qt::UserRole );
378  //Recent group should always be last group
379  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
380  newgroupNode->appendRow( item );
381  newgroupNode->setBackground( QBrush( QColor( "#eee" ) ) );
382  mModel->appendRow( newgroupNode );
383  mExpressionGroups.insert( group, newgroupNode );
384  }
385 
386  if ( highlightedItem )
387  {
388  //insert a copy as a top level item
389  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
390  topLevelItem->setData( label, Qt::UserRole );
391  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
392  QFont font = topLevelItem->font();
393  font.setBold( true );
394  topLevelItem->setFont( font );
395  mModel->appendRow( topLevelItem );
396  }
397 
398 }
399 
401 {
402  return mExpressionValid;
403 }
404 
405 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
406 {
407  QgsSettings settings;
408  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
409  QStringList expressions = settings.value( location ).toStringList();
410  expressions.removeAll( this->expressionText() );
411 
412  expressions.prepend( this->expressionText() );
413 
414  while ( expressions.count() > 20 )
415  {
416  expressions.pop_back();
417  }
418 
419  settings.setValue( location, expressions );
420  this->loadRecent( collection );
421 }
422 
423 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
424 {
425  mRecentKey = collection;
426  QString name = tr( "Recent (%1)" ).arg( collection );
427  if ( mExpressionGroups.contains( name ) )
428  {
429  QgsExpressionItem *node = mExpressionGroups.value( name );
430  node->removeRows( 0, node->rowCount() );
431  }
432 
433  QgsSettings settings;
434  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
435  QStringList expressions = settings.value( location ).toStringList();
436  int i = 0;
437  Q_FOREACH ( const QString &expression, expressions )
438  {
439  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
440  i++;
441  }
442 }
443 
444 void QgsExpressionBuilderWidget::updateFunctionTree()
445 {
446  mModel->clear();
447  mExpressionGroups.clear();
448  // TODO Can we move this stuff to QgsExpression, like the functions?
449  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
450  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "-" ), QStringLiteral( " - " ) );
451  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "*" ), QStringLiteral( " * " ) );
452  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "/" ), QStringLiteral( " / " ) );
453  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "%" ), QStringLiteral( " % " ) );
454  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "^" ), QStringLiteral( " ^ " ) );
455  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "=" ), QStringLiteral( " = " ) );
456  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "~" ), QStringLiteral( " ~ " ) );
457  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">" ), QStringLiteral( " > " ) );
458  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<" ), QStringLiteral( " < " ) );
459  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
460  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
461  registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
462  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
463  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
464  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
465  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
466  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
467  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
468  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
469  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
470 
471  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
472  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
473 
474  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
475 
476  // Load the functions from the QgsExpression class
477  int count = QgsExpression::functionCount();
478  for ( int i = 0; i < count; i++ )
479  {
480  QgsExpressionFunction *func = QgsExpression::Functions()[i];
481  QString name = func->name();
482  if ( name.startsWith( '_' ) ) // do not display private functions
483  continue;
484  if ( func->isDeprecated() ) // don't show deprecated functions
485  continue;
486  if ( func->isContextual() )
487  {
488  //don't show contextual functions by default - it's up the the QgsExpressionContext
489  //object to provide them if supported
490  continue;
491  }
492  if ( func->params() != 0 )
493  name += '(';
494  else if ( !name.startsWith( '$' ) )
495  name += QLatin1String( "()" );
496  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
497  }
498 
499  loadExpressionContext();
500 }
501 
503 {
504  mDa = da;
505 }
506 
508 {
509  return txtExpressionString->text();
510 }
511 
512 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
513 {
514  txtExpressionString->setText( expression );
515 }
516 
518 {
519  mExpressionContext = context;
520  updateFunctionTree();
521  loadFieldNames();
522  loadRecent( mRecentKey );
523 }
524 
525 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
526 {
527  QString text = expressionText();
528 
529  // If the string is empty the expression will still "fail" although
530  // we don't show the user an error as it will be confusing.
531  if ( text.isEmpty() )
532  {
533  lblPreview->setText( QLatin1String( "" ) );
534  lblPreview->setStyleSheet( QLatin1String( "" ) );
535  txtExpressionString->setToolTip( QLatin1String( "" ) );
536  lblPreview->setToolTip( QLatin1String( "" ) );
537  emit expressionParsed( false );
538  return;
539  }
540 
541  QgsExpression exp( text );
542 
543  if ( mLayer )
544  {
545  // Only set calculator if we have layer, else use default.
546  exp.setGeomCalculator( &mDa );
547 
548  if ( !mExpressionContext.feature().isValid() )
549  {
550  // no feature passed yet, try to get from layer
551  QgsFeature f;
552  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
553  mExpressionContext.setFeature( f );
554  }
555  }
556 
557  QVariant value = exp.evaluate( &mExpressionContext );
558  if ( !exp.hasEvalError() )
559  {
560  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
561  }
562 
563  if ( exp.hasParserError() || exp.hasEvalError() )
564  {
565  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
566  if ( exp.hasEvalError() )
567  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
568 
569  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
570  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
571  txtExpressionString->setToolTip( tooltip );
572  lblPreview->setToolTip( tooltip );
573  emit expressionParsed( false );
574  return;
575  }
576  else
577  {
578  lblPreview->setStyleSheet( QLatin1String( "" ) );
579  txtExpressionString->setToolTip( QLatin1String( "" ) );
580  lblPreview->setToolTip( QLatin1String( "" ) );
581  emit expressionParsed( true );
582  }
583 }
584 
585 void QgsExpressionBuilderWidget::loadExpressionContext()
586 {
587  QStringList variableNames = mExpressionContext.filteredVariableNames();
588  Q_FOREACH ( const QString &variable, variableNames )
589  {
590  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
591  QgsExpression::variableHelpText( variable, true, mExpressionContext.variable( variable ) ),
593  mExpressionContext.isHighlightedVariable( variable ) );
594  }
595 
596  // Load the functions from the expression context
597  QStringList contextFunctions = mExpressionContext.functionNames();
598  Q_FOREACH ( const QString &functionName, contextFunctions )
599  {
600  QgsExpressionFunction *func = mExpressionContext.function( functionName );
601  QString name = func->name();
602  if ( name.startsWith( '_' ) ) // do not display private functions
603  continue;
604  if ( func->params() != 0 )
605  name += '(';
606  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
607  }
608 }
609 
610 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
611 {
612  Q_FOREACH ( const QString &group, groups )
613  {
614  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
615  }
616 }
617 
619 {
620  QWidget::showEvent( e );
621  txtExpressionString->setFocus();
622 }
623 
624 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
625 {
626  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
627  if ( txtSearchEdit->text().isEmpty() )
628  {
629  expressionTree->collapseAll();
630  }
631  else
632  {
633  expressionTree->expandAll();
634  QModelIndex index = mProxyModel->index( 0, 0 );
635  if ( mProxyModel->hasChildren( index ) )
636  {
637  QModelIndex child = mProxyModel->index( 0, 0, index );
638  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
639  }
640  }
641 }
642 
643 void QgsExpressionBuilderWidget::on_txtSearchEditValues_textChanged()
644 {
645  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
646  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
647 }
648 
649 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString &link )
650 {
651  Q_UNUSED( link );
652  QgsMessageViewer *mv = new QgsMessageViewer( this );
653  mv->setWindowTitle( tr( "More info on expression error" ) );
654  mv->setMessageAsHtml( txtExpressionString->toolTip() );
655  mv->exec();
656 }
657 
658 void QgsExpressionBuilderWidget::on_mValuesListView_doubleClicked( const QModelIndex &index )
659 {
660  // Insert the item text or replace selected text
661  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
662  txtExpressionString->setFocus();
663 }
664 
665 void QgsExpressionBuilderWidget::operatorButtonClicked()
666 {
667  QPushButton *button = dynamic_cast<QPushButton *>( sender() );
668 
669  // Insert the button text or replace selected text
670  txtExpressionString->insertText( ' ' + button->text() + ' ' );
671  txtExpressionString->setFocus();
672 }
673 
674 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
675 {
676  QModelIndex idx = expressionTree->indexAt( pt );
677  idx = mProxyModel->mapToSource( idx );
678  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
679  if ( !item )
680  return;
681 
682  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
683  {
684  QMenu *menu = new QMenu( this );
685  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
686  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
687  menu->popup( expressionTree->mapToGlobal( pt ) );
688  }
689 }
690 
692 {
693  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
694  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
695  // TODO We should really return a error the user of the widget that
696  // the there is no layer set.
697  if ( !mLayer || !item )
698  return;
699 
700  mValueGroupBox->show();
701  fillFieldValues( item->text(), 10 );
702 }
703 
705 {
706  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
707  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
708  // TODO We should really return a error the user of the widget that
709  // the there is no layer set.
710  if ( !mLayer || !item )
711  return;
712 
713  mValueGroupBox->show();
714  fillFieldValues( item->text(), -1 );
715 }
716 
717 void QgsExpressionBuilderWidget::on_txtPython_textChanged()
718 {
719  lblAutoSave->setText( QStringLiteral( "Saving..." ) );
720  if ( mAutoSave )
721  {
722  autosave();
723  }
724 }
725 
727 {
728  // Don't auto save if not on function editor that would be silly.
729  if ( tabWidget->currentIndex() != 1 )
730  return;
731 
732  QListWidgetItem *item = cmbFileNames->currentItem();
733  if ( !item )
734  return;
735 
736  QString file = item->text();
737  saveFunctionFile( file );
738  lblAutoSave->setText( QStringLiteral( "Saved" ) );
739  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
740  lblAutoSave->setGraphicsEffect( effect );
741  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
742  anim->setDuration( 2000 );
743  anim->setStartValue( 1.0 );
744  anim->setEndValue( 0.0 );
745  anim->setEasingCurve( QEasingCurve::OutQuad );
746  anim->start( QAbstractAnimation::DeleteWhenStopped );
747 }
748 
749 void QgsExpressionBuilderWidget::setExpressionState( bool state )
750 {
751  mExpressionValid = state;
752 }
753 
754 QString QgsExpressionBuilderWidget::helpStylesheet() const
755 {
756  //start with default QGIS report style
757  QString style = QgsApplication::reportStyleSheet();
758 
759  //add some tweaks
760  style += " .functionname {color: #0a6099; font-weight: bold;} "
761  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
762  " td.argument { padding-right: 10px; }";
763 
764  return style;
765 }
766 
767 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
768 {
769  if ( !expressionItem )
770  return QLatin1String( "" );
771 
772  QString helpContents = expressionItem->getHelpText();
773 
774  // Return the function help that is set for the function if there is one.
775  if ( helpContents.isEmpty() )
776  {
777  QString name = expressionItem->data( Qt::UserRole ).toString();
778 
779  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
780  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
781  else
782  helpContents = QgsExpression::helpText( name );
783  }
784 
785  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
786 }
787 
788 
789 
790 
791 
793 {
794  setFilterCaseSensitivity( Qt::CaseInsensitive );
795 }
796 
797 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
798 {
799  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
800  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
801 
802  int count = sourceModel()->rowCount( index );
803  bool matchchild = false;
804  for ( int i = 0; i < count; ++i )
805  {
806  if ( filterAcceptsRow( i, index ) )
807  {
808  matchchild = true;
809  break;
810  }
811  }
812 
813  if ( itemType == QgsExpressionItem::Header && matchchild )
814  return true;
815 
816  if ( itemType == QgsExpressionItem::Header )
817  return false;
818 
819  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
820 }
821 
822 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
823 {
824  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
825  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
826  if ( leftSort != rightSort )
827  return leftSort < rightSort;
828 
829  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
830  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
831 
832  //ignore $ prefixes when sorting
833  if ( leftString.startsWith( '$' ) )
834  leftString = leftString.mid( 1 );
835  if ( rightString.startsWith( '$' ) )
836  rightString = rightString.mid( 1 );
837 
838  return QString::localeAwareCompare( leftString, rightString ) < 0;
839 }
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1) const
Calculates a list of unique values contained within an attribute in the layer.
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:289
void registerItem(const QString &group, const QString &label, const QString &expressionText, const QString &helpText="", QgsExpressionItem::ItemType type=QgsExpressionItem::ExpressionNode, bool highlightedItem=false, int sortOrder=1)
Registers a node item for the expression builder.
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:176
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
QgsExpressionBuilderWidget(QWidget *parent=0)
Create a new expression builder widget with an optional parent.
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:54
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.
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
Container of fields for a vector layer.
Definition: qgsfields.h:41
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
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:61
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.
QgsFields fields() const
Returns the list of fields of this layer.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:135
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.
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...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
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
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.
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.
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.