QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgisexpressionbuilderwidget.cpp - A genric 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 "qgsmessageviewer.h"
20 #include "qgsapplication.h"
21 #include "qgspythonrunner.h"
22 #include "qgsgeometry.h"
23 #include "qgsfeature.h"
24 
25 #include <QSettings>
26 #include <QMenu>
27 #include <QFile>
28 #include <QTextStream>
29 #include <QDir>
30 #include <QInputDialog>
31 #include <QComboBox>
32 #include <QGraphicsOpacityEffect>
33 #include <QPropertyAnimation>
34 
35 
36 
38  : QWidget( parent )
39  , mAutoSave( true )
40  , mLayer( nullptr )
41  , highlighter( nullptr )
42  , mExpressionValid( false )
43 {
44  setupUi( this );
45 
46  mValueGroupBox->hide();
47  mLoadGroupBox->hide();
48 // highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
49 
50  mModel = new QStandardItemModel();
51  mProxyModel = new QgsExpressionItemSearchProxy();
52  mProxyModel->setDynamicSortFilter( true );
53  mProxyModel->setSourceModel( mModel );
54  expressionTree->setModel( mProxyModel );
55  expressionTree->setSortingEnabled( true );
56  expressionTree->sortByColumn( 0, Qt::AscendingOrder );
57 
58  expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
59  connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
60  connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
61  connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
62  this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
63 
64  connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
65  connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
66 
67  Q_FOREACH ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
68  {
69  connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
70  }
71 
72  txtSearchEdit->setPlaceholderText( tr( "Search" ) );
73 
74  mValuesModel = new QStringListModel();
75  mProxyValues = new QSortFilterProxyModel();
76  mProxyValues->setSourceModel( mValuesModel );
77  mValuesListView->setModel( mProxyValues );
78  txtSearchEditValues->setPlaceholderText( tr( "Search" ) );
79 
80  QSettings settings;
81  splitter->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter" ).toByteArray() );
82  editorSplit->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/editorsplitter" ).toByteArray() );
83  functionsplit->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/functionsplitter" ).toByteArray() );
84 
85  txtExpressionString->setFoldingVisible( false );
86 
87  updateFunctionTree();
88 
90  {
91  QgsPythonRunner::eval( "qgis.user.expressionspath", mFunctionsPath );
92  updateFunctionFileList( mFunctionsPath );
93  }
94  else
95  {
96  tab_2->hide();
97  }
98 
99  // select the first item in the function list
100  // in order to avoid a blank help widget
101  QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
102  expressionTree->setCurrentIndex( firstItem );
103 
104  lblAutoSave->setText( "" );
105 }
106 
107 
109 {
110  QSettings settings;
111  settings.setValue( "/windows/QgsExpressionBuilderWidget/splitter", splitter->saveState() );
112  settings.setValue( "/windows/QgsExpressionBuilderWidget/editorsplitter", editorSplit->saveState() );
113  settings.setValue( "/windows/QgsExpressionBuilderWidget/functionsplitter", functionsplit->saveState() );
114 
115  delete mModel;
116  delete mProxyModel;
117  delete mValuesModel;
118  delete mProxyValues;
119 }
120 
122 {
123  mLayer = layer;
124 
125  //TODO - remove existing layer scope from context
126 
127  if ( mLayer )
128  mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
129 }
130 
131 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
132 {
133  txtSearchEditValues->setText( QString( "" ) );
134 
135  // Get the item
136  QModelIndex idx = mProxyModel->mapToSource( index );
137  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
138  if ( !item )
139  return;
140 
141  if ( item->getItemType() == QgsExpressionItem::Field && mFieldValues.contains( item->text() ) )
142  {
143  const QStringList& values = mFieldValues[item->text()];
144  mValuesModel->setStringList( values );
145  }
146 
147  mLoadGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field && mLayer );
148  mValueGroupBox->setVisible(( item->getItemType() == QgsExpressionItem::Field && mLayer ) || mValuesListView->model()->rowCount() > 0 );
149 
150  // Show the help for the current item.
151  QString help = loadFunctionHelp( item );
152  txtHelpText->setText( help );
153 }
154 
155 void QgsExpressionBuilderWidget::on_btnRun_pressed()
156 {
157  if ( !cmbFileNames->currentItem() )
158  return;
159 
160  QString file = cmbFileNames->currentItem()->text();
161  saveFunctionFile( file );
162  runPythonCode( txtPython->text() );
163 }
164 
165 void QgsExpressionBuilderWidget::runPythonCode( const QString& code )
166 {
167  if ( QgsPythonRunner::isValid() )
168  {
169  QString pythontext = code;
170  QgsPythonRunner::run( pythontext );
171  }
172  updateFunctionTree();
173  loadFieldNames();
174  loadRecent( mRecentKey );
175 }
176 
178 {
179  QDir myDir( mFunctionsPath );
180  if ( !myDir.exists() )
181  {
182  myDir.mkpath( mFunctionsPath );
183  }
184 
185  if ( !fileName.endsWith( ".py" ) )
186  {
187  fileName.append( ".py" );
188  }
189 
190  fileName = mFunctionsPath + QDir::separator() + fileName;
191  QFile myFile( fileName );
192  if ( myFile.open( QIODevice::WriteOnly | QFile::Truncate ) )
193  {
194  QTextStream myFileStream( &myFile );
195  myFileStream << txtPython->text() << endl;
196  myFile.close();
197  }
198 }
199 
201 {
202  mFunctionsPath = path;
203  QDir dir( path );
204  dir.setNameFilters( QStringList() << "*.py" );
205  QStringList files = dir.entryList( QDir::Files );
206  cmbFileNames->clear();
207  Q_FOREACH ( const QString& name, files )
208  {
209  QFileInfo info( mFunctionsPath + QDir::separator() + name );
210  if ( info.baseName() == "__init__" ) continue;
211  QListWidgetItem* item = new QListWidgetItem( QgsApplication::getThemeIcon( "console/iconTabEditorConsole.png" ), info.baseName() );
212  cmbFileNames->addItem( item );
213  }
214  if ( !cmbFileNames->currentItem() )
215  cmbFileNames->setCurrentRow( 0 );
216 }
217 
219 {
220  QList<QListWidgetItem*> items = cmbFileNames->findItems( fileName, Qt::MatchExactly );
221  if ( !items.isEmpty() )
222  return;
223 
224  QListWidgetItem *item = new QListWidgetItem( QgsApplication::getThemeIcon( "console/iconTabEditorConsole.png" ), fileName );
225  cmbFileNames->insertItem( 0, item );
226  cmbFileNames->setCurrentRow( 0 );
227 
228  QString templatetxt;
229  QgsPythonRunner::eval( "qgis.user.expressions.template", templatetxt );
230  txtPython->setText( templatetxt );
231  saveFunctionFile( fileName );
232 }
233 
234 void QgsExpressionBuilderWidget::on_btnNewFile_pressed()
235 {
236  bool ok;
237  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
238  tr( "File name:" ), QLineEdit::Normal,
239  "", &ok );
240  if ( ok && !text.isEmpty() )
241  {
242  newFunctionFile( text );
243  }
244 }
245 
246 void QgsExpressionBuilderWidget::on_cmbFileNames_currentItemChanged( QListWidgetItem* item, QListWidgetItem* lastitem )
247 {
248  if ( lastitem )
249  {
250  QString filename = lastitem->text();
251  saveFunctionFile( filename );
252  }
253  QString path = mFunctionsPath + QDir::separator() + item->text();
254  loadCodeFromFile( path );
255 }
256 
258 {
259  if ( !path.endsWith( ".py" ) )
260  path.append( ".py" );
261 
262  txtPython->loadScript( path );
263 }
264 
266 {
267  txtPython->setText( code );
268 }
269 
270 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
271 {
272  QModelIndex idx = mProxyModel->mapToSource( index );
273  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
274  if ( !item )
275  return;
276 
277  // Don't handle the double click it we are on a header node.
278  if ( item->getItemType() == QgsExpressionItem::Header )
279  return;
280 
281  // Insert the expression text or replace selected text
282  txtExpressionString->insertText( item->getExpressionText() );
283  txtExpressionString->setFocus();
284 }
285 
287 {
288  // TODO We should really return a error the user of the widget that
289  // the there is no layer set.
290  if ( !mLayer )
291  return;
292 
293  loadFieldNames( mLayer->fields() );
294 }
295 
297 {
298  if ( fields.isEmpty() )
299  return;
300 
301  QStringList fieldNames;
302  //Q_FOREACH ( const QgsField& field, fields )
303  fieldNames.reserve( fields.count() );
304  for ( int i = 0; i < fields.count(); ++i )
305  {
306  QString fieldName = fields.at( i ).name();
307  fieldNames << fieldName;
308  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field, false, i );
309  }
310 // highlighter->addFields( fieldNames );
311 }
312 
314 {
315  QgsFields fields;
316  Q_FOREACH ( const QString& fieldName, fieldValues.keys() )
317  {
318  fields.append( QgsField( fieldName ) );
319  }
320  loadFieldNames( fields );
321  mFieldValues = fieldValues;
322 }
323 
324 void QgsExpressionBuilderWidget::fillFieldValues( const QString& fieldName, int countLimit )
325 {
326  // TODO We should really return a error the user of the widget that
327  // the there is no layer set.
328  if ( !mLayer )
329  return;
330 
331  // TODO We should thread this so that we don't hold the user up if the layer is massive.
332 
333  int fieldIndex = mLayer->fieldNameIndex( fieldName );
334 
335  if ( fieldIndex < 0 )
336  return;
337 
338  QList<QVariant> values;
339  QStringList strValues;
340  mLayer->uniqueValues( fieldIndex, values, countLimit );
341  Q_FOREACH ( const QVariant& value, values )
342  {
343  QString strValue;
344  if ( value.isNull() )
345  strValue = "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( '\'', "''" ) + '\'';
350  strValues.append( strValue );
351  }
352  mValuesModel->setStringList( strValues );
353  mFieldValues[fieldName] = strValues;
354 }
355 
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::CustomSortRole );
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.
376  newgroupNode->setData( group, Qt::UserRole );
377  //Recent group should always be last group
378  newgroupNode->setData( group.startsWith( "Recent (" ) ? 2 : 1, QgsExpressionItem::CustomSortRole );
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 );
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 
405 {
406  QSettings settings;
407  QString location = QString( "/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 
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  QSettings settings;
433  QString location = QString( "/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( "Operators", "+", " + " );
449  registerItem( "Operators", "-", " - " );
450  registerItem( "Operators", "*", " * " );
451  registerItem( "Operators", "/", " / " );
452  registerItem( "Operators", "%", " % " );
453  registerItem( "Operators", "^", " ^ " );
454  registerItem( "Operators", "=", " = " );
455  registerItem( "Operators", ">", " > " );
456  registerItem( "Operators", "<", " < " );
457  registerItem( "Operators", "<>", " <> " );
458  registerItem( "Operators", "<=", " <= " );
459  registerItem( "Operators", ">=", " >= " );
460  registerItem( "Operators", "||", " || " );
461  registerItem( "Operators", "IN", " IN " );
462  registerItem( "Operators", "LIKE", " LIKE " );
463  registerItem( "Operators", "ILIKE", " ILIKE " );
464  registerItem( "Operators", "IS", " IS " );
465  registerItem( "Operators", "OR", " OR " );
466  registerItem( "Operators", "AND", " AND " );
467  registerItem( "Operators", "NOT", " NOT " );
468 
469  QString casestring = "CASE WHEN condition THEN result END";
470  registerItem( "Conditionals", "CASE", casestring );
471 
472  registerItem( "Fields and Values", "NULL", "NULL" );
473 
474  // Load the functions from the QgsExpression class
475  int count = QgsExpression::functionCount();
476  for ( int i = 0; i < count; i++ )
477  {
479  QString name = func->name();
480  if ( name.startsWith( '_' ) ) // do not display private functions
481  continue;
482  if ( func->isDeprecated() ) // don't show deprecated functions
483  continue;
484  if ( func->isContextual() )
485  {
486  //don't show contextual functions by default - it's up the the QgsExpressionContext
487  //object to provide them if supported
488  continue;
489  }
490  if ( func->params() != 0 )
491  name += '(';
492  else if ( !name.startsWith( '$' ) )
493  name += "()";
494  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helptext() );
495  }
496 
497  loadExpressionContext();
498 }
499 
501 {
502  mDa = da;
503 }
504 
506 {
507  return txtExpressionString->text();
508 }
509 
511 {
512  txtExpressionString->setText( expression );
513 }
514 
516 {
517  mExpressionContext = context;
518 
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( "" );
533  lblPreview->setStyleSheet( "" );
534  txtExpressionString->setToolTip( "" );
535  lblPreview->setToolTip( "" );
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 ( !mFeature.isValid() )
548  {
549  mLayer->getFeatures().nextFeature( mFeature );
550  }
551 
552  if ( mFeature.isValid() )
553  {
554  mExpressionContext.setFeature( mFeature );
555  QVariant value = exp.evaluate( &mExpressionContext );
556  if ( !exp.hasEvalError() )
557  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
558  }
559  else
560  {
561  // The feature is invalid because we don't have one but that doesn't mean user can't
562  // build a expression string. They just get no preview.
563  lblPreview->setText( "" );
564  }
565  }
566  else
567  {
568  // No layer defined
569  QVariant value = exp.evaluate( &mExpressionContext );
570  if ( !exp.hasEvalError() )
571  {
572  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
573  }
574  }
575 
576  if ( exp.hasParserError() || exp.hasEvalError() )
577  {
578  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
579  if ( exp.hasEvalError() )
580  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
581 
582  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
583  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
584  txtExpressionString->setToolTip( tooltip );
585  lblPreview->setToolTip( tooltip );
586  emit expressionParsed( false );
587  return;
588  }
589  else
590  {
591  lblPreview->setStyleSheet( "" );
592  txtExpressionString->setToolTip( "" );
593  lblPreview->setToolTip( "" );
594  emit expressionParsed( true );
595  }
596 }
597 
598 void QgsExpressionBuilderWidget::loadExpressionContext()
599 {
600  QStringList variableNames = mExpressionContext.filteredVariableNames();
601  Q_FOREACH ( const QString& variable, variableNames )
602  {
603  registerItem( "Variables", variable, " @" + variable + ' ',
604  QgsExpression::variableHelpText( variable, true, mExpressionContext.variable( variable ) ),
606  mExpressionContext.isHighlightedVariable( variable ) );
607  }
608 
609  // Load the functions from the expression context
610  QStringList contextFunctions = mExpressionContext.functionNames();
611  Q_FOREACH ( const QString& functionName, contextFunctions )
612  {
613  QgsExpression::Function* func = mExpressionContext.function( functionName );
614  QString name = func->name();
615  if ( name.startsWith( '_' ) ) // do not display private functions
616  continue;
617  if ( func->params() != 0 )
618  name += '(';
619  registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helptext() );
620  }
621 }
622 
623 void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList& groups, const QString& label, const QString& expressionText, const QString& helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
624 {
625  Q_FOREACH ( const QString& group, groups )
626  {
627  registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder );
628  }
629 }
630 
632 {
633  QWidget::showEvent( e );
634  txtExpressionString->setFocus();
635 }
636 
637 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
638 {
639  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
640  if ( txtSearchEdit->text().isEmpty() )
641  {
642  expressionTree->collapseAll();
643  }
644  else
645  {
646  expressionTree->expandAll();
647  QModelIndex index = mProxyModel->index( 0, 0 );
648  if ( mProxyModel->hasChildren( index ) )
649  {
650  QModelIndex child = mProxyModel->index( 0, 0, index );
651  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
652  }
653  }
654 }
655 
656 void QgsExpressionBuilderWidget::on_txtSearchEditValues_textChanged()
657 {
658  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
659  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
660 }
661 
662 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString& link )
663 {
664  Q_UNUSED( link );
665  QgsMessageViewer * mv = new QgsMessageViewer( this );
666  mv->setWindowTitle( tr( "More info on expression error" ) );
667  mv->setMessageAsHtml( txtExpressionString->toolTip() );
668  mv->exec();
669 }
670 
671 void QgsExpressionBuilderWidget::on_mValuesListView_doubleClicked( const QModelIndex &index )
672 {
673  // Insert the item text or replace selected text
674  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
675  txtExpressionString->setFocus();
676 }
677 
678 void QgsExpressionBuilderWidget::operatorButtonClicked()
679 {
680  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
681 
682  // Insert the button text or replace selected text
683  txtExpressionString->insertText( ' ' + button->text() + ' ' );
684  txtExpressionString->setFocus();
685 }
686 
687 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
688 {
689  QModelIndex idx = expressionTree->indexAt( pt );
690  idx = mProxyModel->mapToSource( idx );
691  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
692  if ( !item )
693  return;
694 
695  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
696  {
697  QMenu* menu = new QMenu( this );
698  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
699  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
700  menu->popup( expressionTree->mapToGlobal( pt ) );
701  }
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(), 10 );
715 }
716 
718 {
719  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
720  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
721  // TODO We should really return a error the user of the widget that
722  // the there is no layer set.
723  if ( !mLayer || !item )
724  return;
725 
726  mValueGroupBox->show();
727  fillFieldValues( item->text(), -1 );
728 }
729 
730 void QgsExpressionBuilderWidget::on_txtPython_textChanged()
731 {
732  lblAutoSave->setText( "Saving..." );
733  if ( mAutoSave )
734  {
735  autosave();
736  }
737 }
738 
740 {
741  // Don't auto save if not on function editor that would be silly.
742  if ( tabWidget->currentIndex() != 1 )
743  return;
744 
745  QListWidgetItem* item = cmbFileNames->currentItem();
746  if ( !item )
747  return;
748 
749  QString file = item->text();
750  saveFunctionFile( file );
751  lblAutoSave->setText( "Saved" );
753  lblAutoSave->setGraphicsEffect( effect );
754  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
755  anim->setDuration( 2000 );
756  anim->setStartValue( 1.0 );
757  anim->setEndValue( 0.0 );
758  anim->setEasingCurve( QEasingCurve::OutQuad );
759  anim->start( QAbstractAnimation::DeleteWhenStopped );
760 }
761 
762 void QgsExpressionBuilderWidget::setExpressionState( bool state )
763 {
764  mExpressionValid = state;
765 }
766 
767 QString QgsExpressionBuilderWidget::helpStylesheet() const
768 {
769  //start with default QGIS report style
771 
772  //add some tweaks
773  style += " .functionname {color: #0a6099; font-weight: bold;} "
774  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
775  " td.argument { padding-right: 10px; }";
776 
777  return style;
778 }
779 
780 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* expressionItem )
781 {
782  if ( !expressionItem )
783  return "";
784 
785  QString helpContents = expressionItem->getHelpText();
786 
787  // Return the function help that is set for the function if there is one.
788  if ( helpContents.isEmpty() )
789  {
790  QString name = expressionItem->data( Qt::UserRole ).toString();
791 
792  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
793  helpContents = QgsExpression::helptext( "Field" );
794  else
795  helpContents = QgsExpression::helptext( name );
796  }
797 
798  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
799 }
800 
801 
802 
803 
804 
806 {
807  setFilterCaseSensitivity( Qt::CaseInsensitive );
808 }
809 
810 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
811 {
812  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
813  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ItemTypeRole ).toInt() );
814 
815  int count = sourceModel()->rowCount( index );
816  bool matchchild = false;
817  for ( int i = 0; i < count; ++i )
818  {
819  if ( filterAcceptsRow( i, index ) )
820  {
821  matchchild = true;
822  break;
823  }
824  }
825 
826  if ( itemType == QgsExpressionItem::Header && matchchild )
827  return true;
828 
829  if ( itemType == QgsExpressionItem::Header )
830  return false;
831 
832  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
833 }
834 
835 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex& left, const QModelIndex& right ) const
836 {
837  int leftSort = sourceModel()->data( left, QgsExpressionItem::CustomSortRole ).toInt();
838  int rightSort = sourceModel()->data( right, QgsExpressionItem::CustomSortRole ).toInt();
839  if ( leftSort != rightSort )
840  return leftSort < rightSort;
841 
842  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
843  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
844 
845  //ignore $ prefixes when sorting
846  if ( leftString.startsWith( '$' ) )
847  leftString = leftString.mid( 1 );
848  if ( rightString.startsWith( '$' ) )
849  rightString = rightString.mid( 1 );
850 
851  return QString::localeAwareCompare( leftString, rightString ) < 0;
852 }
void customContextMenuRequested(const QPoint &pos)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
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:199
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
void clear()
QByteArray toByteArray() const
static unsigned index
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
QString & append(QChar ch)
void setFilterCaseSensitivity(Qt::CaseSensitivity cs)
void setupUi(QWidget *widget)
const QString helptext() const
The help text for the function.
void setEasingCurve(const QEasingCurve &easing)
bool contains(const Key &key) const
void setNameFilters(const QStringList &nameFilters)
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
QString name
Definition: qgsfield.h:52
int localeAwareCompare(const QString &other) const
static QString group(const QString &group)
Returns the translated name for a function group.
static const int CustomSortRole
Custom sort order role.
A abstract base class for defining QgsExpression functions.
virtual void setSourceModel(QAbstractItemModel *sourceModel)
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setBackground(const QBrush &brush)
void setGeomCalculator(const QgsDistanceArea &da)
Sets geometry calculator used in distance/area calculations.
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1)
Calculates a list of unique values contained within an attribute in the layer.
QObject * sender() const
void reserve(int alloc)
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
QStyle * style() const
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
void loadSampleValues()
Load sample values into the sample value area.
void setLayer(QgsVectorLayer *layer)
Sets layer in order to get the fields and values.
static QString helptext(QString name)
Returns the help text for a specified function.
void loadRecent(const QString &collection="generic")
Loads the recent expressions from the given collection.
void addAction(QAction *action)
QString evalErrorString() const
Returns evaluation error.
int exec()
Container of fields for a vector layer.
Definition: qgsfield.h:252
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
static QString formatPreviewString(const QVariant &value)
Formats an expression result for friendly display to the user.
static QString reportStyleSheet()
get a standard css style sheet for reports.
void clear()
int count() const
Return number of items.
Definition: qgsfield.cpp:402
QString parserErrorString() const
Returns parser error.
QChar separator()
QString tr(const char *sourceText, const char *disambiguation, int n)
QString text() const
void loadFunctionCode(const QString &code)
Load code into the function editor.
void setStartValue(const QVariant &value)
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.
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:422
virtual void setData(const QVariant &value, int role)
QgsFields fields() const
Returns the list of fields of this layer.
void updateFunctionFileList(const QString &path)
Update the list of function files found at the given path.
void setBold(bool enable)
static const QList< Function * > & Functions()
QList< T > findChildren(const QString &name) const
QList< Key > keys() const
void setMessageAsHtml(const QString &msg)
void setValue(const QString &key, const QVariant &value)
static int functionCount()
Returns the number of functions defined in the parser.
const char * name() const
virtual void showEvent(QShowEvent *event)
int count(const T &value) const
bool exists() const
void append(const T &value)
Search proxy used to filter the QgsExpressionBuilderWidget tree.
void popup(const QPoint &p, QAction *atAction)
bool isNull() const
bool isHighlightedVariable(const QString &name) const
Returns true if the specified variable name is intended to be highlighted to the user.
void setFocus()
void appendRow(const QList< QStandardItem * > &items)
bool isEmpty() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
bool isEmpty() const
int removeAll(const T &value)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
void setDynamicSortFilter(bool enable)
QString name() const
The name of the function.
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
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: qgsfield.cpp:346
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object...
void loadFieldNames()
Loads all the field names from the layer.
void removeRows(int row, int count)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
void autosave()
Auto save the current Python function code.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void pop_back()
void setFont(const QFont &font)
virtual bool hasChildren(const QModelIndex &parent) const
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, QFlags< Qt::WindowType > flags, QFlags< Qt::InputMethodHint > inputMethodHints)
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 eg header, field, ExpressionNode.
const QFont & font() const
virtual void close()
void setFilterWildcard(const QString &pattern)
General purpose distance and area calculator.
QgsExpression::Function * function(const QString &name) const
Fetches a matching function from the context.
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
QString & replace(int position, int n, QChar after)
An expression item that can be used in the QgsExpressionBuilderWidget tree.
QFont font() const
QVariant value(const QString &key, const QVariant &defaultValue) const
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const
QString mid(int position, int n) const
QStringList functionNames() const
Retrieves a list of function names contained in the context.
QStringList toStringList() const
QVariant data(int role) const
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
QString getExpressionText() const
QStandardItem * itemFromIndex(const QModelIndex &index) const
static const int ItemTypeRole
Item type role.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QStringList entryList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
void setWindowTitle(const QString &)
A generic message view for displaying QGIS messages.
void setEndValue(const QVariant &value)
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfield.cpp:397
QString expressionText()
Gets the expression string that has been set in the expression area.
void setDuration(int msecs)
void setGeomCalculator(const QgsDistanceArea &calc)
Sets the geometry calculator used for distance and area calculations in expressions.
void setExpressionText(const QString &expression)
Sets the expression string for the widget.
int rowCount() const
QString getHelpText() const
Get the help text that is associated with this expression item.
void prepend(const T &value)
static QString variableHelpText(const QString &variableName, bool showValue=true, const QVariant &value=QVariant())
Returns the help text for a specified variable.
iterator insert(const Key &key, const T &value)
int params() const
The number of parameters this function takes.
QgsExpressionBuilderWidget(QWidget *parent=nullptr)
Create a new expression builder widget with an optional parent.
void start(QAbstractAnimation::DeletionPolicy policy)
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
Type type() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QStringList groups() const
Returns a list of the groups the function belongs to.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
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.
QChar * data()
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
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 appendRow(const QList< QStandardItem * > &items)
QString baseName() const
void loadAllValues()
Load all unique values from the set layer into the sample area.
void setStringList(const QStringList &strings)
virtual QVariant data(int role) const
QString text() const
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
bool mkpath(const QString &dirPath) const
const T value(const Key &key) const
virtual bool isDeprecated() const
Returns true if the function is deprecated and should not be presented as a valid option to users in ...