QGIS API Documentation  master-59fd5e0
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   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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines