QGIS API Documentation  2.15.0-Master (af20121)
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  QString templatetxt;
225  QgsPythonRunner::eval( "qgis.user.expressions.template", templatetxt );
226  txtPython->setText( templatetxt );
227  cmbFileNames->insertItem( 0, fileName );
228  cmbFileNames->setCurrentRow( 0 );
229  saveFunctionFile( fileName );
230 }
231 
232 void QgsExpressionBuilderWidget::on_btnNewFile_pressed()
233 {
234  bool ok;
235  QString text = QInputDialog::getText( this, tr( "Enter new file name" ),
236  tr( "File name:" ), QLineEdit::Normal,
237  "", &ok );
238  if ( ok && !text.isEmpty() )
239  {
240  newFunctionFile( text );
241  }
242 }
243 
244 void QgsExpressionBuilderWidget::on_cmbFileNames_currentItemChanged( QListWidgetItem* item, QListWidgetItem* lastitem )
245 {
246  if ( lastitem )
247  {
248  QString filename = lastitem->text();
249  saveFunctionFile( filename );
250  }
251  QString path = mFunctionsPath + QDir::separator() + item->text();
252  loadCodeFromFile( path );
253 }
254 
256 {
257  if ( !path.endsWith( ".py" ) )
258  path.append( ".py" );
259 
260  txtPython->loadScript( path );
261 }
262 
264 {
265  txtPython->setText( code );
266 }
267 
268 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
269 {
270  QModelIndex idx = mProxyModel->mapToSource( index );
271  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
272  if ( !item )
273  return;
274 
275  // Don't handle the double click it we are on a header node.
276  if ( item->getItemType() == QgsExpressionItem::Header )
277  return;
278 
279  // Insert the expression text or replace selected text
280  txtExpressionString->insertText( item->getExpressionText() );
281  txtExpressionString->setFocus();
282 }
283 
285 {
286  // TODO We should really return a error the user of the widget that
287  // the there is no layer set.
288  if ( !mLayer )
289  return;
290 
291  loadFieldNames( mLayer->fields() );
292 }
293 
295 {
296  if ( fields.isEmpty() )
297  return;
298 
299  QStringList fieldNames;
300  //Q_FOREACH ( const QgsField& field, fields )
301  fieldNames.reserve( fields.count() );
302  for ( int i = 0; i < fields.count(); ++i )
303  {
304  QString fieldName = fields.at( i ).name();
305  fieldNames << fieldName;
306  registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field, false, i );
307  }
308 // highlighter->addFields( fieldNames );
309 }
310 
312 {
313  QgsFields fields;
314  Q_FOREACH ( const QString& fieldName, fieldValues.keys() )
315  {
316  fields.append( QgsField( fieldName ) );
317  }
318  loadFieldNames( fields );
319  mFieldValues = fieldValues;
320 }
321 
322 void QgsExpressionBuilderWidget::fillFieldValues( const QString& fieldName, int countLimit )
323 {
324  // TODO We should really return a error the user of the widget that
325  // the there is no layer set.
326  if ( !mLayer )
327  return;
328 
329  // TODO We should thread this so that we don't hold the user up if the layer is massive.
330 
331  int fieldIndex = mLayer->fieldNameIndex( fieldName );
332 
333  if ( fieldIndex < 0 )
334  return;
335 
336  QList<QVariant> values;
337  QStringList strValues;
338  mLayer->uniqueValues( fieldIndex, values, countLimit );
339  Q_FOREACH ( const QVariant& value, values )
340  {
341  QString strValue;
342  if ( value.isNull() )
343  strValue = "NULL";
344  else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
345  strValue = value.toString();
346  else
347  strValue = '\'' + value.toString().replace( '\'', "''" ) + '\'';
348  strValues.append( strValue );
349  }
350  mValuesModel->setStringList( strValues );
351  mFieldValues[fieldName] = strValues;
352 }
353 
355  const QString& label,
356  const QString& expressionText,
357  const QString& helpText,
358  QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder )
359 {
360  QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
361  item->setData( label, Qt::UserRole );
362  item->setData( sortOrder, QgsExpressionItem::CustomSortRole );
363 
364  // Look up the group and insert the new function.
365  if ( mExpressionGroups.contains( group ) )
366  {
367  QgsExpressionItem *groupNode = mExpressionGroups.value( group );
368  groupNode->appendRow( item );
369  }
370  else
371  {
372  // If the group doesn't exist yet we make it first.
374  newgroupNode->setData( group, Qt::UserRole );
375  //Recent group should always be last group
376  newgroupNode->setData( group.startsWith( "Recent (" ) ? 2 : 1, QgsExpressionItem::CustomSortRole );
377  newgroupNode->appendRow( item );
378  newgroupNode->setBackground( QBrush( QColor( "#eee" ) ) );
379  mModel->appendRow( newgroupNode );
380  mExpressionGroups.insert( group, newgroupNode );
381  }
382 
383  if ( highlightedItem )
384  {
385  //insert a copy as a top level item
386  QgsExpressionItem* topLevelItem = new QgsExpressionItem( label, expressionText, helpText, type );
387  topLevelItem->setData( label, Qt::UserRole );
389  QFont font = topLevelItem->font();
390  font.setBold( true );
391  topLevelItem->setFont( font );
392  mModel->appendRow( topLevelItem );
393  }
394 
395 }
396 
398 {
399  return mExpressionValid;
400 }
401 
403 {
404  QSettings settings;
405  QString location = QString( "/expressions/recent/%1" ).arg( key );
406  QStringList expressions = settings.value( location ).toStringList();
407  expressions.removeAll( this->expressionText() );
408 
409  expressions.prepend( this->expressionText() );
410 
411  while ( expressions.count() > 20 )
412  {
413  expressions.pop_back();
414  }
415 
416  settings.setValue( location, expressions );
417  this->loadRecent( key );
418 }
419 
421 {
422  mRecentKey = key;
423  QString name = tr( "Recent (%1)" ).arg( key );
424  if ( mExpressionGroups.contains( name ) )
425  {
426  QgsExpressionItem* node = mExpressionGroups.value( name );
427  node->removeRows( 0, node->rowCount() );
428  }
429 
430  QSettings settings;
431  QString location = QString( "/expressions/recent/%1" ).arg( key );
432  QStringList expressions = settings.value( location ).toStringList();
433  int i = 0;
434  Q_FOREACH ( const QString& expression, expressions )
435  {
436  this->registerItem( name, expression, expression, expression, QgsExpressionItem::ExpressionNode, false, i );
437  i++;
438  }
439 }
440 
441 void QgsExpressionBuilderWidget::updateFunctionTree()
442 {
443  mModel->clear();
444  mExpressionGroups.clear();
445  // TODO Can we move this stuff to QgsExpression, like the functions?
446  registerItem( "Operators", "+", " + " );
447  registerItem( "Operators", "-", " - " );
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", "IN", " IN " );
460  registerItem( "Operators", "LIKE", " LIKE " );
461  registerItem( "Operators", "ILIKE", " ILIKE " );
462  registerItem( "Operators", "IS", " IS " );
463  registerItem( "Operators", "OR", " OR " );
464  registerItem( "Operators", "AND", " AND " );
465  registerItem( "Operators", "NOT", " NOT " );
466 
467  QString casestring = "CASE WHEN condition THEN result END";
468  registerItem( "Conditionals", "CASE", casestring );
469 
470  registerItem( "Fields and Values", "NULL", "NULL" );
471 
472  // Load the functions from the QgsExpression class
473  int count = QgsExpression::functionCount();
474  for ( int i = 0; i < count; i++ )
475  {
477  QString name = func->name();
478  if ( name.startsWith( '_' ) ) // do not display private functions
479  continue;
480  if ( func->group() == "deprecated" ) // don't show deprecated functions
481  continue;
482  if ( func->isContextual() )
483  {
484  //don't show contextual functions by default - it's up the the QgsExpressionContext
485  //object to provide them if supported
486  continue;
487  }
488  if ( func->params() != 0 )
489  name += '(';
490  else if ( !name.startsWith( '$' ) )
491  name += "()";
492  registerItem( func->group(), func->name(), ' ' + name + ' ', func->helptext() );
493  }
494 
496  for ( int i = 0; i < specials.size(); ++i )
497  {
498  QString name = specials[i]->name();
499  registerItem( specials[i]->group(), name, ' ' + name + ' ' );
500  }
501 
502  loadExpressionContext();
503 }
504 
506 {
507  mDa = da;
508 }
509 
511 {
512  return txtExpressionString->text();
513 }
514 
516 {
517  txtExpressionString->setText( expression );
518 }
519 
521 {
522  mExpressionContext = context;
523 
524  loadExpressionContext();
525 }
526 
527 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
528 {
529  QString text = expressionText();
530 
531  // If the string is empty the expression will still "fail" although
532  // we don't show the user an error as it will be confusing.
533  if ( text.isEmpty() )
534  {
535  lblPreview->setText( "" );
536  lblPreview->setStyleSheet( "" );
537  txtExpressionString->setToolTip( "" );
538  lblPreview->setToolTip( "" );
539  emit expressionParsed( false );
540  return;
541  }
542 
543  QgsExpression exp( text );
544 
545  if ( mLayer )
546  {
547  // Only set calculator if we have layer, else use default.
548  exp.setGeomCalculator( mDa );
549 
550  if ( !mFeature.isValid() )
551  {
552  mLayer->getFeatures().nextFeature( mFeature );
553  }
554 
555  if ( mFeature.isValid() )
556  {
557  mExpressionContext.setFeature( mFeature );
558  QVariant value = exp.evaluate( &mExpressionContext );
559  if ( !exp.hasEvalError() )
560  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
561  }
562  else
563  {
564  // The feature is invalid because we don't have one but that doesn't mean user can't
565  // build a expression string. They just get no preview.
566  lblPreview->setText( "" );
567  }
568  }
569  else
570  {
571  // No layer defined
572  QVariant value = exp.evaluate( &mExpressionContext );
573  if ( !exp.hasEvalError() )
574  {
575  lblPreview->setText( QgsExpression::formatPreviewString( value ) );
576  }
577  }
578 
579  if ( exp.hasParserError() || exp.hasEvalError() )
580  {
581  QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ), exp.parserErrorString() );
582  if ( exp.hasEvalError() )
583  tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ), exp.evalErrorString() );
584 
585  lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
586  lblPreview->setStyleSheet( "color: rgba(255, 6, 10, 255);" );
587  txtExpressionString->setToolTip( tooltip );
588  lblPreview->setToolTip( tooltip );
589  emit expressionParsed( false );
590  return;
591  }
592  else
593  {
594  lblPreview->setStyleSheet( "" );
595  txtExpressionString->setToolTip( "" );
596  lblPreview->setToolTip( "" );
597  emit expressionParsed( true );
598  }
599 }
600 
601 void QgsExpressionBuilderWidget::loadExpressionContext()
602 {
603  QStringList variableNames = mExpressionContext.filteredVariableNames();
604  Q_FOREACH ( const QString& variable, variableNames )
605  {
606  registerItem( "Variables", variable, " @" + variable + ' ',
607  QgsExpression::variableHelpText( variable, true, mExpressionContext.variable( variable ) ),
609  mExpressionContext.isHighlightedVariable( variable ) );
610  }
611 
612  // Load the functions from the expression context
613  QStringList contextFunctions = mExpressionContext.functionNames();
614  Q_FOREACH ( const QString& functionName, contextFunctions )
615  {
616  QgsExpression::Function* func = mExpressionContext.function( functionName );
617  QString name = func->name();
618  if ( name.startsWith( '_' ) ) // do not display private functions
619  continue;
620  if ( func->params() != 0 )
621  name += '(';
622  registerItem( func->group(), func->name(), ' ' + name + ' ', func->helptext() );
623  }
624 }
625 
627 {
628  QWidget::showEvent( e );
629  txtExpressionString->setFocus();
630 }
631 
632 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
633 {
634  mProxyModel->setFilterWildcard( txtSearchEdit->text() );
635  if ( txtSearchEdit->text().isEmpty() )
636  {
637  expressionTree->collapseAll();
638  }
639  else
640  {
641  expressionTree->expandAll();
642  QModelIndex index = mProxyModel->index( 0, 0 );
643  if ( mProxyModel->hasChildren( index ) )
644  {
645  QModelIndex child = mProxyModel->index( 0, 0, index );
646  expressionTree->selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
647  }
648  }
649 }
650 
651 void QgsExpressionBuilderWidget::on_txtSearchEditValues_textChanged()
652 {
653  mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
654  mProxyValues->setFilterWildcard( txtSearchEditValues->text() );
655 }
656 
657 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( const QString& link )
658 {
659  Q_UNUSED( link );
660  QgsMessageViewer * mv = new QgsMessageViewer( this );
661  mv->setWindowTitle( tr( "More info on expression error" ) );
662  mv->setMessageAsHtml( txtExpressionString->toolTip() );
663  mv->exec();
664 }
665 
666 void QgsExpressionBuilderWidget::on_mValuesListView_doubleClicked( const QModelIndex &index )
667 {
668  // Insert the item text or replace selected text
669  txtExpressionString->insertText( ' ' + index.data( Qt::DisplayRole ).toString() + ' ' );
670  txtExpressionString->setFocus();
671 }
672 
673 void QgsExpressionBuilderWidget::operatorButtonClicked()
674 {
675  QPushButton* button = dynamic_cast<QPushButton*>( sender() );
676 
677  // Insert the button text or replace selected text
678  txtExpressionString->insertText( ' ' + button->text() + ' ' );
679  txtExpressionString->setFocus();
680 }
681 
682 void QgsExpressionBuilderWidget::showContextMenu( QPoint pt )
683 {
684  QModelIndex idx = expressionTree->indexAt( pt );
685  idx = mProxyModel->mapToSource( idx );
686  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
687  if ( !item )
688  return;
689 
690  if ( item->getItemType() == QgsExpressionItem::Field && mLayer )
691  {
692  QMenu* menu = new QMenu( this );
693  menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
694  menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
695  menu->popup( expressionTree->mapToGlobal( pt ) );
696  }
697 }
698 
700 {
701  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
702  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
703  // TODO We should really return a error the user of the widget that
704  // the there is no layer set.
705  if ( !mLayer || !item )
706  return;
707 
708  mValueGroupBox->show();
709  fillFieldValues( item->text(), 10 );
710 }
711 
713 {
714  QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
715  QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
716  // TODO We should really return a error the user of the widget that
717  // the there is no layer set.
718  if ( !mLayer || !item )
719  return;
720 
721  mValueGroupBox->show();
722  fillFieldValues( item->text(), -1 );
723 }
724 
725 void QgsExpressionBuilderWidget::on_txtPython_textChanged()
726 {
727  lblAutoSave->setText( "Saving..." );
728  if ( mAutoSave )
729  {
730  autosave();
731  }
732 }
733 
735 {
736  // Don't auto save if not on function editor that would be silly.
737  if ( tabWidget->currentIndex() != 1 )
738  return;
739 
740  QListWidgetItem* item = cmbFileNames->currentItem();
741  if ( !item )
742  return;
743 
744  QString file = item->text();
745  saveFunctionFile( file );
746  lblAutoSave->setText( "Saved" );
748  lblAutoSave->setGraphicsEffect( effect );
749  QPropertyAnimation *anim = new QPropertyAnimation( effect, "opacity" );
750  anim->setDuration( 2000 );
751  anim->setStartValue( 1.0 );
752  anim->setEndValue( 0.0 );
753  anim->setEasingCurve( QEasingCurve::OutQuad );
754  anim->start( QAbstractAnimation::DeleteWhenStopped );
755 }
756 
757 void QgsExpressionBuilderWidget::setExpressionState( bool state )
758 {
759  mExpressionValid = state;
760 }
761 
762 QString QgsExpressionBuilderWidget::helpStylesheet() const
763 {
764  //start with default QGIS report style
766 
767  //add some tweaks
768  style += " .functionname {color: #0a6099; font-weight: bold;} "
769  " .argument {font-family: monospace; color: #bf0c0c; font-style: italic; } "
770  " td.argument { padding-right: 10px; }";
771 
772  return style;
773 }
774 
775 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* expressionItem )
776 {
777  if ( !expressionItem )
778  return "";
779 
780  QString helpContents = expressionItem->getHelpText();
781 
782  // Return the function help that is set for the function if there is one.
783  if ( helpContents.isEmpty() )
784  {
785  QString name = expressionItem->data( Qt::UserRole ).toString();
786 
787  if ( expressionItem->getItemType() == QgsExpressionItem::Field )
788  helpContents = QgsExpression::helptext( "Field" );
789  else
790  helpContents = QgsExpression::helptext( name );
791  }
792 
793  return "<head><style>" + helpStylesheet() + "</style></head><body>" + helpContents + "</body>";
794 }
795 
796 
797 
798 
799 
801 {
802  setFilterCaseSensitivity( Qt::CaseInsensitive );
803 }
804 
805 bool QgsExpressionItemSearchProxy::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
806 {
807  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
808  QgsExpressionItem::ItemType itemType = QgsExpressionItem::ItemType( sourceModel()->data( index, QgsExpressionItem::ItemTypeRole ).toInt() );
809 
810  int count = sourceModel()->rowCount( index );
811  bool matchchild = false;
812  for ( int i = 0; i < count; ++i )
813  {
814  if ( filterAcceptsRow( i, index ) )
815  {
816  matchchild = true;
817  break;
818  }
819  }
820 
821  if ( itemType == QgsExpressionItem::Header && matchchild )
822  return true;
823 
824  if ( itemType == QgsExpressionItem::Header )
825  return false;
826 
827  return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
828 }
829 
830 bool QgsExpressionItemSearchProxy::lessThan( const QModelIndex& left, const QModelIndex& right ) const
831 {
832  int leftSort = sourceModel()->data( left, QgsExpressionItem::CustomSortRole ).toInt();
833  int rightSort = sourceModel()->data( right, QgsExpressionItem::CustomSortRole ).toInt();
834  if ( leftSort != rightSort )
835  return leftSort < rightSort;
836 
837  QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
838  QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
839 
840  //ignore $ prefixes when sorting
841  if ( leftString.startsWith( '$' ) )
842  leftString = leftString.mid( 1 );
843  if ( rightString.startsWith( '$' ) )
844  rightString = rightString.mid( 1 );
845 
846  return QString::localeAwareCompare( leftString, rightString ) < 0;
847 }
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.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
void clear()
bool isHighlightedVariable(const QString &name) const
Returns true if the specified variable name is intended to be highlighted to the user.
QByteArray toByteArray() const
static unsigned index
void saveFunctionFile(QString fileName)
Save the current function editor text to the given file.
QString getExpressionText() const
QString & append(QChar ch)
void setFilterCaseSensitivity(Qt::CaseSensitivity cs)
void setupUi(QWidget *widget)
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
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.
int localeAwareCompare(const QString &other) const
int params() const
The number of parameters this function takes.
QString getHelpText() const
Get the help text that is associated with this expression item.
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:199
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.
QgsFields fields() const
Returns the list of fields of this layer.
QString name() const
The name of the function.
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)
Returns unique values for column.
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 addAction(QAction *action)
int exec()
Container of fields for a vector layer.
Definition: qgsfield.h:193
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()
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)
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.
QVariant variable(const QString &name) const
Fetches a matching variable from the context.
virtual void setData(const QVariant &value, int role)
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
void setFocus()
void appendRow(const QList< QStandardItem * > &items)
bool isContextual() const
Returns whether the function is only available if provided by a QgsExpressionContext object...
QString name() const
Gets the name of the field.
Definition: qgsfield.cpp:84
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)
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:309
int count() const
Return number of items.
Definition: qgsfield.cpp:365
void loadFieldNames()
Loads all the field names from the layer.
QStringList functionNames() const
Retrieves a list of function names contained in the context.
const QString helptext() const
The help text for the function.
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)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:385
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.
const QFont & font() const
virtual void close()
void setFilterWildcard(const QString &pattern)
General purpose distance and area calculator.
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 toStringList() const
QVariant data(int role) const
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
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
static QList< Function * > specialColumns()
Returns a list of special Column definitions.
void loadFieldsAndValues(const QMap< QString, QStringList > &fieldValues)
Loads field names and values from the specified map.
QStringList filteredVariableNames() const
Returns a filtered list of variables names set by all scopes in the context.
void setWindowTitle(const QString &)
A generic message view for displaying QGIS messages.
QString group() const
The group the function belongs to.
void setEndValue(const QVariant &value)
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
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)
QgsExpressionBuilderWidget(QWidget *parent=nullptr)
Create a new expression builder widget with an optional parent.
void start(QAbstractAnimation::DeletionPolicy policy)
QgsExpression::Function * function(const QString &name) const
Fetches a matching function from the context.
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)
Represents a vector layer which manages a vector based data sets.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
bool isEmpty() const
Check whether the container is empty.
Definition: qgsfield.cpp:360
QString parserErrorString() const
Returns parser error.
QChar * data()
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
QString evalErrorString() const
Returns evaluation error.
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
bool mkpath(const QString &dirPath) const
QgsExpressionItem::ItemType getItemType() const
Get the type of expression item eg header, field, ExpressionNode.
const T value(const Key &key) const