Quantum GIS API Documentation  1.8
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   // The open and save button are for future.
00034   btnOpen->hide();
00035   btnSave->hide();
00036   highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
00037 
00038   mModel = new QStandardItemModel( );
00039   mProxyModel = new QgsExpressionItemSearchProxy();
00040   mProxyModel->setSourceModel( mModel );
00041   expressionTree->setModel( mProxyModel );
00042 
00043   expressionTree->setContextMenuPolicy( Qt::CustomContextMenu );
00044   connect( this, SIGNAL( expressionParsed( bool ) ), this, SLOT( setExpressionState( bool ) ) );
00045   connect( expressionTree, SIGNAL( customContextMenuRequested( const QPoint & ) ), this, SLOT( showContextMenu( const QPoint & ) ) );
00046   connect( expressionTree->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ),
00047            this, SLOT( currentChanged( const QModelIndex &, const QModelIndex & ) ) );
00048 
00049   foreach( QPushButton* button, mOperatorsGroupBox->findChildren<QPushButton *>() )
00050   {
00051     connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) );
00052   }
00053 
00054   // TODO Can we move this stuff to QgsExpression, like the functions?
00055   registerItem( tr( "Operators" ), "+", " + " );
00056   registerItem( tr( "Operators" ), "-", " -" );
00057   registerItem( tr( "Operators" ), "*", " * " );
00058   registerItem( tr( "Operators" ), "/", " / " );
00059   registerItem( tr( "Operators" ), "%", " % " );
00060   registerItem( tr( "Operators" ), "^", " ^ " );
00061   registerItem( tr( "Operators" ), "=", " = " );
00062   registerItem( tr( "Operators" ), ">", " > " );
00063   registerItem( tr( "Operators" ), "<", " < " );
00064   registerItem( tr( "Operators" ), "<>", " <> " );
00065   registerItem( tr( "Operators" ), "<=", " <= " );
00066   registerItem( tr( "Operators" ), ">=", " >= " );
00067   registerItem( tr( "Operators" ), "||", " || ",
00068                 QString( "<b>|| %1</b><br><i>%2</i><br><i>%3:</i>%4" )
00069                 .arg( tr( "(String Concatenation)" ) )
00070                 .arg( tr( "Joins two values together into a string" ) )
00071                 .arg( tr( "Usage" ) )
00072                 .arg( tr( "'Dia' || Diameter" ) ) );
00073   registerItem( tr( "Operators" ), "LIKE", " LIKE " );
00074   registerItem( tr( "Operators" ), "ILIKE", " ILIKE " );
00075   registerItem( tr( "Operators" ), "IS", " IS NOT " );
00076   registerItem( tr( "Operators" ), "OR", " OR " );
00077   registerItem( tr( "Operators" ), "AND", " AND " );
00078   registerItem( tr( "Operators" ), "NOT", " NOT " );
00079 
00080 
00081   // Load the functions from the QgsExpression class
00082   int count = QgsExpression::functionCount();
00083   for ( int i = 0; i < count; i++ )
00084   {
00085     QgsExpression::FunctionDef func = QgsExpression::BuiltinFunctions()[i];
00086     QString name = func.mName;
00087     if ( func.mParams >= 1 )
00088       name += "(";
00089     registerItem( func.mGroup, func.mName, " " + name + " " );
00090   };
00091 }
00092 
00093 
00094 QgsExpressionBuilderWidget::~QgsExpressionBuilderWidget()
00095 {
00096 
00097 }
00098 
00099 void QgsExpressionBuilderWidget::setLayer( QgsVectorLayer *layer )
00100 {
00101   mLayer = layer;
00102 }
00103 
00104 void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const QModelIndex & )
00105 {
00106   // Get the item
00107   QModelIndex idx = mProxyModel->mapToSource( index );
00108   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00109   if ( item == 0 )
00110     return;
00111 
00112   // Loading field values are handled with a
00113   // right click so we just show the help.
00114   if ( item->getItemType() != QgsExpressionItem::Field )
00115   {
00116 
00117     mValueGroupBox->hide();
00118     mValueListWidget->clear();
00119   }
00120   // Show the help for the current item.
00121   QString help = loadFunctionHelp( item );
00122   txtHelpText->setText( help );
00123   txtHelpText->setToolTip( txtHelpText->toPlainText() );
00124 }
00125 
00126 void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index )
00127 {
00128   QModelIndex idx = mProxyModel->mapToSource( index );
00129   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00130   if ( item == 0 )
00131     return;
00132 
00133   // Don't handle the double click it we are on a header node.
00134   if ( item->getItemType() == QgsExpressionItem::Header )
00135     return;
00136 
00137   // Insert the expression text.
00138   txtExpressionString->insertPlainText( item->getExpressionText() );
00139 }
00140 
00141 void QgsExpressionBuilderWidget::loadFieldNames()
00142 {
00143   // TODO We should really return a error the user of the widget that
00144   // the there is no layer set.
00145   if ( !mLayer )
00146     return;
00147 
00148   const QgsFieldMap fieldMap = mLayer->pendingFields();
00149   loadFieldNames( fieldMap );
00150 }
00151 
00152 void QgsExpressionBuilderWidget::loadFieldNames( QgsFieldMap fields )
00153 {
00154   if ( fields.isEmpty() )
00155     return;
00156 
00157   QStringList fieldNames;
00158   foreach( QgsField field, fields )
00159   {
00160     QString fieldName = field.name();
00161     fieldNames << fieldName;
00162     registerItem( tr( "Fields and Values" ), fieldName, " \"" + fieldName + "\" ", "", QgsExpressionItem::Field );
00163   }
00164   highlighter->addFields( fieldNames );
00165 }
00166 
00167 void QgsExpressionBuilderWidget::fillFieldValues( int fieldIndex, int countLimit )
00168 {
00169   // TODO We should really return a error the user of the widget that
00170   // the there is no layer set.
00171   if ( !mLayer )
00172     return;
00173 
00174   // TODO We should thread this so that we don't hold the user up if the layer is massive.
00175   mValueListWidget->clear();
00176   mValueListWidget->setUpdatesEnabled( false );
00177   mValueListWidget->blockSignals( true );
00178 
00179   QList<QVariant> values;
00180   mLayer->uniqueValues( fieldIndex, values, countLimit );
00181   foreach( QVariant value, values )
00182   {
00183     if ( value.isNull() )
00184       mValueListWidget->addItem( "NULL" );
00185     else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong )
00186       mValueListWidget->addItem( value.toString() );
00187     else
00188       mValueListWidget->addItem( "'" + value.toString().replace( "'", "''" ) + "'" );
00189   }
00190 
00191   mValueListWidget->setUpdatesEnabled( true );
00192   mValueListWidget->blockSignals( false );
00193 }
00194 
00195 void QgsExpressionBuilderWidget::registerItem( QString group,
00196     QString label,
00197     QString expressionText,
00198     QString helpText,
00199     QgsExpressionItem::ItemType type )
00200 {
00201   QgsExpressionItem* item = new QgsExpressionItem( label, expressionText, helpText, type );
00202   // Look up the group and insert the new function.
00203   if ( mExpressionGroups.contains( group ) )
00204   {
00205     QgsExpressionItem* groupNode = mExpressionGroups.value( group );
00206     groupNode->appendRow( item );
00207   }
00208   else
00209   {
00210     // If the group doesn't exist yet we make it first.
00211     QgsExpressionItem* newgroupNode = new QgsExpressionItem( group, "", QgsExpressionItem::Header );
00212     newgroupNode->appendRow( item );
00213     mModel->appendRow( newgroupNode );
00214     mExpressionGroups.insert( group , newgroupNode );
00215   }
00216 }
00217 
00218 bool QgsExpressionBuilderWidget::isExpressionValid()
00219 {
00220   return mExpressionValid;
00221 }
00222 
00223 QString QgsExpressionBuilderWidget::expressionText()
00224 {
00225   return txtExpressionString->toPlainText();
00226 }
00227 
00228 void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
00229 {
00230   txtExpressionString->setPlainText( expression );
00231 }
00232 
00233 void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
00234 {
00235   QString text = txtExpressionString->toPlainText();
00236 
00237   // If the string is empty the expression will still "fail" although
00238   // we don't show the user an error as it will be confusing.
00239   if ( text.isEmpty() )
00240   {
00241     lblPreview->setText( "" );
00242     lblPreview->setStyleSheet( "" );
00243     txtExpressionString->setToolTip( "" );
00244     lblPreview->setToolTip( "" );
00245     emit expressionParsed( false );
00246     return;
00247   }
00248 
00249   QgsExpression exp( text );
00250 
00251   // TODO We could do this without a layer.
00252   // Maybe just calling exp.evaluate()?
00253   if ( mLayer )
00254   {
00255     if ( !mFeature.isValid() )
00256     {
00257       mLayer->select( mLayer->pendingAllAttributesList(), QgsRectangle(), mLayer->geometryType() != QGis::NoGeometry && exp.needsGeometry() );
00258       mLayer->nextFeature( mFeature );
00259     }
00260 
00261     if ( mFeature.isValid() )
00262     {
00263       QVariant value = exp.evaluate( &mFeature, mLayer->pendingFields() );
00264       if ( !exp.hasEvalError() )
00265         lblPreview->setText( value.toString() );
00266     }
00267     else
00268     {
00269       // The feature is invalid because we don't have one but that doesn't mean user can't
00270       // build a expression string.  They just get no preview.
00271       lblPreview->setText( "" );
00272     }
00273   }
00274 
00275   if ( exp.hasParserError() || exp.hasEvalError() )
00276   {
00277     QString tooltip = QString( "<b>%1:</b><br>%2" ).arg( tr( "Parser Error" ) ).arg( exp.parserErrorString() );
00278     if ( exp.hasEvalError() )
00279       tooltip += QString( "<br><br><b>%1:</b><br>%2" ).arg( tr( "Eval Error" ) ).arg( exp.evalErrorString() );
00280 
00281     lblPreview->setText( tr( "Expression is invalid <a href=""more"">(more info)</a>" ) );
00282     lblPreview->setStyleSheet( "color: rgba(255, 6, 10,  255);" );
00283     txtExpressionString->setToolTip( tooltip );
00284     lblPreview->setToolTip( tooltip );
00285     emit expressionParsed( false );
00286     return;
00287   }
00288   else
00289   {
00290     lblPreview->setStyleSheet( "" );
00291     txtExpressionString->setToolTip( "" );
00292     lblPreview->setToolTip( "" );
00293     emit expressionParsed( true );
00294   }
00295 }
00296 
00297 void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
00298 {
00299   mProxyModel->setFilterWildcard( txtSearchEdit->text() );
00300   if ( txtSearchEdit->text().isEmpty() )
00301     expressionTree->collapseAll();
00302   else
00303     expressionTree->expandAll();
00304 }
00305 
00306 void QgsExpressionBuilderWidget::on_lblPreview_linkActivated( QString link )
00307 {
00308   Q_UNUSED( link );
00309   QgsMessageViewer * mv = new QgsMessageViewer( this );
00310   mv->setWindowTitle( tr( "More info on expression error" ) );
00311   mv->setMessageAsHtml( txtExpressionString->toolTip() );
00312   mv->exec();
00313 }
00314 
00315 void QgsExpressionBuilderWidget::on_mValueListWidget_itemDoubleClicked( QListWidgetItem *item )
00316 {
00317   txtExpressionString->insertPlainText( " " + item->text() + " " );
00318 }
00319 
00320 void QgsExpressionBuilderWidget::operatorButtonClicked()
00321 {
00322   QPushButton* button = dynamic_cast<QPushButton*>( sender() );
00323   txtExpressionString->insertPlainText( " " + button->text() + " " );
00324 }
00325 
00326 void QgsExpressionBuilderWidget::showContextMenu( const QPoint & pt )
00327 {
00328   QModelIndex idx = expressionTree->indexAt( pt );
00329   idx = mProxyModel->mapToSource( idx );
00330   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00331   if ( !item )
00332     return;
00333 
00334   if ( item->getItemType() == QgsExpressionItem::Field )
00335   {
00336     QMenu* menu = new QMenu( this );
00337     menu->addAction( tr( "Load top 10 unique values" ), this, SLOT( loadSampleValues() ) );
00338     menu->addAction( tr( "Load all unique values" ), this, SLOT( loadAllValues() ) );
00339     menu->popup( expressionTree->mapToGlobal( pt ) );
00340   }
00341 }
00342 
00343 void QgsExpressionBuilderWidget::loadSampleValues()
00344 {
00345   QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
00346   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00347   // TODO We should really return a error the user of the widget that
00348   // the there is no layer set.
00349   if ( !mLayer )
00350     return;
00351 
00352   mValueGroupBox->show();
00353   int fieldIndex = mLayer->fieldNameIndex( item->text() );
00354   fillFieldValues( fieldIndex, 10 );
00355 }
00356 
00357 void QgsExpressionBuilderWidget::loadAllValues()
00358 {
00359   QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() );
00360   QgsExpressionItem* item = dynamic_cast<QgsExpressionItem*>( mModel->itemFromIndex( idx ) );
00361   // TODO We should really return a error the user of the widget that
00362   // the there is no layer set.
00363   if ( !mLayer )
00364     return;
00365 
00366   mValueGroupBox->show();
00367   int fieldIndex = mLayer->fieldNameIndex( item->text() );
00368   fillFieldValues( fieldIndex, -1 );
00369 }
00370 
00371 void QgsExpressionBuilderWidget::setExpressionState( bool state )
00372 {
00373   mExpressionValid = state;
00374 }
00375 
00376 QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem* functionName )
00377 {
00378   if ( functionName == NULL )
00379     return "";
00380 
00381   // set up the path to the help file
00382   QString helpFilesPath = QgsApplication::pkgDataPath() + "/resources/function_help/";
00383   /*
00384    * determine the locale and create the file name from
00385    * the context id
00386    */
00387   QString lang = QLocale::system().name();
00388 
00389   QSettings settings;
00390   if ( settings.value( "locale/overrideFlag", false ).toBool() )
00391   {
00392     QLocale l( settings.value( "locale/userLocale", "en_US" ).toString() );
00393     lang = l.name();
00394   }
00395   /*
00396    * If the language isn't set on the system, assume en_US,
00397    * otherwise we get the banner at the top of the help file
00398    * saying it isn't available in "your" language. Some systems
00399    * may be installed without the LANG environment being set.
00400    */
00401   if ( lang.length() == 0 || lang == "C" || lang.startsWith( "en_" ) )
00402   {
00403     lang = "en_US";
00404   }
00405 
00406   QString name = functionName->text();
00407 
00408   if ( functionName->getItemType() == QgsExpressionItem::Field )
00409     name = "Field";
00410 
00411   QString fullHelpPath = helpFilesPath + name + "-" + lang;
00412   // get the help content and title from the localized file
00413   QString helpContents;
00414   QFile file( fullHelpPath );
00415 
00416   QString missingError = tr( "<h3>Oops! QGIS can't find help for this function.</h3>"
00417                              "The help file for %1 was not found.<br>"
00418                            ).arg( Qt::escape( name ) );
00419 
00420   if ( !lang.startsWith( "en_" ) )
00421   {
00422     // try en_US version if localized version is unavailable
00423     if ( !file.exists() )
00424     {
00425       helpContents = tr( "(Showing English version as there was no help available in your language (%1). If you would like to create it, contact the QGIS translation team).<br>" ).arg( lang );
00426 
00427       missingError += tr( "It was neither available in your language (%1) nor English." ).arg( lang );
00428 
00429       // try en_US next
00430       fullHelpPath = helpFilesPath + functionName->text() + "-en_US";
00431       file.setFileName( fullHelpPath );
00432     }
00433   }
00434 
00435   missingError += tr( "<br>If you would like to create it, contact the QGIS development team." );
00436 
00437   if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
00438   {
00439     helpContents = missingError;
00440   }
00441   else
00442   {
00443     QTextStream in( &file );
00444     in.setCodec( "UTF-8" ); // Help files must be in Utf-8
00445     while ( !in.atEnd() )
00446     {
00447       QString line = in.readLine();
00448       helpContents += line;
00449     }
00450   }
00451 
00452   file.close();
00453 
00454   // Set the browser text to the help contents
00455   QString myStyle = QgsApplication::reportStyleSheet();
00456   helpContents = "<head><style>" + myStyle + "</style></head><body>" + helpContents + "</body>";
00457   return helpContents;
00458 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines