QGIS API Documentation  2.17.0-Master (eef6f05)
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 )
36  , mLayer( layer )
37 {
38  setupUi( this );
39  setupListViews();
40 
41  setWindowTitle( tr( "Search query builder" ) );
42 
43  QPushButton *pbn = new QPushButton( tr( "&Test" ) );
44  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
45  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnTest_clicked() ) );
46 
47  pbn = new QPushButton( tr( "&Clear" ) );
48  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
49  connect( pbn, SIGNAL( clicked() ), this, SLOT( on_btnClear_clicked() ) );
50 
51  pbn = new QPushButton( tr( "&Save..." ) );
52  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
53  pbn->setToolTip( tr( "Save query to an xml file" ) );
54  connect( pbn, SIGNAL( clicked() ), this, SLOT( saveQuery() ) );
55 
56  pbn = new QPushButton( tr( "&Load..." ) );
57  buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
58  pbn->setToolTip( tr( "Load query from xml file" ) );
59  connect( pbn, SIGNAL( clicked() ), this, SLOT( loadQuery() ) );
60 
61  if ( layer )
62  lblDataUri->setText( layer->name() );
63  populateFields();
64 }
65 
67 {
68 }
69 
70 
71 void QgsSearchQueryBuilder::populateFields()
72 {
73  if ( !mLayer )
74  return;
75 
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  //Models
90  mModelFields = new QStandardItemModel();
91  mModelValues = new QStandardItemModel();
92  lstFields->setModel( mModelFields );
93  lstValues->setModel( mModelValues );
94  // Modes
95  lstFields->setViewMode( QListView::ListMode );
96  lstValues->setViewMode( QListView::ListMode );
97  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
98  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
99  // Performance tip since Qt 4.1
100  lstFields->setUniformItemSizes( true );
101  lstValues->setUniformItemSizes( true );
102 }
103 
104 void QgsSearchQueryBuilder::getFieldValues( int limit )
105 {
106  if ( !mLayer )
107  {
108  return;
109  }
110  // clear the values list
111  mModelValues->clear();
112 
113  // determine the field type
114  QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
115  int fieldIndex = mFieldMap[fieldName];
116  QgsField field = mLayer->fields().at( fieldIndex );//provider->fields().at( fieldIndex );
117  bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double );
118 
119  QgsFeature feat;
120  QString value;
121 
122  QgsAttributeList attrs;
123  attrs.append( fieldIndex );
124 
125  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( attrs ) );
126 
127  lstValues->setCursor( Qt::WaitCursor );
128  // Block for better performance
129  mModelValues->blockSignals( true );
130  lstValues->setUpdatesEnabled( false );
131 
133  QSet<QString> insertedValues;
134 
135  while ( fit.nextFeature( feat ) &&
136  ( limit == 0 || mModelValues->rowCount() != limit ) )
137  {
138  value = feat.attribute( fieldIndex ).toString();
139 
140  if ( !numeric )
141  {
142  // put string in single quotes and escape single quotes in the string
143  value = '\'' + value.replace( '\'', "''" ) + '\'';
144  }
145 
146  // add item only if it's not there already
147  if ( !insertedValues.contains( value ) )
148  {
149  QStandardItem *myItem = new QStandardItem( value );
150  myItem->setEditable( false );
151  mModelValues->insertRow( mModelValues->rowCount(), myItem );
152  insertedValues.insert( value );
153  }
154  }
155  // Unblock for normal use
156  mModelValues->blockSignals( false );
157  lstValues->setUpdatesEnabled( true );
158  // TODO: already sorted, signal emit to refresh model
159  mModelValues->sort( 0 );
160  lstValues->setCursor( Qt::ArrowCursor );
161 }
162 
164 {
165  getFieldValues( 25 );
166 }
167 
169 {
170  getFieldValues( 0 );
171 }
172 
174 {
175  long count = countRecords( txtSQL->text() );
176 
177  // error?
178  if ( count == -1 )
179  return;
180 
181  QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) );
182 }
183 
184 // This method tests the number of records that would be returned
185 long QgsSearchQueryBuilder::countRecords( const QString& searchString )
186 {
187  QgsExpression search( searchString );
188  if ( search.hasParserError() )
189  {
190  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
191  return -1;
192  }
193 
194  if ( !mLayer )
195  return -1;
196 
197  bool fetchGeom = search.needsGeometry();
198 
199  int count = 0;
200  QgsFeature feat;
201 
202  QgsExpressionContext context;
206 
207  if ( !search.prepare( &context ) )
208  {
209  QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
210  return -1;
211  }
212 
213  QApplication::setOverrideCursor( Qt::WaitCursor );
214 
216 
217  while ( fit.nextFeature( feat ) )
218  {
219  context.setFeature( feat );
220  QVariant value = search.evaluate( &context );
221  if ( value.toInt() != 0 )
222  {
223  count++;
224  }
225 
226  // check if there were errors during evaulating
227  if ( search.hasEvalError() )
228  break;
229  }
230 
232 
233  if ( search.hasEvalError() )
234  {
235  QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
236  return -1;
237  }
238 
239  return count;
240 }
241 
242 
244 {
245  // if user hits Ok and there is no query, skip the validation
246  if ( txtSQL->text().trimmed().length() > 0 )
247  {
248  accept();
249  return;
250  }
251 
252  // test the query to see if it will result in a valid layer
253  long numRecs = countRecords( txtSQL->text() );
254  if ( numRecs == -1 )
255  {
256  // error shown in countRecords
257  }
258  else if ( numRecs == 0 )
259  {
260  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
261  }
262  else
263  {
264  accept();
265  }
266 
267 }
268 
270 {
271  txtSQL->insertText( " = " );
272 }
273 
275 {
276  txtSQL->insertText( " < " );
277 }
278 
280 {
281  txtSQL->insertText( " > " );
282 }
283 
285 {
286  txtSQL->insertText( "%" );
287 }
288 
290 {
291  txtSQL->insertText( " IN " );
292 }
293 
295 {
296  txtSQL->insertText( " NOT IN " );
297 }
298 
300 {
301  txtSQL->insertText( " LIKE " );
302 }
303 
305 {
306  return txtSQL->text();
307 }
308 
310 {
311  txtSQL->setText( searchString );
312 }
313 
315 {
316  txtSQL->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
317 }
318 
320 {
321  txtSQL->insertText( mModelValues->data( index ).toString() );
322 }
323 
325 {
326  txtSQL->insertText( " <= " );
327 }
328 
330 {
331  txtSQL->insertText( " >= " );
332 }
333 
335 {
336  txtSQL->insertText( " != " );
337 }
338 
340 {
341  txtSQL->insertText( " AND " );
342 }
343 
345 {
346  txtSQL->insertText( " NOT " );
347 }
348 
350 {
351  txtSQL->insertText( " OR " );
352 }
353 
355 {
356  txtSQL->clear();
357 }
358 
360 {
361  txtSQL->insertText( " ILIKE " );
362 }
363 
365 {
366  QSettings s;
367  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", QDir::homePath() ).toString();
368  //save as qqt (QGIS query file)
369  QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" );
370  if ( saveFileName.isNull() )
371  {
372  return;
373  }
374 
375  if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) )
376  {
377  saveFileName += ".qqf";
378  }
379 
380  QFile saveFile( saveFileName );
381  if ( !saveFile.open( QIODevice::WriteOnly ) )
382  {
383  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for writing" ) );
384  return;
385  }
386 
387  QDomDocument xmlDoc;
388  QDomElement queryElem = xmlDoc.createElement( "Query" );
389  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->text() );
390  queryElem.appendChild( queryTextNode );
391  xmlDoc.appendChild( queryElem );
392 
393  QTextStream fileStream( &saveFile );
394  xmlDoc.save( fileStream, 2 );
395 
396  QFileInfo fi( saveFile );
397  s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() );
398 }
399 
401 {
402  QSettings s;
403  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", QDir::homePath() ).toString();
404 
405  QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
406  if ( queryFileName.isNull() )
407  {
408  return;
409  }
410 
411  QFile queryFile( queryFileName );
412  if ( !queryFile.open( QIODevice::ReadOnly ) )
413  {
414  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for reading" ) );
415  return;
416  }
417  QDomDocument queryDoc;
418  if ( !queryDoc.setContent( &queryFile ) )
419  {
420  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid xml document" ) );
421  return;
422  }
423 
424  QDomElement queryElem = queryDoc.firstChildElement( "Query" );
425  if ( queryElem.isNull() )
426  {
427  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid query document" ) );
428  return;
429  }
430 
431  QString query = queryElem.text();
432 
433  //todo: test if all the attributes are valid
434  QgsExpression search( query );
435  if ( search.hasParserError() )
436  {
437  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
438  return;
439  }
440 
441  QString newQueryText = query;
442 
443 #if 0
444  // TODO: implement with visitor pattern in QgsExpression
445 
446  QStringList attributes = searchTree->referencedColumns();
447  QMap< QString, QString> attributesToReplace;
448  QStringList existingAttributes;
449 
450  //get all existing fields
451  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
452  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
453  {
454  existingAttributes.push_back( fieldIt.key() );
455  }
456 
457  //if a field does not exist, ask what field should be used instead
458  QStringList::const_iterator attIt = attributes.constBegin();
459  for ( ; attIt != attributes.constEnd(); ++attIt )
460  {
461  //test if attribute is there
462  if ( !mFieldMap.contains( *attIt ) )
463  {
464  bool ok;
465  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 ),
466  existingAttributes, 0, false, &ok );
467  if ( !ok || replaceAttribute.isEmpty() )
468  {
469  return;
470  }
471  attributesToReplace.insert( *attIt, replaceAttribute );
472  }
473  }
474 
475  //Now replace all the string in the query
476  QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes();
477  QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin();
478  for ( ; columnIt != columnRefList.end(); ++columnIt )
479  {
480  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() );
481  if ( replaceIt != attributesToReplace.constEnd() )
482  {
483  ( *columnIt )->setColumnRef( replaceIt.value() );
484  }
485  }
486 
487  if ( attributesToReplace.size() > 0 )
488  {
489  newQueryText = query;
490  }
491 #endif
492 
493  txtSQL->clear();
494  txtSQL->insertText( newQueryText );
495 }
496 
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
void setupUi(QWidget *widget)
bool contains(const Key &key) const
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
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)
Q_DECL_DEPRECATED bool prepare(const QgsFields &fields)
Get the expression ready for evaluation - find out column indexes.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
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
QString evalErrorString() const
Returns evaluation error.
Container of fields for a vector layer.
Definition: qgsfield.h:252
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
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:187
QString homePath()
int count() const
Return number of items.
Definition: qgsfield.cpp:402
QString parserErrorString() const
Returns parser error.
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)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:422
bool isNull() const
QgsFields fields() const
Returns the list of fields of this layer.
void clear()
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...
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=nullptr, const Qt::WindowFlags &fl=QgisGui::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
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
void on_btnTest_clicked()
Test the constructed search string to see if it&#39;s correct.
void insertRow(int row, const QList< QStandardItem * > &items)
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
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
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
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
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)
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
Represents a vector layer which manages a vector based data sets.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:97
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:271
QString toString() const
iterator find(const Key &key)
iterator begin()
int size() const
void setEditable(bool editable)
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
const T value(const Key &key) const
QString searchString()
returns newly created search string