QGIS API Documentation  2.4.0-Chugiak
 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 );
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() );
63 }
64 
66 {
67 }
68 
69 
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 
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 
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->toPlainText() );
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->toPlainText().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->toPlainText() );
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->insertPlainText( " = " );
268 }
269 
271 {
272  txtSQL->insertPlainText( " < " );
273 }
274 
276 {
277  txtSQL->insertPlainText( " > " );
278 }
279 
281 {
282  txtSQL->insertPlainText( "%" );
283 }
284 
286 {
287  txtSQL->insertPlainText( " IN " );
288 }
289 
291 {
292  txtSQL->insertPlainText( " NOT IN " );
293 }
294 
296 {
297  txtSQL->insertPlainText( " LIKE " );
298 }
299 
301 {
302  return txtSQL->toPlainText();
303 }
304 
305 void QgsSearchQueryBuilder::setSearchString( QString searchString )
306 {
307  txtSQL->setPlainText( searchString );
308 }
309 
311 {
312  txtSQL->insertPlainText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
313 }
314 
316 {
317  txtSQL->insertPlainText( mModelValues->data( index ).toString() );
318 }
319 
321 {
322  txtSQL->insertPlainText( " <= " );
323 }
324 
326 {
327  txtSQL->insertPlainText( " >= " );
328 }
329 
331 {
332  txtSQL->insertPlainText( " != " );
333 }
334 
336 {
337  txtSQL->insertPlainText( " AND " );
338 }
339 
341 {
342  txtSQL->insertPlainText( " NOT " );
343 }
344 
346 {
347  txtSQL->insertPlainText( " OR " );
348 }
349 
351 {
352  txtSQL->clear();
353 }
354 
356 {
357  txtSQL->insertPlainText( " 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->toPlainText() );
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->insertPlainText( newQueryText );
491 }
492 
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:89
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
void setSearchString(QString searchString)
change search string shown in text field
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:96
static QString quotedColumnRef(QString name)
return quoted column reference (in double quotes)
QStandardItemModel * mModelFields
Model for fields ListView.
QVariant evaluate(const QgsFeature *f=NULL)
Evaluate the feature and return the result.
void on_lstFields_doubleClicked(const QModelIndex &index)
bool prepare(const QgsFields &fields)
Get the expression ready for evaluation - find out column indexes.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
QStandardItemModel * mModelValues
Model for values ListView.
Container of fields for a vector layer.
Definition: qgsfield.h:161
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
const QString & name() const
Get the display name of the layer.
QMap< QString, int > mFieldMap
Map that holds field information, keyed by field name.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
int count() const
Return number of items.
Definition: qgsfield.h:195
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:31
void on_lstValues_doubleClicked(const QModelIndex &index)
bool needsGeometry()
Returns true if the expression uses feature geometry for some computation.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:230
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Represents a vector layer which manages a vector based data sets.
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:98
QString evalErrorString() const
Returns evaluation error.
QgsVectorLayer * mLayer
Layer for which is the query builder opened.
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=0, Qt::WindowFlags fl=QgisGui::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:63
QString searchString()
returns newly created search string
#define tr(sourceText)