Quantum GIS API Documentation
1.8
|
00001 /*************************************************************************** 00002 qgssearchquerybuilder.cpp - Query builder for search strings 00003 ---------------------- 00004 begin : March 2006 00005 copyright : (C) 2006 by Martin Dobias 00006 email : wonder.sk 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 <QDomDocument> 00017 #include <QDomElement> 00018 #include <QFileDialog> 00019 #include <QFileInfo> 00020 #include <QInputDialog> 00021 #include <QListView> 00022 #include <QMessageBox> 00023 #include <QSettings> 00024 #include <QStandardItem> 00025 #include <QTextStream> 00026 #include "qgsfeature.h" 00027 #include "qgsfield.h" 00028 #include "qgssearchquerybuilder.h" 00029 #include "qgsexpression.h" 00030 #include "qgsvectorlayer.h" 00031 #include "qgslogger.h" 00032 00033 QgsSearchQueryBuilder::QgsSearchQueryBuilder( QgsVectorLayer* layer, 00034 QWidget *parent, Qt::WFlags fl ) 00035 : QDialog( parent, fl ), mLayer( layer ) 00036 { 00037 setupUi( this ); 00038 setupListViews(); 00039 00040 setWindowTitle( tr( "Search query builder" ) ); 00041 00042 QPushButton *pbn = new QPushButton( tr( "&Test" ) ); 00043 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole ); 00044 connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnTest_clicked() ) ); 00045 00046 pbn = new QPushButton( tr( "&Clear" ) ); 00047 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole ); 00048 connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnClear_clicked() ) ); 00049 00050 pbn = new QPushButton( tr( "&Save..." ) ); 00051 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole ); 00052 pbn->setToolTip( tr( "Save query to an xml file" ) ); 00053 connect( pbn, SIGNAL( clicked() ), this, SLOT( saveQuery() ) ); 00054 00055 pbn = new QPushButton( tr( "&Load..." ) ); 00056 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole ); 00057 pbn->setToolTip( tr( "Load query from xml file" ) ); 00058 connect( pbn, SIGNAL( clicked() ), this, SLOT( loadQuery() ) ); 00059 00060 if ( layer ) 00061 lblDataUri->setText( layer->name() ); 00062 populateFields(); 00063 } 00064 00065 QgsSearchQueryBuilder::~QgsSearchQueryBuilder() 00066 { 00067 } 00068 00069 00070 void QgsSearchQueryBuilder::populateFields() 00071 { 00072 if ( !mLayer ) 00073 return; 00074 00075 QgsDebugMsg( "entering." ); 00076 QRegExp reQuote( "[A-Za-z_][A-Za-z0-9_]*" ); 00077 const QgsFieldMap& fields = mLayer->pendingFields(); 00078 for ( QgsFieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it ) 00079 { 00080 QString fieldName = it->name(); 00081 mFieldMap[fieldName] = it.key(); 00082 if ( !reQuote.exactMatch( fieldName ) ) // quote if necessary 00083 fieldName = QgsExpression::quotedColumnRef( fieldName ); 00084 QStandardItem *myItem = new QStandardItem( fieldName ); 00085 myItem->setEditable( false ); 00086 mModelFields->insertRow( mModelFields->rowCount(), myItem ); 00087 } 00088 } 00089 00090 void QgsSearchQueryBuilder::setupListViews() 00091 { 00092 QgsDebugMsg( "entering." ); 00093 //Models 00094 mModelFields = new QStandardItemModel(); 00095 mModelValues = new QStandardItemModel(); 00096 lstFields->setModel( mModelFields ); 00097 lstValues->setModel( mModelValues ); 00098 // Modes 00099 lstFields->setViewMode( QListView::ListMode ); 00100 lstValues->setViewMode( QListView::ListMode ); 00101 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows ); 00102 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows ); 00103 // Performance tip since Qt 4.1 00104 lstFields->setUniformItemSizes( true ); 00105 lstValues->setUniformItemSizes( true ); 00106 } 00107 00108 void QgsSearchQueryBuilder::getFieldValues( int limit ) 00109 { 00110 if ( !mLayer ) 00111 { 00112 return; 00113 } 00114 // clear the values list 00115 mModelValues->clear(); 00116 00117 // determine the field type 00118 QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString(); 00119 int fieldIndex = mFieldMap[fieldName]; 00120 QgsField field = mLayer->pendingFields()[fieldIndex];//provider->fields()[fieldIndex]; 00121 bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double ); 00122 00123 QgsFeature feat; 00124 QString value; 00125 00126 QgsAttributeList attrs; 00127 attrs.append( fieldIndex ); 00128 00129 mLayer->select( attrs, QgsRectangle(), false ); 00130 00131 lstValues->setCursor( Qt::WaitCursor ); 00132 // Block for better performance 00133 mModelValues->blockSignals( true ); 00134 lstValues->setUpdatesEnabled( false ); 00135 00137 QSet<QString> insertedValues; 00138 00139 while ( mLayer->nextFeature( feat ) && 00140 ( limit == 0 || mModelValues->rowCount() != limit ) ) 00141 { 00142 const QgsAttributeMap& attributes = feat.attributeMap(); 00143 value = attributes[fieldIndex].toString(); 00144 00145 if ( !numeric ) 00146 { 00147 // put string in single quotes and escape single quotes in the string 00148 value = "'" + value.replace( "'", "''" ) + "'"; 00149 } 00150 00151 // add item only if it's not there already 00152 if ( !insertedValues.contains( value ) ) 00153 { 00154 QStandardItem *myItem = new QStandardItem( value ); 00155 myItem->setEditable( false ); 00156 mModelValues->insertRow( mModelValues->rowCount(), myItem ); 00157 insertedValues.insert( value ); 00158 } 00159 } 00160 // Unblock for normal use 00161 mModelValues->blockSignals( false ); 00162 lstValues->setUpdatesEnabled( true ); 00163 // TODO: already sorted, signal emit to refresh model 00164 mModelValues->sort( 0 ); 00165 lstValues->setCursor( Qt::ArrowCursor ); 00166 } 00167 00168 void QgsSearchQueryBuilder::on_btnSampleValues_clicked() 00169 { 00170 getFieldValues( 25 ); 00171 } 00172 00173 void QgsSearchQueryBuilder::on_btnGetAllValues_clicked() 00174 { 00175 getFieldValues( 0 ); 00176 } 00177 00178 void QgsSearchQueryBuilder::on_btnTest_clicked() 00179 { 00180 long count = countRecords( txtSQL->toPlainText() ); 00181 00182 // error? 00183 if ( count == -1 ) 00184 return; 00185 00186 QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) ); 00187 } 00188 00189 // This method tests the number of records that would be returned 00190 long QgsSearchQueryBuilder::countRecords( QString searchString ) 00191 { 00192 QgsExpression search( searchString ); 00193 if ( search.hasParserError() ) 00194 { 00195 QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() ); 00196 return -1; 00197 } 00198 00199 if ( !mLayer ) 00200 return -1; 00201 00202 bool fetchGeom = search.needsGeometry(); 00203 00204 int count = 0; 00205 QgsFeature feat; 00206 const QgsFieldMap& fields = mLayer->pendingFields(); 00207 00208 if ( !search.prepare( fields ) ) 00209 { 00210 QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() ); 00211 return -1; 00212 } 00213 00214 QApplication::setOverrideCursor( Qt::WaitCursor ); 00215 00216 QgsAttributeList allAttributes = mLayer->pendingAllAttributesList(); 00217 mLayer->select( allAttributes, QgsRectangle(), fetchGeom ); 00218 00219 while ( mLayer->nextFeature( feat ) ) 00220 { 00221 QVariant value = search.evaluate( &feat ); 00222 if ( value.toInt() != 0 ) 00223 { 00224 count++; 00225 } 00226 00227 // check if there were errors during evaulating 00228 if ( search.hasEvalError() ) 00229 break; 00230 } 00231 00232 QApplication::restoreOverrideCursor(); 00233 00234 if ( search.hasEvalError() ) 00235 { 00236 QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() ); 00237 return -1; 00238 } 00239 00240 return count; 00241 } 00242 00243 00244 void QgsSearchQueryBuilder::on_btnOk_clicked() 00245 { 00246 // if user hits Ok and there is no query, skip the validation 00247 if ( txtSQL->toPlainText().trimmed().length() > 0 ) 00248 { 00249 accept(); 00250 return; 00251 } 00252 00253 // test the query to see if it will result in a valid layer 00254 long numRecs = countRecords( txtSQL->toPlainText() ); 00255 if ( numRecs == -1 ) 00256 { 00257 // error shown in countRecords 00258 } 00259 else if ( numRecs == 0 ) 00260 { 00261 QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) ); 00262 } 00263 else 00264 { 00265 accept(); 00266 } 00267 00268 } 00269 00270 void QgsSearchQueryBuilder::on_btnEqual_clicked() 00271 { 00272 txtSQL->insertPlainText( " = " ); 00273 } 00274 00275 void QgsSearchQueryBuilder::on_btnLessThan_clicked() 00276 { 00277 txtSQL->insertPlainText( " < " ); 00278 } 00279 00280 void QgsSearchQueryBuilder::on_btnGreaterThan_clicked() 00281 { 00282 txtSQL->insertPlainText( " > " ); 00283 } 00284 00285 void QgsSearchQueryBuilder::on_btnPct_clicked() 00286 { 00287 txtSQL->insertPlainText( "%" ); 00288 } 00289 00290 void QgsSearchQueryBuilder::on_btnIn_clicked() 00291 { 00292 txtSQL->insertPlainText( " IN " ); 00293 } 00294 00295 void QgsSearchQueryBuilder::on_btnNotIn_clicked() 00296 { 00297 txtSQL->insertPlainText( " NOT IN " ); 00298 } 00299 00300 void QgsSearchQueryBuilder::on_btnLike_clicked() 00301 { 00302 txtSQL->insertPlainText( " LIKE " ); 00303 } 00304 00305 QString QgsSearchQueryBuilder::searchString() 00306 { 00307 return txtSQL->toPlainText(); 00308 } 00309 00310 void QgsSearchQueryBuilder::setSearchString( QString searchString ) 00311 { 00312 txtSQL->setPlainText( searchString ); 00313 } 00314 00315 void QgsSearchQueryBuilder::on_lstFields_doubleClicked( const QModelIndex &index ) 00316 { 00317 txtSQL->insertPlainText( mModelFields->data( index ).toString() ); 00318 } 00319 00320 void QgsSearchQueryBuilder::on_lstValues_doubleClicked( const QModelIndex &index ) 00321 { 00322 txtSQL->insertPlainText( mModelValues->data( index ).toString() ); 00323 } 00324 00325 void QgsSearchQueryBuilder::on_btnLessEqual_clicked() 00326 { 00327 txtSQL->insertPlainText( " <= " ); 00328 } 00329 00330 void QgsSearchQueryBuilder::on_btnGreaterEqual_clicked() 00331 { 00332 txtSQL->insertPlainText( " >= " ); 00333 } 00334 00335 void QgsSearchQueryBuilder::on_btnNotEqual_clicked() 00336 { 00337 txtSQL->insertPlainText( " != " ); 00338 } 00339 00340 void QgsSearchQueryBuilder::on_btnAnd_clicked() 00341 { 00342 txtSQL->insertPlainText( " AND " ); 00343 } 00344 00345 void QgsSearchQueryBuilder::on_btnNot_clicked() 00346 { 00347 txtSQL->insertPlainText( " NOT " ); 00348 } 00349 00350 void QgsSearchQueryBuilder::on_btnOr_clicked() 00351 { 00352 txtSQL->insertPlainText( " OR " ); 00353 } 00354 00355 void QgsSearchQueryBuilder::on_btnClear_clicked() 00356 { 00357 txtSQL->clear(); 00358 } 00359 00360 void QgsSearchQueryBuilder::on_btnILike_clicked() 00361 { 00362 txtSQL->insertPlainText( " ILIKE " ); 00363 } 00364 00365 void QgsSearchQueryBuilder::saveQuery() 00366 { 00367 QSettings s; 00368 QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString(); 00369 //save as qqt (QGIS query file) 00370 QString saveFileName = QFileDialog::getSaveFileName( 0, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" ); 00371 if ( saveFileName.isNull() ) 00372 { 00373 return; 00374 } 00375 00376 if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) ) 00377 { 00378 saveFileName += ".qqf"; 00379 } 00380 00381 QFile saveFile( saveFileName ); 00382 if ( !saveFile.open( QIODevice::WriteOnly ) ) 00383 { 00384 QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for writing" ) ); 00385 return; 00386 } 00387 00388 QDomDocument xmlDoc; 00389 QDomElement queryElem = xmlDoc.createElement( "Query" ); 00390 QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->toPlainText() ); 00391 queryElem.appendChild( queryTextNode ); 00392 xmlDoc.appendChild( queryElem ); 00393 00394 QTextStream fileStream( &saveFile ); 00395 xmlDoc.save( fileStream, 2 ); 00396 00397 QFileInfo fi( saveFile ); 00398 s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() ); 00399 } 00400 00401 void QgsSearchQueryBuilder::loadQuery() 00402 { 00403 QSettings s; 00404 QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString(); 00405 00406 QString queryFileName = QFileDialog::getOpenFileName( 0, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" ); 00407 if ( queryFileName.isNull() ) 00408 { 00409 return; 00410 } 00411 00412 QFile queryFile( queryFileName ); 00413 if ( !queryFile.open( QIODevice::ReadOnly ) ) 00414 { 00415 QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for reading" ) ); 00416 return; 00417 } 00418 QDomDocument queryDoc; 00419 if ( !queryDoc.setContent( &queryFile ) ) 00420 { 00421 QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid xml document" ) ); 00422 return; 00423 } 00424 00425 QDomElement queryElem = queryDoc.firstChildElement( "Query" ); 00426 if ( queryElem.isNull() ) 00427 { 00428 QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid query document" ) ); 00429 return; 00430 } 00431 00432 QString query = queryElem.text(); 00433 00434 //todo: test if all the attributes are valid 00435 QgsExpression search( query ); 00436 if ( search.hasParserError() ) 00437 { 00438 QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() ); 00439 return; 00440 } 00441 00442 QString newQueryText = query; 00443 00444 #if 0 00445 // TODO: implement with visitor pattern in QgsExpression 00446 00447 QStringList attributes = searchTree->referencedColumns(); 00448 QMap< QString, QString> attributesToReplace; 00449 QStringList existingAttributes; 00450 00451 //get all existing fields 00452 QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin(); 00453 for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt ) 00454 { 00455 existingAttributes.push_back( fieldIt.key() ); 00456 } 00457 00458 //if a field does not exist, ask what field should be used instead 00459 QStringList::const_iterator attIt = attributes.constBegin(); 00460 for ( ; attIt != attributes.constEnd(); ++attIt ) 00461 { 00462 //test if attribute is there 00463 if ( !mFieldMap.contains( *attIt ) ) 00464 { 00465 bool ok; 00466 QString replaceAttribute = QInputDialog::getItem( 0, tr( "Select attribute" ), tr( "There is no attribute '%1' in the current vector layer. Please select an existing attribute" ).arg( *attIt ), 00467 existingAttributes, 0, false, &ok ); 00468 if ( !ok || replaceAttribute.isEmpty() ) 00469 { 00470 return; 00471 } 00472 attributesToReplace.insert( *attIt, replaceAttribute ); 00473 } 00474 } 00475 00476 //Now replace all the string in the query 00477 QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes(); 00478 QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin(); 00479 for ( ; columnIt != columnRefList.end(); ++columnIt ) 00480 { 00481 QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() ); 00482 if ( replaceIt != attributesToReplace.constEnd() ) 00483 { 00484 ( *columnIt )->setColumnRef( replaceIt.value() ); 00485 } 00486 } 00487 00488 if ( attributesToReplace.size() > 0 ) 00489 { 00490 newQueryText = query; 00491 } 00492 #endif 00493 00494 txtSQL->clear(); 00495 txtSQL->insertPlainText( newQueryText ); 00496 } 00497