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