QGIS API Documentation  2.12.0-Lyon
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, const 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->fields();
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->fields().at( fieldIndex );//provider->fields().at( 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( const 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 
203  QgsExpressionContext context;
207 
208  if ( !search.prepare( &context ) )
209  {
210  QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
211  return -1;
212  }
213 
214  QApplication::setOverrideCursor( Qt::WaitCursor );
215 
217 
218  while ( fit.nextFeature( feat ) )
219  {
220  context.setFeature( feat );
221  QVariant value = search.evaluate( &context );
222  if ( value.toInt() != 0 )
223  {
224  count++;
225  }
226 
227  // check if there were errors during evaulating
228  if ( search.hasEvalError() )
229  break;
230  }
231 
233 
234  if ( search.hasEvalError() )
235  {
236  QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
237  return -1;
238  }
239 
240  return count;
241 }
242 
243 
245 {
246  // if user hits Ok and there is no query, skip the validation
247  if ( txtSQL->text().trimmed().length() > 0 )
248  {
249  accept();
250  return;
251  }
252 
253  // test the query to see if it will result in a valid layer
254  long numRecs = countRecords( txtSQL->text() );
255  if ( numRecs == -1 )
256  {
257  // error shown in countRecords
258  }
259  else if ( numRecs == 0 )
260  {
261  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
262  }
263  else
264  {
265  accept();
266  }
267 
268 }
269 
271 {
272  txtSQL->insertText( " = " );
273 }
274 
276 {
277  txtSQL->insertText( " < " );
278 }
279 
281 {
282  txtSQL->insertText( " > " );
283 }
284 
286 {
287  txtSQL->insertText( "%" );
288 }
289 
291 {
292  txtSQL->insertText( " IN " );
293 }
294 
296 {
297  txtSQL->insertText( " NOT IN " );
298 }
299 
301 {
302  txtSQL->insertText( " LIKE " );
303 }
304 
306 {
307  return txtSQL->text();
308 }
309 
311 {
312  txtSQL->setText( searchString );
313 }
314 
316 {
317  txtSQL->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
318 }
319 
321 {
322  txtSQL->insertText( mModelValues->data( index ).toString() );
323 }
324 
326 {
327  txtSQL->insertText( " <= " );
328 }
329 
331 {
332  txtSQL->insertText( " >= " );
333 }
334 
336 {
337  txtSQL->insertText( " != " );
338 }
339 
341 {
342  txtSQL->insertText( " AND " );
343 }
344 
346 {
347  txtSQL->insertText( " NOT " );
348 }
349 
351 {
352  txtSQL->insertText( " OR " );
353 }
354 
356 {
357  txtSQL->clear();
358 }
359 
361 {
362  txtSQL->insertText( " ILIKE " );
363 }
364 
366 {
367  QSettings s;
368  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
369  //save as qqt (QGIS query file)
370  QString saveFileName = QFileDialog::getSaveFileName( 0, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" );
371  if ( saveFileName.isNull() )
372  {
373  return;
374  }
375 
376  if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) )
377  {
378  saveFileName += ".qqf";
379  }
380 
381  QFile saveFile( saveFileName );
382  if ( !saveFile.open( QIODevice::WriteOnly ) )
383  {
384  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for writing" ) );
385  return;
386  }
387 
388  QDomDocument xmlDoc;
389  QDomElement queryElem = xmlDoc.createElement( "Query" );
390  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->text() );
391  queryElem.appendChild( queryTextNode );
392  xmlDoc.appendChild( queryElem );
393 
394  QTextStream fileStream( &saveFile );
395  xmlDoc.save( fileStream, 2 );
396 
397  QFileInfo fi( saveFile );
398  s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() );
399 }
400 
402 {
403  QSettings s;
404  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", "" ).toString();
405 
406  QString queryFileName = QFileDialog::getOpenFileName( 0, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
407  if ( queryFileName.isNull() )
408  {
409  return;
410  }
411 
412  QFile queryFile( queryFileName );
413  if ( !queryFile.open( QIODevice::ReadOnly ) )
414  {
415  QMessageBox::critical( 0, tr( "Error" ), tr( "Could not open file for reading" ) );
416  return;
417  }
418  QDomDocument queryDoc;
419  if ( !queryDoc.setContent( &queryFile ) )
420  {
421  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid xml document" ) );
422  return;
423  }
424 
425  QDomElement queryElem = queryDoc.firstChildElement( "Query" );
426  if ( queryElem.isNull() )
427  {
428  QMessageBox::critical( 0, tr( "Error" ), tr( "File is not a valid query document" ) );
429  return;
430  }
431 
432  QString query = queryElem.text();
433 
434  //todo: test if all the attributes are valid
435  QgsExpression search( query );
436  if ( search.hasParserError() )
437  {
438  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
439  return;
440  }
441 
442  QString newQueryText = query;
443 
444 #if 0
445  // TODO: implement with visitor pattern in QgsExpression
446 
447  QStringList attributes = searchTree->referencedColumns();
448  QMap< QString, QString> attributesToReplace;
449  QStringList existingAttributes;
450 
451  //get all existing fields
452  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
453  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
454  {
455  existingAttributes.push_back( fieldIt.key() );
456  }
457 
458  //if a field does not exist, ask what field should be used instead
459  QStringList::const_iterator attIt = attributes.constBegin();
460  for ( ; attIt != attributes.constEnd(); ++attIt )
461  {
462  //test if attribute is there
463  if ( !mFieldMap.contains( *attIt ) )
464  {
465  bool ok;
466  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 ),
467  existingAttributes, 0, false, &ok );
468  if ( !ok || replaceAttribute.isEmpty() )
469  {
470  return;
471  }
472  attributesToReplace.insert( *attIt, replaceAttribute );
473  }
474  }
475 
476  //Now replace all the string in the query
477  QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes();
478  QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin();
479  for ( ; columnIt != columnRefList.end(); ++columnIt )
480  {
481  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() );
482  if ( replaceIt != attributesToReplace.constEnd() )
483  {
484  ( *columnIt )->setColumnRef( replaceIt.value() );
485  }
486  }
487 
488  if ( attributesToReplace.size() > 0 )
489  {
490  newQueryText = query;
491  }
492 #endif
493 
494  txtSQL->clear();
495  txtSQL->insertText( newQueryText );
496 }
497 
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:92
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
void setupUi(QWidget *widget)
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=0, const Qt::WindowFlags &fl=QgisGui::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
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)
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsFields fields() const
Returns the list of fields of this layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
void setSearchString(const QString &searchString)
change search string shown in text field
const_iterator constBegin() const
Container of fields for a vector layer.
Definition: qgsfield.h:177
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:176
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)
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QString text() const
int toInt(bool *ok) const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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:311
void on_btnTest_clicked()
Test the constructed search string to see if it's correct.
void insertRow(int row, const QList< QStandardItem * > &items)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:40
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:331
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:238
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)
void setToolTip(const QString &)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
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.
QString toString() const
iterator find(const Key &key)
iterator begin()
int size() const
void setEditable(bool editable)
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:77
const T value(const Key &key) const
QString searchString()
returns newly created search string