Quantum GIS API Documentation  1.8
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 
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines