QGIS API Documentation  2.99.0-Master (23ddace)
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  QStringList strValues;
340  QSet<QVariant> values = mLayer->uniqueValues( fieldIndex, countLimit );
341  Q_FOREACH ( const QVariant &value, values )
342  {
343  QString strValue;
344  if ( value.isNull() )
345  strValue = QStringLiteral( "NULL" );
346  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
347  strValue = value.toString();
348  else
349  strValue = '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'';
350  strValues.append( strValue );
351  }
352  mValuesModel->setStringList( strValues );
353  mFieldValues[fieldName] = strValues;
354 }
355 
356 void QgsExpressionBuilderWidget::registerItem( const QString &group,
357  const QString &label,
358  const QString &expressionText,
359  const QString &helpText,
360  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
361 {
362  QgsExpressionItem *item = new QgsExpressionItem( label, expressionText, helpText, type );
363  item->setData( label, Qt::UserRole );
364  item->setData( sortOrder, QgsExpressionItem::CUSTOM_SORT_ROLE );
365 
366  // Look up the group and insert the new function.
367  if ( mExpressionGroups.contains( group ) )
368  {
369  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
370  groupNode->appendRow( item );
371  }
372  else
373  {
374  // If the group doesn't exist yet we make it first.
375  QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), QLatin1String( "" ), QgsExpressionItem::Header );
376  newgroupNode->setData( group, Qt::UserRole );
377  //Recent group should always be last group
378  newgroupNode->setData( group.startsWith( QLatin1String( "Recent (" ) ) ? 2 : 1, QgsExpressionItem::CUSTOM_SORT_ROLE );
379  newgroupNode->appendRow( item );
380  newgroupNode->setBackground( QBrush( QColor( "#eee" ) ) );
381  mModel->appendRow( newgroupNode );
382  mExpressionGroups.insert( group, newgroupNode );
383  }
384 
385  if ( highlightedItem )
386  {
387  //insert a copy as a top level item
388  QgsExpressionItem *topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
389  topLevelItem->setData( label, Qt::UserRole );
390  item->setData( 0, QgsExpressionItem::CUSTOM_SORT_ROLE );
391  QFont font = topLevelItem->font();
392  font.setBold( true );
393  topLevelItem->setFont( font );
394  mModel->appendRow( topLevelItem );
395  }
396 
397 }
398 
400 {
401  return mExpressionValid;
402 }
403 
404 void QgsExpressionBuilderWidget::saveToRecent( const QString &collection )
405 {
406  QgsSettings settings;
407  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
408  QStringList expressions = settings.value( location ).toStringList();
409  expressions.removeAll( this->expressionText() );
410 
411  expressions.prepend( this->expressionText() );
412 
413  while ( expressions.count() > 20 )
414  {
415  expressions.pop_back();
416  }
417 
418  settings.setValue( location, expressions );
419  this->loadRecent( collection );
420 }
421 
422 void QgsExpressionBuilderWidget::loadRecent( const QString &collection )
423 {
424  mRecentKey = collection;
425  QString name = tr( "Recent (%1)" ).arg( collection );
426  if ( mExpressionGroups.contains( name ) )
427  {
428  QgsExpressionItem *node = mExpressionGroups.value( name );
429  node->removeRows( 0, node->rowCount() );
430  }
431 
432  QgsSettings settings;
433  QString location = QStringLiteral( "/expressions/recent/%1" ).arg( collection );
434  QStringList expressions = settings.value( location ).toStringList();
435  int i = 0;
436  Q_FOREACH ( const QString &expression, expressions )
437  {
438  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
439  i++;
440  }
441 }
442 
443 void QgsExpressionBuilderWidget::updateFunctionTree()
444 {
445  mModel->clear();
446  mExpressionGroups.clear();
447  // TODO Can we move this stuff to QgsExpression, like the functions?
448  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "+" ), QStringLiteral( " + " ) );
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( "IN" ), QStringLiteral( " IN " ) );
463  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
464  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "ILIKE" ), QStringLiteral( " ILIKE " ) );
465  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IS" ), QStringLiteral( " IS " ) );
466  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "OR" ), QStringLiteral( " OR " ) );
467  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "AND" ), QStringLiteral( " AND " ) );
468  registerItem( QStringLiteral( "Operators" ), QStringLiteral( "NOT" ), QStringLiteral( " NOT " ) );
469 
470  QString casestring = QStringLiteral( "CASE WHEN condition THEN result END" );
471  registerItem( QStringLiteral( "Conditionals" ), QStringLiteral( "CASE" ), casestring );
472 
473  registerItem( QStringLiteral( "Fields and Values" ), QStringLiteral( "NULL" ), QStringLiteral( "NULL" ) );
474 
475  // Load the functions from the QgsExpression class
476  int count = QgsExpression::functionCount();
477  for ( int i = 0; i < count; i++ )
478  {
479  QgsExpressionFunction *func = QgsExpression::Functions()[i];
480  QString name = func->name();
481  if ( name.startsWith( '_' ) ) // do not display private functions
482  continue;
483  if ( func->isDeprecated() ) // don't show deprecated functions
484  continue;
485  if ( func->isContextual() )
486  {
487  //don't show contextual functions by default - it's up the the QgsExpressionContext
488  //object to provide them if supported
489  continue;
490  }
491  if ( func->params() != 0 )
492  name += '(';
493  else if ( !name.startsWith( '$' ) )
494  name += QLatin1String( "()" );
495  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
496  }
497 
498  loadExpressionContext();
499 }
500 
502 {
503  mDa = da;
504 }
505 
507 {
508  return txtExpressionString->text();
509 }
510 
511 void QgsExpressionBuilderWidget::setExpressionText( const QString &expression )
512 {
513  txtExpressionString->setText( expression );
514 }
515 
517 {
518  mExpressionContext = context;
519  updateFunctionTree();
520  loadFieldNames();
521  loadRecent( mRecentKey );
522 }
523 
524 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
525 {
526  QString text = expressionText();
527 
528  // If the string is empty the expression will still "fail" although
529  // we don't show the user an error as it will be confusing.
530  if ( text.isEmpty() )
531  {
532  lblPreview->setText( QLatin1String( "" ) );
533  lblPreview->setStyleSheet( QLatin1String( "" ) );
534  txtExpressionString->setToolTip( QLatin1String( "" ) );
535  lblPreview->setToolTip( QLatin1String( "" ) );
536  emit expressionParsed( false );
537  return;
538  }
539 
540  QgsExpression exp( text );
541 
542  if ( mLayer )
543  {
544  // Only set calculator if we have layer, else use default.
545  exp.setGeomCalculator( &mDa );
546 
547  if ( !mExpressionContext.feature().isValid() )
548  {
549  // no feature passed yet, try to get from layer
550  QgsFeature f;
551  mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
552  mExpressionContext.setFeature( f );
553  }
554  }
555 
556  QVariant value = exp.evaluate( &mExpressionContext );
557  if ( !exp.hasEvalError() )
558  {
559  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
560  }
561 
562  if ( exp.hasParserError() || exp.hasEvalError() )
563  {
564  QString tooltip = QStringLiteral( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
565  if ( exp.hasEvalError() )
566  tooltip += QStringLiteral( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
567 
568  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
569  lblPreview->setStyleSheet( QStringLiteral( "color: rgba(255, 6, 10, 255);" ) );
570  txtExpressionString->setToolTip( tooltip );
571  lblPreview->setToolTip( tooltip );
572  emit expressionParsed( false );
573  return;
574  }
575  else
576  {
577  lblPreview->setStyleSheet( QLatin1String( "" ) );
578  txtExpressionString->setToolTip( QLatin1String( "" ) );
579  lblPreview->setToolTip( QLatin1String( "" ) );
580  emit expressionParsed( true );
581  }
582 }
583 
584 void QgsExpressionBuilderWidget::loadExpressionContext()
585 {
586  QStringList variableNames = mExpressionContext.filteredVariableNames();
587  Q_FOREACH ( const QString &variable, variableNames )
588  {
589  registerItem( QStringLiteral( "Variables" ), variable, " @" + variable + ' ',
590  QgsExpression::variableHelpText( variable, true, mExpressionContext.variable( variable ) ),
592  mExpressionContext.isHighlightedVariable( variable ) );
593  }
594 
595  // Load the functions from the expression context
596  QStringList contextFunctions = mExpressionContext.functionNames();
597  Q_FOREACH ( const QString &functionName, contextFunctions )
598  {
599  QgsExpressionFunction *func = mExpressionContext.function( functionName );
600  QString name = func->name();
601  if ( name.startsWith( '_' ) ) // do not display private functions
602  continue;
603  if ( func->params() != 0 )
604  name += '(';
605  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() );
606  }
607 }
608 
609 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList &groups, const QString &label, const QString &expressionText, const QString &helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
610 {
611  Q_FOREACH ( const QString &group, groups )
612  {
613  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
614  }
615 }
616 
618 {
619  QWidget::showEvent( e );
620  txtExpressionString->setFocus();
621 }
622 
623 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
624 {
625  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
626  if ( txtSearchEdit->text().isEmpty() )
627  {
628  expressionTree->collapseAll();
629  }
630  else
631  {
632  expressionTree->expandAll();
633  QModelIndex index = mProxyModel->index( 0, 0 );
634  if ( mProxyModel->hasChildren( index ) )
635  {
636  QModelIndex child = mProxyModel->index( 0, 0, index );
637  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
638  }
639  }
640 }
641 
642 void QgsExpressionBuilderWidget::on_txtSearchEditValues_textChanged()
643 {
644  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
645  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
646 }
647 
648 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString &link )
649 {
650  Q_UNUSED( link );
651  QgsMessageViewer *mv = new QgsMessageViewer( this );
652  mv->setWindowTitle( tr( "More info on expression error" ) );
653  mv->setMessageAsHtml( txtExpressionString->toolTip() );
654  mv->exec();
655 }
656 
657 void QgsExpressionBuilderWidget::on_mValuesListView_doubleClicked( const QModelIndex &index )
658 {
659  // Insert the item text or replace selected text
660  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
661  txtExpressionString->setFocus();
662 }
663 
664 void QgsExpressionBuilderWidget::operatorButtonClicked()
665 {
666  QPushButton *button = dynamic_cast<QPushButton *>( sender() );
667 
668  // Insert the button text or replace selected text
669  txtExpressionString->insertText( ' ' + button->text() + ' ' );
670  txtExpressionString->setFocus();
671 }
672 
673 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
674 {
675  QModelIndex idx = expressionTree->indexAt( pt );
676  idx = mProxyModel->mapToSource( idx );
677  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
678  if ( !item )
679  return;
680 
681  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
682  {
683  QMenu *menu = new QMenu( this );
684  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
685  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
686  menu->popup( expressionTree->mapToGlobal( pt ) );
687  }
688 }
689 
691 {
692  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
693  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
694  // TODO We should really return a error the user of the widget that
695  // the there is no layer set.
696  if ( !mLayer || !item )
697  return;
698 
699  mValueGroupBox->show();
700  fillFieldValues( item->text(), 10 );
701 }
702 
704 {
705  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
706  QgsExpressionItem *item = dynamic_cast<QgsExpressionItem *>( mModel->itemFromIndex( idx ) );
707  // TODO We should really return a error the user of the widget that
708  // the there is no layer set.
709  if ( !mLayer || !item )
710  return;
711 
712  mValueGroupBox->show();
713  fillFieldValues( item->text(), -1 );
714 }
715 
716 void QgsExpressionBuilderWidget::on_txtPython_textChanged()
717 {
718  lblAutoSave->setText( QStringLiteral( "Saving..." ) );
719  if ( mAutoSave )
720  {
721  autosave();
722  }
723 }
724 
726 {
727  // Don't auto save if not on function editor that would be silly.
728  if ( tabWidget->currentIndex() != 1 )
729  return;
730 
731  QListWidgetItem *item = cmbFileNames->currentItem();
732  if ( !item )
733  return;
734 
735  QString file = item->text();
736  saveFunctionFile( file );
737  lblAutoSave->setText( QStringLiteral( "Saved" ) );
738  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect();
739  lblAutoSave->setGraphicsEffect( effect );
740  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
741  anim->setDuration( 2000 );
742  anim->setStartValue( 1.0 );
743  anim->setEndValue( 0.0 );
744  anim->setEasingCurve( QEasingCurve::OutQuad );
745  anim->start( QAbstractAnimation::DeleteWhenStopped );
746 }
747 
748 void QgsExpressionBuilderWidget::setExpressionState( bool state )
749 {
750  mExpressionValid = state;
751 }
752 
753 QString QgsExpressionBuilderWidget::helpStylesheet() const
754 {
755  //start with default QGIS report style
756  QString style = QgsApplication::reportStyleSheet();
757 
758  //add some tweaks
759  style += " .functionname {color: #0a6099; font-weight: bold;} "
760  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
761  " td.argument { padding-right: 10px; }";
762 
763  return style;
764 }
765 
766 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *expressionItem )
767 {
768  if ( !expressionItem )
769  return QLatin1String( "" );
770 
771  QString helpContents = expressionItem->getHelpText();
772 
773  // Return the function help that is set for the function if there is one.
774  if ( helpContents.isEmpty() )
775  {
776  QString name = expressionItem->data( Qt::UserRole ).toString();
777 
778  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
779  helpContents = QgsExpression::helpText( QStringLiteral( "Field" ) );
780  else
781  helpContents = QgsExpression::helpText( name );
782  }
783 
784  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
785 }
786 
787 
788 
789 
790 
792 {
793  setFilterCaseSensitivity( Qt::CaseInsensitive );
794 }
795 
796 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
797 {
798  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
799  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ITEM_TYPE_ROLE ).toInt() );
800 
801  int count = sourceModel()->rowCount( index );
802  bool matchchild = false;
803  for ( int i = 0; i < count; ++i )
804  {
805  if ( filterAcceptsRow( i, index ) )
806  {
807  matchchild = true;
808  break;
809  }
810  }
811 
812  if ( itemType == QgsExpressionItem::Header && matchchild )
813  return true;
814 
815  if ( itemType == QgsExpressionItem::Header )
816  return false;
817 
818  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
819 }
820 
821 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex &left, const QModelIndex &right ) const
822 {
823  int leftSort = sourceModel()->data( left, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
824  int rightSort = sourceModel()->data( right, QgsExpressionItem::CUSTOM_SORT_ROLE ).toInt();
825  if ( leftSort != rightSort )
826  return leftSort < rightSort;
827 
828  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
829  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
830 
831  //ignore $ prefixes when sorting
832  if ( leftString.startsWith( '$' ) )
833  leftString = leftString.mid( 1 );
834  if ( rightString.startsWith( '$' ) )
835  rightString = rightString.mid( 1 );
836 
837  return QString::localeAwareCompare( leftString, rightString ) < 0;
838 }
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:54
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.
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: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.
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.
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
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:46
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.
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.