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