QGIS API Documentation
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", QDir::homePath() ).toString();
369  //save as qqt (QGIS query file)
370  QString saveFileName = QFileDialog::getSaveFileName( nullptr, 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( nullptr, 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", QDir::homePath() ).toString();
405 
406  QString queryFileName = QFileDialog::getOpenFileName( nullptr, 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( nullptr, tr( "Error" ), tr( "Could not open file for reading" ) );
416  return;
417  }
418  QDomDocument queryDoc;
419  if ( !queryDoc.setContent( &queryFile ) )
420  {
421  QMessageBox::critical( nullptr, 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( nullptr, 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").
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 setupUi(QWidget *widget)
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
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)
QString name() const
Get the display name of the layer.
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.
#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:187
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()
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()
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
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
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:365
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)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:385
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:271
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
QString evalErrorString() const
Returns evaluation error.
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)
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:89
const T value(const Key &key) const
QString searchString()
returns newly created search string