Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgssearchquerybuilder.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssearchquerybuilder.cpp - Query builder for search strings
3  ----------------------
4  begin : March 2006
5  copyright : (C) 2006 by Martin Dobias
6  email : wonder.sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 /* $Id$ */
16 
17 #include <QDomDocument>
18 #include <QDomElement>
19 #include <QFileDialog>
20 #include <QFileInfo>
21 #include <QInputDialog>
22 #include <QListView>
23 #include <QMessageBox>
24 #include <QSettings>
25 #include <QStandardItem>
26 #include <QTextStream>
27 #include "qgsfeature.h"
28 #include "qgsfield.h"
29 #include "qgssearchquerybuilder.h"
30 #include "qgssearchstring.h"
31 #include "qgssearchtreenode.h"
32 #include "qgsvectorlayer.h"
33 #include "qgslogger.h"
34 
36  QWidget *parent, Qt::WFlags fl )
37  : QDialog( parent, fl ), mLayer( layer )
38 {
39  setupUi( this );
41 
42  setWindowTitle( tr( "Search query builder" ) );
43 
44  QPushButton *pbn = new QPushButton( tr( "&Test" ) );
45  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
46  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnTest_clicked() ) );
47 
48  pbn = new QPushButton( tr( "&Clear" ) );
49  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
50  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnClear_clicked() ) );
51 
52  pbn = new QPushButton( tr( "&Save..." ) );
53  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
54  pbn->setToolTip( tr( "Save query to an xml file" ) );
55  connect( pbn, SIGNAL( clicked() ), this, SLOT( saveQuery() ) );
56 
57  pbn = new QPushButton( tr( "&Load..." ) );
58  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
59  pbn->setToolTip( tr( "Load query from xml file" ) );
60  connect( pbn, SIGNAL( clicked() ), this, SLOT( loadQuery() ) );
61 
62  if ( layer )
63  lblDataUri->setText( layer->name() );
65 }
66 
68 {
69 }
70 
71 
73 {
74  if ( !mLayer )
75  return;
76 
77  QgsDebugMsg( "entering." );
78  QRegExp reQuote( "[A-Za-z_][A-Za-z0-9_]*" );
79  const QgsFieldMap& fields = mLayer->pendingFields();
80  for ( QgsFieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it )
81  {
82  QString fieldName = it->name();
83  mFieldMap[fieldName] = it.key();
84  if ( !reQuote.exactMatch( fieldName ) ) // quote if necessary
85  fieldName = QgsSearchTreeNode::quotedColumnRef( fieldName );
86  QStandardItem *myItem = new QStandardItem( fieldName );
87  myItem->setEditable( false );
88  mModelFields->insertRow( mModelFields->rowCount(), myItem );
89  }
90 }
91 
93 {
94  QgsDebugMsg( "entering." );
95  //Models
96  mModelFields = new QStandardItemModel();
97  mModelValues = new QStandardItemModel();
98  lstFields->setModel( mModelFields );
99  lstValues->setModel( mModelValues );
100  // Modes
101  lstFields->setViewMode( QListView::ListMode );
102  lstValues->setViewMode( QListView::ListMode );
103  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
104  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
105  // Performance tip since Qt 4.1
106  lstFields->setUniformItemSizes( true );
107  lstValues->setUniformItemSizes( true );
108 }
109 
111 {
112  if ( !mLayer )
113  {
114  return;
115  }
116  // clear the values list
117  mModelValues->clear();
118 
119  // determine the field type
120  QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
121  int fieldIndex = mFieldMap[fieldName];
122  QgsField field = mLayer->pendingFields()[fieldIndex];//provider->fields()[fieldIndex];
123  bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double );
124 
125  QgsFeature feat;
126  QString value;
127 
128  QgsAttributeList attrs;
129  attrs.append( fieldIndex );
130 
131  mLayer->select( attrs, QgsRectangle(), false );
132 
133  lstValues->setCursor( Qt::WaitCursor );
134  // Block for better performance
135  mModelValues->blockSignals( true );
136  lstValues->setUpdatesEnabled( false );
137 
139  QSet<QString> insertedValues;
140 
141  while ( mLayer->nextFeature( feat ) &&
142  ( limit == 0 || mModelValues->rowCount() != limit ) )
143  {
144  const QgsAttributeMap& attributes = feat.attributeMap();
145  value = attributes[fieldIndex].toString();
146 
147  if ( !numeric )
148  {
149  // put string in single quotes and escape single quotes in the string
150  value = "'" + value.replace( "'", "''" ) + "'";
151  }
152 
153  // add item only if it's not there already
154  if ( !insertedValues.contains( value ) )
155  {
156  QStandardItem *myItem = new QStandardItem( value );
157  myItem->setEditable( false );
158  mModelValues->insertRow( mModelValues->rowCount(), myItem );
159  insertedValues.insert( value );
160  }
161  }
162  // Unblock for normal use
163  mModelValues->blockSignals( false );
164  lstValues->setUpdatesEnabled( true );
165  // TODO: already sorted, signal emit to refresh model
166  mModelValues->sort( 0 );
167  lstValues->setCursor( Qt::ArrowCursor );
168 }
169 
171 {
172  getFieldValues( 25 );
173 }
174 
176 {
177  getFieldValues( 0 );
178 }
179 
181 {
182  long count = countRecords( txtSQL->toPlainText() );
183 
184  // error?
185  if ( count == -1 )
186  return;
187 
188  QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) );
189 }
190 
191 // This method tests the number of records that would be returned
192 long QgsSearchQueryBuilder::countRecords( QString searchString )
193 {
194  QgsSearchString search;
195  if ( !search.setString( searchString ) )
196  {
197  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorMsg() );
198  return -1;
199  }
200 
201  if ( !mLayer )
202  return -1;
203 
204  QgsSearchTreeNode* searchTree = search.tree();
205  if ( !searchTree )
206  {
207  // entered empty search string
208  return mLayer->featureCount();
209  }
210 
211  bool fetchGeom = searchTree->needsGeometry();
212 
213  QApplication::setOverrideCursor( Qt::WaitCursor );
214 
215  int count = 0;
216  QgsFeature feat;
217  const QgsFieldMap& fields = mLayer->pendingFields();
219  mLayer->select( allAttributes, QgsRectangle(), fetchGeom );
220 
221  while ( mLayer->nextFeature( feat ) )
222  {
223  if ( searchTree->checkAgainst( fields, feat ) )
224  {
225  count++;
226  }
227 
228  // check if there were errors during evaulating
229  if ( searchTree->hasError() )
230  break;
231  }
232 
233  QApplication::restoreOverrideCursor();
234 
235  if ( searchTree->hasError() )
236  {
237  QMessageBox::critical( this, tr( "Error during search" ), searchTree->errorMsg() );
238  return -1;
239  }
240 
241  return count;
242 }
243 
244 
246 {
247  // if user hits Ok and there is no query, skip the validation
248  if ( txtSQL->toPlainText().trimmed().length() > 0 )
249  {
250  accept();
251  return;
252  }
253 
254  // test the query to see if it will result in a valid layer
255  long numRecs = countRecords( txtSQL->toPlainText() );
256  if ( numRecs == -1 )
257  {
258  // error shown in countRecords
259  }
260  else if ( numRecs == 0 )
261  {
262  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
263  }
264  else
265  {
266  accept();
267  }
268 
269 }
270 
272 {
273  txtSQL->insertPlainText( " = " );
274 }
275 
277 {
278  txtSQL->insertPlainText( " < " );
279 }
280 
282 {
283  txtSQL->insertPlainText( " > " );
284 }
285 
287 {
288  txtSQL->insertPlainText( "%" );
289 }
290 
292 {
293  txtSQL->insertPlainText( " IN " );
294 }
295 
297 {
298  txtSQL->insertPlainText( " NOT IN " );
299 }
300 
302 {
303  txtSQL->insertPlainText( " LIKE " );
304 }
305 
307 {
308  return txtSQL->toPlainText();
309 }
310 
311 void QgsSearchQueryBuilder::setSearchString( QString searchString )
312 {
313  txtSQL->setPlainText( searchString );
314 }
315 
317 {
318  txtSQL->insertPlainText( mModelFields->data( index ).toString() );
319 }
320 
322 {
323  txtSQL->insertPlainText( mModelValues->data( index ).toString() );
324 }
325 
327 {
328  txtSQL->insertPlainText( " <= " );
329 }
330 
332 {
333  txtSQL->insertPlainText( " >= " );
334 }
335 
337 {
338  txtSQL->insertPlainText( " != " );
339 }
340 
342 {
343  txtSQL->insertPlainText( " AND " );
344 }
345 
347 {
348  txtSQL->insertPlainText( " NOT " );
349 }
350 
352 {
353  txtSQL->insertPlainText( " OR " );
354 }
355 
357 {
358  txtSQL->clear();
359 }
360 
362 {
363  txtSQL->insertPlainText( " ILIKE " );
364 }
365 
367 {
368  QSettings s;
369  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
370  //save as qqt (QGIS query file)
371  QString saveFileName = QFileDialog::getSaveFileName( 0, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" );
372  if ( saveFileName.isNull() )
373  {
374  return;
375  }
376 
377  if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) )
378  {
379  saveFileName += ".qqf";
380  }
381 
382  QFile saveFile( saveFileName );
383  if ( !saveFile.open( QIODevice::WriteOnly ) )
384  {
385  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for writing" ) );
386  return;
387  }
388 
389  QDomDocument xmlDoc;
390  QDomElement queryElem = xmlDoc.createElement( "Query" );
391  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->toPlainText() );
392  queryElem.appendChild( queryTextNode );
393  xmlDoc.appendChild( queryElem );
394 
395  QTextStream fileStream( &saveFile );
396  xmlDoc.save( fileStream, 2 );
397 
398  QFileInfo fi( saveFile );
399  s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() );
400 }
401 
403 {
404  QSettings s;
405  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
406 
407  QString queryFileName = QFileDialog::getOpenFileName( 0, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + "(*.qqf);;" + tr( "All files" ) + "(*)" );
408  if ( queryFileName.isNull() )
409  {
410  return;
411  }
412 
413  QFile queryFile( queryFileName );
414  if ( !queryFile.open( QIODevice::ReadOnly ) )
415  {
416  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for reading" ) );
417  return;
418  }
419  QDomDocument queryDoc;
420  if ( !queryDoc.setContent( &queryFile ) )
421  {
422  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid xml document" ) );
423  return;
424  }
425 
426  QDomElement queryElem = queryDoc.firstChildElement( "Query" );
427  if ( queryElem.isNull() )
428  {
429  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid query document" ) );
430  return;
431  }
432 
433  QString query = queryElem.text();
434 
435  //todo: test if all the attributes are valid
436  QgsSearchString search;
437  if ( !search.setString( query ) )
438  {
439  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorMsg() );
440  return;
441  }
442 
443  QgsSearchTreeNode* searchTree = search.tree();
444  if ( !searchTree )
445  {
446  QMessageBox::critical( this, tr( "Error creating search tree" ), search.parserErrorMsg() );
447  return;
448  }
449 
450  QStringList attributes = searchTree->referencedColumns();
451  QMap< QString, QString> attributesToReplace;
452  QStringList existingAttributes;
453 
454  //get all existing fields
455  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
456  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
457  {
458  existingAttributes.push_back( fieldIt.key() );
459  }
460 
461  //if a field does not exist, ask what field should be used instead
462  QStringList::const_iterator attIt = attributes.constBegin();
463  for ( ; attIt != attributes.constEnd(); ++attIt )
464  {
465  //test if attribute is there
466  if ( !mFieldMap.contains( *attIt ) )
467  {
468  bool ok;
469  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 ),
470  existingAttributes, 0, false, &ok );
471  if ( !ok || replaceAttribute.isEmpty() )
472  {
473  return;
474  }
475  attributesToReplace.insert( *attIt, replaceAttribute );
476  }
477  }
478 
479  //Now replace all the string in the query
480  QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes();
481  QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin();
482  for ( ; columnIt != columnRefList.end(); ++columnIt )
483  {
484  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() );
485  if ( replaceIt != attributesToReplace.constEnd() )
486  {
487  ( *columnIt )->setColumnRef( replaceIt.value() );
488  }
489  }
490 
491  txtSQL->clear();
492  QString newQueryText = query;
493  if ( attributesToReplace.size() > 0 )
494  {
495  newQueryText = searchTree->makeSearchString();
496  }
497  txtSQL->insertPlainText( newQueryText );
498 }
499