QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties 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 
16 #include <QDomDocument>
17 #include <QDomElement>
18 #include <QFileDialog>
19 #include <QFileInfo>
20 #include <QInputDialog>
21 #include <QListView>
22 #include <QMessageBox>
23 #include <QSettings>
24 #include <QStandardItem>
25 #include <QTextStream>
26 #include "qgsfeature.h"
27 #include "qgsfield.h"
28 #include "qgssearchquerybuilder.h"
29 #include "qgsexpression.h"
30 #include "qgsvectorlayer.h"
31 #include "qgslogger.h"
32 
34  QWidget *parent, Qt::WindowFlags fl )
35  : QDialog( parent, fl ), mLayer( layer )
36 {
37  setupUi( this );
38  setupListViews();
39 
40  setWindowTitle( tr( "Search query builder" ) );
41 
42  QPushButton *pbn = new QPushButton( tr( "&Test" ) );
43  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
44  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnTest_clicked() ) );
45 
46  pbn = new QPushButton( tr( "&Clear" ) );
47  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
48  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnClear_clicked() ) );
49 
50  pbn = new QPushButton( tr( "&Save..." ) );
51  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
52  pbn->setToolTip( tr( "Save query to an xml file" ) );
53  connect( pbn, SIGNAL( clicked() ), this, SLOT( saveQuery() ) );
54 
55  pbn = new QPushButton( tr( "&Load..." ) );
56  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
57  pbn->setToolTip( tr( "Load query from xml file" ) );
58  connect( pbn, SIGNAL( clicked() ), this, SLOT( loadQuery() ) );
59 
60  if ( layer )
61  lblDataUri->setText( layer->name() );
62  populateFields();
63 }
64 
66 {
67 }
68 
69 
70 void QgsSearchQueryBuilder::populateFields()
71 {
72  if ( !mLayer )
73  return;
74 
75  QgsDebugMsg( "entering." );
76  const QgsFields& fields = mLayer->pendingFields();
77  for ( int idx = 0; idx < fields.count(); ++idx )
78  {
79  QString fieldName = fields[idx].name();
80  mFieldMap[fieldName] = idx;
81  QStandardItem *myItem = new QStandardItem( fieldName );
82  myItem->setEditable( false );
83  mModelFields->insertRow( mModelFields->rowCount(), myItem );
84  }
85 }
86 
87 void QgsSearchQueryBuilder::setupListViews()
88 {
89  QgsDebugMsg( "entering." );
90  //Models
91  mModelFields = new QStandardItemModel();
92  mModelValues = new QStandardItemModel();
93  lstFields->setModel( mModelFields );
94  lstValues->setModel( mModelValues );
95  // Modes
96  lstFields->setViewMode( QListView::ListMode );
97  lstValues->setViewMode( QListView::ListMode );
98  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
99  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
100  // Performance tip since Qt 4.1
101  lstFields->setUniformItemSizes( true );
102  lstValues->setUniformItemSizes( true );
103 }
104 
105 void QgsSearchQueryBuilder::getFieldValues( int limit )
106 {
107  if ( !mLayer )
108  {
109  return;
110  }
111  // clear the values list
112  mModelValues->clear();
113 
114  // determine the field type
115  QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
116  int fieldIndex = mFieldMap[fieldName];
117  QgsField field = mLayer->pendingFields()[fieldIndex];//provider->fields()[fieldIndex];
118  bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double );
119 
120  QgsFeature feat;
121  QString value;
122 
123  QgsAttributeList attrs;
124  attrs.append( fieldIndex );
125 
126  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( attrs ) );
127 
128  lstValues->setCursor( Qt::WaitCursor );
129  // Block for better performance
130  mModelValues->blockSignals( true );
131  lstValues->setUpdatesEnabled( false );
132 
134  QSet<QString> insertedValues;
135 
136  while ( fit.nextFeature( feat ) &&
137  ( limit == 0 || mModelValues->rowCount() != limit ) )
138  {
139  value = feat.attribute( fieldIndex ).toString();
140 
141  if ( !numeric )
142  {
143  // put string in single quotes and escape single quotes in the string
144  value = "'" + value.replace( "'", "''" ) + "'";
145  }
146 
147  // add item only if it's not there already
148  if ( !insertedValues.contains( value ) )
149  {
150  QStandardItem *myItem = new QStandardItem( value );
151  myItem->setEditable( false );
152  mModelValues->insertRow( mModelValues->rowCount(), myItem );
153  insertedValues.insert( value );
154  }
155  }
156  // Unblock for normal use
157  mModelValues->blockSignals( false );
158  lstValues->setUpdatesEnabled( true );
159  // TODO: already sorted, signal emit to refresh model
160  mModelValues->sort( 0 );
161  lstValues->setCursor( Qt::ArrowCursor );
162 }
163 
165 {
166  getFieldValues( 25 );
167 }
168 
170 {
171  getFieldValues( 0 );
172 }
173 
175 {
176  long count = countRecords( txtSQL->text() );
177 
178  // error?
179  if ( count == -1 )
180  return;
181 
182  QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) );
183 }
184 
185 // This method tests the number of records that would be returned
186 long QgsSearchQueryBuilder::countRecords( QString searchString )
187 {
188  QgsExpression search( searchString );
189  if ( search.hasParserError() )
190  {
191  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
192  return -1;
193  }
194 
195  if ( !mLayer )
196  return -1;
197 
198  bool fetchGeom = search.needsGeometry();
199 
200  int count = 0;
201  QgsFeature feat;
202  const QgsFields& fields = mLayer->pendingFields();
203 
204  if ( !search.prepare( fields ) )
205  {
206  QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
207  return -1;
208  }
209 
210  QApplication::setOverrideCursor( Qt::WaitCursor );
211 
213 
214  while ( fit.nextFeature( feat ) )
215  {
216  QVariant value = search.evaluate( &feat );
217  if ( value.toInt() != 0 )
218  {
219  count++;
220  }
221 
222  // check if there were errors during evaulating
223  if ( search.hasEvalError() )
224  break;
225  }
226 
227  QApplication::restoreOverrideCursor();
228 
229  if ( search.hasEvalError() )
230  {
231  QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
232  return -1;
233  }
234 
235  return count;
236 }
237 
238 
240 {
241  // if user hits Ok and there is no query, skip the validation
242  if ( txtSQL->text().trimmed().length() > 0 )
243  {
244  accept();
245  return;
246  }
247 
248  // test the query to see if it will result in a valid layer
249  long numRecs = countRecords( txtSQL->text() );
250  if ( numRecs == -1 )
251  {
252  // error shown in countRecords
253  }
254  else if ( numRecs == 0 )
255  {
256  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
257  }
258  else
259  {
260  accept();
261  }
262 
263 }
264 
266 {
267  txtSQL->insertText( " = " );
268 }
269 
271 {
272  txtSQL->insertText( " < " );
273 }
274 
276 {
277  txtSQL->insertText( " > " );
278 }
279 
281 {
282  txtSQL->insertText( "%" );
283 }
284 
286 {
287  txtSQL->insertText( " IN " );
288 }
289 
291 {
292  txtSQL->insertText( " NOT IN " );
293 }
294 
296 {
297  txtSQL->insertText( " LIKE " );
298 }
299 
301 {
302  return txtSQL->text();
303 }
304 
305 void QgsSearchQueryBuilder::setSearchString( QString searchString )
306 {
307  txtSQL->setText( searchString );
308 }
309 
311 {
312  txtSQL->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
313 }
314 
316 {
317  txtSQL->insertText( mModelValues->data( index ).toString() );
318 }
319 
321 {
322  txtSQL->insertText( " <= " );
323 }
324 
326 {
327  txtSQL->insertText( " >= " );
328 }
329 
331 {
332  txtSQL->insertText( " != " );
333 }
334 
336 {
337  txtSQL->insertText( " AND " );
338 }
339 
341 {
342  txtSQL->insertText( " NOT " );
343 }
344 
346 {
347  txtSQL->insertText( " OR " );
348 }
349 
351 {
352  txtSQL->clear();
353 }
354 
356 {
357  txtSQL->insertText( " ILIKE " );
358 }
359 
361 {
362  QSettings s;
363  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
364  //save as qqt (QGIS query file)
365  QString saveFileName = QFileDialog::getSaveFileName( 0, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" );
366  if ( saveFileName.isNull() )
367  {
368  return;
369  }
370 
371  if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) )
372  {
373  saveFileName += ".qqf";
374  }
375 
376  QFile saveFile( saveFileName );
377  if ( !saveFile.open( QIODevice::WriteOnly ) )
378  {
379  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for writing" ) );
380  return;
381  }
382 
383  QDomDocument xmlDoc;
384  QDomElement queryElem = xmlDoc.createElement( "Query" );
385  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->text() );
386  queryElem.appendChild( queryTextNode );
387  xmlDoc.appendChild( queryElem );
388 
389  QTextStream fileStream( &saveFile );
390  xmlDoc.save( fileStream, 2 );
391 
392  QFileInfo fi( saveFile );
393  s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() );
394 }
395 
397 {
398  QSettings s;
399  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
400 
401  QString queryFileName = QFileDialog::getOpenFileName( 0, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
402  if ( queryFileName.isNull() )
403  {
404  return;
405  }
406 
407  QFile queryFile( queryFileName );
408  if ( !queryFile.open( QIODevice::ReadOnly ) )
409  {
410  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for reading" ) );
411  return;
412  }
413  QDomDocument queryDoc;
414  if ( !queryDoc.setContent( &queryFile ) )
415  {
416  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid xml document" ) );
417  return;
418  }
419 
420  QDomElement queryElem = queryDoc.firstChildElement( "Query" );
421  if ( queryElem.isNull() )
422  {
423  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid query document" ) );
424  return;
425  }
426 
427  QString query = queryElem.text();
428 
429  //todo: test if all the attributes are valid
430  QgsExpression search( query );
431  if ( search.hasParserError() )
432  {
433  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
434  return;
435  }
436 
437  QString newQueryText = query;
438 
439 #if 0
440  // TODO: implement with visitor pattern in QgsExpression
441 
442  QStringList attributes = searchTree->referencedColumns();
443  QMap< QString, QString> attributesToReplace;
444  QStringList existingAttributes;
445 
446  //get all existing fields
447  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
448  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
449  {
450  existingAttributes.push_back( fieldIt.key() );
451  }
452 
453  //if a field does not exist, ask what field should be used instead
454  QStringList::const_iterator attIt = attributes.constBegin();
455  for ( ; attIt != attributes.constEnd(); ++attIt )
456  {
457  //test if attribute is there
458  if ( !mFieldMap.contains( *attIt ) )
459  {
460  bool ok;
461  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 ),
462  existingAttributes, 0, false, &ok );
463  if ( !ok || replaceAttribute.isEmpty() )
464  {
465  return;
466  }
467  attributesToReplace.insert( *attIt, replaceAttribute );
468  }
469  }
470 
471  //Now replace all the string in the query
472  QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes();
473  QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin();
474  for ( ; columnIt != columnRefList.end(); ++columnIt )
475  {
476  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() );
477  if ( replaceIt != attributesToReplace.constEnd() )
478  {
479  ( *columnIt )->setColumnRef( replaceIt.value() );
480  }
481  }
482 
483  if ( attributesToReplace.size() > 0 )
484  {
485  newQueryText = query;
486  }
487 #endif
488 
489  txtSQL->clear();
490  txtSQL->insertText( newQueryText );
491 }
492