Quantum GIS API Documentation  1.7.4
src/gui/qgssearchquerybuilder.cpp
Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines