QGIS API Documentation  2.11.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules 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 
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 
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 
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:86
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
void setupUi(QWidget *widget)
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:93
bool contains(const Key &key) const
static QString quotedColumnRef(QString name)
return quoted column reference (in double quotes)
QString getItem(QWidget *parent, const QString &title, const QString &label, const QStringList &items, int current, bool editable, bool *ok, QFlags< Qt::WindowType > flags, QFlags< Qt::InputMethodHint > inputMethodHints)
virtual QVariant data(const QModelIndex &index, int role) const
QDomNode appendChild(const QDomNode &newChild)
void push_back(const T &value)
void on_lstFields_doubleClicked(const QModelIndex &index)
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
const_iterator constBegin() const
Container of fields for a vector layer.
Definition: qgsfield.h:173
const_iterator insert(const T &value)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:119
QString tr(const char *sourceText, const char *disambiguation, int n)
StandardButton information(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
bool isNull() const
void clear()
const QString & name() const
Get the display name of the layer.
void setValue(const QString &key, const QVariant &value)
void append(const T &value)
QString text() const
int toInt(bool *ok) const
bool isEmpty() const
const_iterator constEnd() const
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setOverrideCursor(const QCursor &cursor)
void restoreOverrideCursor()
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
int count() const
Return number of items.
Definition: qgsfield.cpp:283
void insertRow(int row, const QList< QStandardItem * > &items)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:38
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
virtual void accept()
QDomText createTextNode(const QString &value)
iterator end()
void on_lstValues_doubleClicked(const QModelIndex &index)
bool blockSignals(bool block)
bool contains(const T &value) const
bool isNull() const
const Key key(const T &value) const
QString & replace(int position, int n, QChar after)
QVariant value(const QString &key, const QVariant &defaultValue) const
void save(QTextStream &str, int indent) const
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:236
void setWindowTitle(const QString &)
QDomElement firstChildElement(const QString &tagName) const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
StandardButton critical(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
virtual int rowCount(const QModelIndex &parent) const
StandardButton warning(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
typedef WindowFlags
iterator insert(const Key &key, const T &value)
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
void setToolTip(const QString &)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
const_iterator constEnd() const
QDomElement createElement(const QString &tagName)
bool nextFeature(QgsFeature &f)
const_iterator constBegin() const
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QString absolutePath() const
virtual void sort(int column, Qt::SortOrder order)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:95
QString toString() const
iterator find(const Key &key)
iterator begin()
int size() const
void setEditable(bool editable)
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=0, Qt::WindowFlags fl=QgisGui::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
char * toString(const QLatin1String &string)
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:74
const T value(const Key &key) const
QString searchString()
returns newly created search string