|
QGIS API Documentation
master-59fd5e0
|
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 }