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