QGIS API Documentation  master-59fd5e0
src/gui/qgsexpressionbuilderwidget.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002     qgisexpressionbuilderwidget.cpp - A genric expression string builder widget.
00003      --------------------------------------
00004     Date                 :  29-May-2011
00005     Copyright            : (C) 2011 by Nathan Woodrow
00006     Email                : woodrow.nathan at gmail dot com
00007  ***************************************************************************
00008  *                                                                         *
00009  *   This program is free software; you can redistribute it and/or modify  *
00010  *   it under the terms of the GNU General Public License as published by  *
00011  *   the Free Software Foundation; either version 2 of the License, or     *
00012  *   (at your option) any later version.                                   *
00013  *                                                                         *
00014  ***************************************************************************/
00015 
00016 #include "qgsexpressionbuilderwidget.h"
00017 #include "qgslogger.h"
00018 #include "qgsexpression.h"
00019 #include "qgsmessageviewer.h"
00020 #include "qgsapplication.h"
00021 
00022 #include <QSettings>
00023 #include <QMenu>
00024 #include <QFile>
00025 #include <QTextStream>
00026 
00027 QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
00028     : QWidget( parent )
00029 {
00030   setupUi( this );
00031 
00032   mValueGroupBox->hide();
00033   btnLoadAll->hide();
00034   btnLoadSample->hide();
00035   highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
00036 
00037   mModel = new QStandardItemModel( );
00038   mProxyModel = new QgsExpressionItemSearchProxy();
00039   mProxyModel->setSourceModel( mModel );
00040   expressionTree->setModel( mProxyModel );
00041 
00042   expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
00043   connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
00044   connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
00045   connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
00046            this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
00047 
00048   connect( btnLoadAll, SIGNAL( pressed() ), this, SLOT( loadAllValues() ) );
00049   connect( btnLoadSample, SIGNAL( pressed() ), this, SLOT( loadSampleValues() ) );
00050 
00051   foreach ( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
00052   {
00053     connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
00054   }
00055 
00056   // TODO Can we move this stuff to QgsExpression, like the functions?
00057   registerItem( "Operators", "+", " + ", tr( "Addition operator" ) );
00058   registerItem( "Operators", "-", " -" , tr( "Subtraction operator" ) );
00059   registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) );
00060   registerItem( "Operators", "/", " / ", tr( "Division operator" ) );
00061   registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) );
00062   registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) );
00063   registerItem( "Operators", "=", " = ", tr( "Equal operator" ) );
00064   registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) );
00065   registerItem( "Operators", "<", " < ", tr( "Less than operator" ) );
00066   registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) );
00067   registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) );
00068   registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) );
00069   registerItem( "Operators", "||", " || ",
00070                 QString( "<b>|| %1</b><br><i>%2</i><br><i>%3:</i>%4" )
00071                 .arg( tr( "(String Concatenation)" ) )
00072                 .arg( tr( "Joins two values together into a string" ) )
00073                 .arg( tr( "Usage" ) )
00074                 .arg( tr( "'Dia' || Diameter" ) ) );
00075   registerItem( "Operators", "LIKE", " LIKE " );
00076   registerItem( "Operators", "ILIKE", " ILIKE " );
00077   registerItem( "Operators", "IS", " IS " );
00078   registerItem( "Operators", "OR", " OR " );
00079   registerItem( "Operators", "AND", " AND " );
00080   registerItem( "Operators", "NOT", " NOT " );
00081 
00082   QString casestring = "CASE WHEN condition THEN result END";
00083   QString caseelsestring = "CASE WHEN condition THEN result ELSE result END";
00084   registerItem( "Conditionals", "CASE", casestring );
00085   registerItem( "Conditionals", "CASE ELSE", caseelsestring );
00086 
00087   // Load the functions from the QgsExpression class
00088   int count = QgsExpression::functionCount();
00089   for ( int i = 0; i < count; i++ )
00090   {
00091     QgsExpression::Function* func = QgsExpression::Functions()[i];
00092     QString name = func->name();
00093     if ( name.startsWith( "_" ) ) // do not display private functions
00094       continue;
00095     if ( func->params() >= 1 )
00096       name += "(";
00097     registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
00098   }
00099 
00100   QList<QgsExpression::Function*> specials = QgsExpression::specialColumns();
00101   for ( int i = 0; i < specials.size(); ++i )
00102   {
00103     QString name = specials[i]->name();
00104     registerItem( specials[i]->group(), name, " " + name + " " );
00105   }
00106 
00107 #if QT_VERSION >= 0x040700
00108   txtSearchEdit->setPlaceholderText( tr( "Search" ) );
00109 #endif
00110 }
00111 
00112 
00113 QgsExpressionBuilderWidget::~QgsExpressionBuilderWidget()
00114 {
00115 
00116 }
00117 
00118 void QgsExpressionBuilderWidget::setLayer( QgsVectorLayer *layer )
00119 {
00120   mLayer = layer;
00121 }
00122 
00123 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
00124 {
00125   // Get the item
00126   QModelIndex idx = mProxyModel->mapToSource( index );
00127   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00128   if ( item == 0 )
00129     return;
00130 
00131   if ( item->getItemType() != QgsExpressionItem::Field )
00132   {
00133     mValueListWidget->clear();
00134   }
00135 
00136   btnLoadAll->setVisible( item->getItemType() == QgsExpressionItem::Field );
00137   btnLoadSample->setVisible( item->getItemType() == QgsExpressionItem::Field );
00138   mValueGroupBox->setVisible( item->getItemType() == QgsExpressionItem::Field );
00139 
00140   // Show the help for the current item.
00141   QString help = loadFunctionHelp( item );
00142   txtHelpText->setText( help );
00143   txtHelpText->setToolTip( txtHelpText->toPlainText() );
00144 }
00145 
00146 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
00147 {
00148   QModelIndex idx = mProxyModel->mapToSource( index );
00149   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00150   if ( item == 0 )
00151     return;
00152 
00153   // Don't handle the double click it we are on a header node.
00154   if ( item->getItemType() == QgsExpressionItem::Header )
00155     return;
00156 
00157   // Insert the expression text.
00158   txtExpressionString->insertPlainText( item->getExpressionText() );
00159 }
00160 
00161 void QgsExpressionBuilderWidget::loadFieldNames()
00162 {
00163   // TODO We should really return a error the user of the widget that
00164   // the there is no layer set.
00165   if ( !mLayer )
00166     return;
00167 
00168   loadFieldNames( mLayer->pendingFields() );
00169 }
00170 
00171 void QgsExpressionBuilderWidget::loadFieldNames( const QgsFields& fields )
00172 {
00173   if ( fields.isEmpty() )
00174     return;
00175 
00176   QStringList fieldNames;
00177   //foreach ( const QgsField& field, fields )
00178   for ( int i = 0; i < fields.count(); ++i )
00179   {
00180     QString fieldName = fields[i].name();
00181     fieldNames << fieldName;
00182     registerItem( "Fields and Values", fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
00183   }
00184   highlighter->addFields( fieldNames );
00185 }
00186 
00187 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
00188 {
00189   // TODO We should really return a error the user of the widget that
00190   // the there is no layer set.
00191   if ( !mLayer )
00192     return;
00193 
00194   // TODO We should thread this so that we don't hold the user up if the layer is massive.
00195   mValueListWidget->clear();
00196   mValueListWidget->setUpdatesEnabled( false );
00197   mValueListWidget->blockSignals( true );
00198 
00199   QList<QVariant> values;
00200   mLayer->uniqueValues( fieldIndex, values, countLimit );
00201   foreach ( QVariant value, values )
00202   {
00203     if ( value.isNull() )
00204       mValueListWidget->addItem( "NULL" );
00205     else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
00206       mValueListWidget->addItem( value.toString() );
00207     else
00208       mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
00209   }
00210 
00211   mValueListWidget->setUpdatesEnabled( true );
00212   mValueListWidget->blockSignals( false );
00213 }
00214 
00215 void QgsExpressionBuilderWidget::registerItem( QString group,
00216     QString label,
00217     QString expressionText,
00218     QString helpText,
00219     QgsExpressionItem::ItemType type )
00220 {
00221   QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
00222   item->setData( label, Qt::UserRole );
00223   // Look up the group and insert the new function.
00224   if ( mExpressionGroups.contains( group ) )
00225   {
00226     QgsExpressionItem *groupNode = mExpressionGroups.value( group );
00227     groupNode->appendRow( item );
00228   }
00229   else
00230   {
00231     // If the group doesn't exist yet we make it first.
00232     QgsExpressionItem *newgroupNode = new QgsExpressionItem( QgsExpression::group( group ), "", QgsExpressionItem::Header );
00233     newgroupNode->setData( group, Qt::UserRole );
00234     newgroupNode->appendRow( item );
00235     mModel->appendRow( newgroupNode );
00236     mExpressionGroups.insert( group, newgroupNode );
00237   }
00238 }
00239 
00240 bool QgsExpressionBuilderWidget::isExpressionValid()
00241 {
00242   return mExpressionValid;
00243 }
00244 
00245 void QgsExpressionBuilderWidget::setGeomCalculator( const QgsDistanceArea & da )
00246 {
00247   mDa = da;
00248 }
00249 
00250 QString QgsExpressionBuilderWidget::expressionText()
00251 {
00252   return txtExpressionString->toPlainText();
00253 }
00254 
00255 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
00256 {
00257   txtExpressionString->setPlainText( expression );
00258 }
00259 
00260 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
00261 {
00262   QString text = txtExpressionString->toPlainText();
00263 
00264   // If the string is empty the expression will still "fail" although
00265   // we don't show the user an error as it will be confusing.
00266   if ( text.isEmpty() )
00267   {
00268     lblPreview->setText( "" );
00269     lblPreview->setStyleSheet( "" );
00270     txtExpressionString->setToolTip( "" );
00271     lblPreview->setToolTip( "" );
00272     emit expressionParsed( false );
00273     return;
00274   }
00275 
00276 
00277 
00278   QgsExpression exp( text );
00279 
00280   if ( mLayer )
00281   {
00282     // Only set calculator if we have layer, else use default.
00283     exp.setGeomCalculator( mDa );
00284 
00285     if ( !mFeature.isValid() )
00286     {
00287       mLayer->getFeatures( QgsFeatureRequest().setFlags(( mLayer->geometryType() != QGis::NoGeometry && exp.needsGeometry() ) ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ).nextFeature( mFeature );
00288     }
00289 
00290     if ( mFeature.isValid() )
00291     {
00292       QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
00293       if ( !exp.hasEvalError() )
00294         lblPreview->setText( value.toString() );
00295     }
00296     else
00297     {
00298       // The feature is invalid because we don't have one but that doesn't mean user can't
00299       // build a expression string.  They just get no preview.
00300       lblPreview->setText( "" );
00301     }
00302   }
00303   else
00304   {
00305     // No layer defined
00306     QVariant value = exp.evaluate();
00307     if ( !exp.hasEvalError() )
00308     {
00309       lblPreview->setText( value.toString() );
00310     }
00311   }
00312 
00313   if ( exp.hasParserError() || exp.hasEvalError() )
00314   {
00315     QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
00316     if ( exp.hasEvalError() )
00317       tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
00318 
00319     lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
00320     lblPreview->setStyleSheet( "color: rgba(255, 6, 10,  255);" );
00321     txtExpressionString->setToolTip( tooltip );
00322     lblPreview->setToolTip( tooltip );
00323     emit expressionParsed( false );
00324     return;
00325   }
00326   else
00327   {
00328     lblPreview->setStyleSheet( "" );
00329     txtExpressionString->setToolTip( "" );
00330     lblPreview->setToolTip( "" );
00331     emit expressionParsed( true );
00332   }
00333 }
00334 
00335 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
00336 {
00337   mProxyModel->setFilterWildcard( txtSearchEdit->text() );
00338   if ( txtSearchEdit->text().isEmpty() )
00339     expressionTree->collapseAll();
00340   else
00341     expressionTree->expandAll();
00342 }
00343 
00344 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( QString link )
00345 {
00346   Q_UNUSED( link );
00347   QgsMessageViewer * mv = new QgsMessageViewer( this );
00348   mv->setWindowTitle( tr( "More info on expression error" ) );
00349   mv->setMessageAsHtml( txtExpressionString->toolTip() );
00350   mv->exec();
00351 }
00352 
00353 void QgsExpressionBuilderWidget::on_mValueListWidget_itemDoubleClicked( QListWidgetItem *item )
00354 {
00355   txtExpressionString->insertPlainText( " " + item->text() + " " );
00356 }
00357 
00358 void QgsExpressionBuilderWidget::operatorButtonClicked()
00359 {
00360   QPushButton* button = dynamic_cast<QPushButton*>( sender() );
00361   txtExpressionString->insertPlainText( " " + button->text() + " " );
00362 }
00363 
00364 void QgsExpressionBuilderWidget::showContextMenu( const QPoint & pt )
00365 {
00366   QModelIndex idx = expressionTree->indexAt( pt );
00367   idx = mProxyModel->mapToSource( idx );
00368   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00369   if ( !item )
00370     return;
00371 
00372   if ( item->getItemType() == QgsExpressionItem::Field )
00373   {
00374     QMenu* menu = new QMenu( this );
00375     menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
00376     menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
00377     menu->popup( expressionTree->mapToGlobal( pt ) );
00378   }
00379 }
00380 
00381 void QgsExpressionBuilderWidget::loadSampleValues()
00382 {
00383   QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
00384   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00385   // TODO We should really return a error the user of the widget that
00386   // the there is no layer set.
00387   if ( !mLayer )
00388     return;
00389 
00390   mValueGroupBox->show();
00391   int fieldIndex = mLayer->fieldNameIndex( item->text() );
00392   fillFieldValues( fieldIndex, 10 );
00393 }
00394 
00395 void QgsExpressionBuilderWidget::loadAllValues()
00396 {
00397   QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
00398   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00399   // TODO We should really return a error the user of the widget that
00400   // the there is no layer set.
00401   if ( !mLayer )
00402     return;
00403 
00404   mValueGroupBox->show();
00405   int fieldIndex = mLayer->fieldNameIndex( item->text() );
00406   fillFieldValues( fieldIndex, -1 );
00407 }
00408 
00409 void QgsExpressionBuilderWidget::setExpressionState( bool state )
00410 {
00411   mExpressionValid = state;
00412 }
00413 
00414 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* expressionItem )
00415 {
00416   if ( !expressionItem )
00417     return "";
00418 
00419   QString helpContents = expressionItem->getHelpText();
00420 
00421   // Return the function help that is set for the function if there is one.
00422   if ( helpContents.isEmpty() )
00423   {
00424     QString name = expressionItem->data( Qt::UserRole ).toString();
00425 
00426     if ( expressionItem->getItemType() == QgsExpressionItem::Field )
00427       helpContents = QgsExpression::helptext( "Field" );
00428     else
00429       helpContents = QgsExpression::helptext( name );
00430   }
00431 
00432   QString myStyle = QgsApplication::reportStyleSheet();
00433   return "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
00434 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines