QGIS API Documentation  2.15.0-Master (af20121)
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  QgsDebugMsg( "entering." );
77  const QgsFields& fields = mLayer->fields();
78  for ( int idx = 0; idx < fields.count(); ++idx )
79  {
80  QString fieldName = fields[idx].name();
81  mFieldMap[fieldName] = idx;
82  QStandardItem *myItem = new QStandardItem( fieldName );
83  myItem->setEditable( false );
84  mModelFields->insertRow( mModelFields->rowCount(), myItem );
85  }
86 }
87 
88 void QgsSearchQueryBuilder::setupListViews()
89 {
90  QgsDebugMsg( "entering." );
91  //Models
92  mModelFields = new QStandardItemModel();
93  mModelValues = new QStandardItemModel();
94  lstFields->setModel( mModelFields );
95  lstValues->setModel( mModelValues );
96  // Modes
97  lstFields->setViewMode( QListView::ListMode );
98  lstValues->setViewMode( QListView::ListMode );
99  lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
100  lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
101  // Performance tip since Qt 4.1
102  lstFields->setUniformItemSizes( true );
103  lstValues->setUniformItemSizes( true );
104 }
105 
106 void QgsSearchQueryBuilder::getFieldValues( int limit )
107 {
108  if ( !mLayer )
109  {
110  return;
111  }
112  // clear the values list
113  mModelValues->clear();
114 
115  // determine the field type
116  QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
117  int fieldIndex = mFieldMap[fieldName];
118  QgsField field = mLayer->fields().at( fieldIndex );//provider->fields().at( fieldIndex );
119  bool numeric = ( field.type() == QVariant::Int || field.type() == QVariant::Double );
120 
121  QgsFeature feat;
122  QString value;
123 
124  QgsAttributeList attrs;
125  attrs.append( fieldIndex );
126 
127  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( attrs ) );
128 
129  lstValues->setCursor( Qt::WaitCursor );
130  // Block for better performance
131  mModelValues->blockSignals( true );
132  lstValues->setUpdatesEnabled( false );
133 
135  QSet<QString> insertedValues;
136 
137  while ( fit.nextFeature( feat ) &&
138  ( limit == 0 || mModelValues->rowCount() != limit ) )
139  {
140  value = feat.attribute( fieldIndex ).toString();
141 
142  if ( !numeric )
143  {
144  // put string in single quotes and escape single quotes in the string
145  value = '\'' + value.replace( '\'', "''" ) + '\'';
146  }
147 
148  // add item only if it's not there already
149  if ( !insertedValues.contains( value ) )
150  {
151  QStandardItem *myItem = new QStandardItem( value );
152  myItem->setEditable( false );
153  mModelValues->insertRow( mModelValues->rowCount(), myItem );
154  insertedValues.insert( value );
155  }
156  }
157  // Unblock for normal use
158  mModelValues->blockSignals( false );
159  lstValues->setUpdatesEnabled( true );
160  // TODO: already sorted, signal emit to refresh model
161  mModelValues->sort( 0 );
162  lstValues->setCursor( Qt::ArrowCursor );
163 }
164 
166 {
167  getFieldValues( 25 );
168 }
169 
171 {
172  getFieldValues( 0 );
173 }
174 
176 {
177  long count = countRecords( txtSQL->text() );
178 
179  // error?
180  if ( count == -1 )
181  return;
182 
183  QMessageBox::information( this, tr( "Search results" ), tr( "Found %n matching feature(s).", "test result", count ) );
184 }
185 
186 // This method tests the number of records that would be returned
187 long QgsSearchQueryBuilder::countRecords( const QString& searchString )
188 {
189  QgsExpression search( searchString );
190  if ( search.hasParserError() )
191  {
192  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
193  return -1;
194  }
195 
196  if ( !mLayer )
197  return -1;
198 
199  bool fetchGeom = search.needsGeometry();
200 
201  int count = 0;
202  QgsFeature feat;
203 
204  QgsExpressionContext context;
208 
209  if ( !search.prepare( &context ) )
210  {
211  QMessageBox::critical( this, tr( "Evaluation error" ), search.evalErrorString() );
212  return -1;
213  }
214 
215  QApplication::setOverrideCursor( Qt::WaitCursor );
216 
218 
219  while ( fit.nextFeature( feat ) )
220  {
221  context.setFeature( feat );
222  QVariant value = search.evaluate( &context );
223  if ( value.toInt() != 0 )
224  {
225  count++;
226  }
227 
228  // check if there were errors during evaulating
229  if ( search.hasEvalError() )
230  break;
231  }
232 
234 
235  if ( search.hasEvalError() )
236  {
237  QMessageBox::critical( this, tr( "Error during search" ), search.evalErrorString() );
238  return -1;
239  }
240 
241  return count;
242 }
243 
244 
246 {
247  // if user hits Ok and there is no query, skip the validation
248  if ( txtSQL->text().trimmed().length() > 0 )
249  {
250  accept();
251  return;
252  }
253 
254  // test the query to see if it will result in a valid layer
255  long numRecs = countRecords( txtSQL->text() );
256  if ( numRecs == -1 )
257  {
258  // error shown in countRecords
259  }
260  else if ( numRecs == 0 )
261  {
262  QMessageBox::warning( this, tr( "No Records" ), tr( "The query you specified results in zero records being returned." ) );
263  }
264  else
265  {
266  accept();
267  }
268 
269 }
270 
272 {
273  txtSQL->insertText( " = " );
274 }
275 
277 {
278  txtSQL->insertText( " < " );
279 }
280 
282 {
283  txtSQL->insertText( " > " );
284 }
285 
287 {
288  txtSQL->insertText( "%" );
289 }
290 
292 {
293  txtSQL->insertText( " IN " );
294 }
295 
297 {
298  txtSQL->insertText( " NOT IN " );
299 }
300 
302 {
303  txtSQL->insertText( " LIKE " );
304 }
305 
307 {
308  return txtSQL->text();
309 }
310 
312 {
313  txtSQL->setText( searchString );
314 }
315 
317 {
318  txtSQL->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
319 }
320 
322 {
323  txtSQL->insertText( mModelValues->data( index ).toString() );
324 }
325 
327 {
328  txtSQL->insertText( " <= " );
329 }
330 
332 {
333  txtSQL->insertText( " >= " );
334 }
335 
337 {
338  txtSQL->insertText( " != " );
339 }
340 
342 {
343  txtSQL->insertText( " AND " );
344 }
345 
347 {
348  txtSQL->insertText( " NOT " );
349 }
350 
352 {
353  txtSQL->insertText( " OR " );
354 }
355 
357 {
358  txtSQL->clear();
359 }
360 
362 {
363  txtSQL->insertText( " ILIKE " );
364 }
365 
367 {
368  QSettings s;
369  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", QDir::homePath() ).toString();
370  //save as qqt (QGIS query file)
371  QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save query to file" ), lastQueryFileDir, "*.qqf" );
372  if ( saveFileName.isNull() )
373  {
374  return;
375  }
376 
377  if ( !saveFileName.endsWith( ".qqf", Qt::CaseInsensitive ) )
378  {
379  saveFileName += ".qqf";
380  }
381 
382  QFile saveFile( saveFileName );
383  if ( !saveFile.open( QIODevice::WriteOnly ) )
384  {
385  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for writing" ) );
386  return;
387  }
388 
389  QDomDocument xmlDoc;
390  QDomElement queryElem = xmlDoc.createElement( "Query" );
391  QDomText queryTextNode = xmlDoc.createTextNode( txtSQL->text() );
392  queryElem.appendChild( queryTextNode );
393  xmlDoc.appendChild( queryElem );
394 
395  QTextStream fileStream( &saveFile );
396  xmlDoc.save( fileStream, 2 );
397 
398  QFileInfo fi( saveFile );
399  s.setValue( "/UI/lastQueryFileDir", fi.absolutePath() );
400 }
401 
403 {
404  QSettings s;
405  QString lastQueryFileDir = s.value( "/UI/lastQueryFileDir", QDir::homePath() ).toString();
406 
407  QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load query from file" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
408  if ( queryFileName.isNull() )
409  {
410  return;
411  }
412 
413  QFile queryFile( queryFileName );
414  if ( !queryFile.open( QIODevice::ReadOnly ) )
415  {
416  QMessageBox::critical( nullptr, tr( "Error" ), tr( "Could not open file for reading" ) );
417  return;
418  }
419  QDomDocument queryDoc;
420  if ( !queryDoc.setContent( &queryFile ) )
421  {
422  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid xml document" ) );
423  return;
424  }
425 
426  QDomElement queryElem = queryDoc.firstChildElement( "Query" );
427  if ( queryElem.isNull() )
428  {
429  QMessageBox::critical( nullptr, tr( "Error" ), tr( "File is not a valid query document" ) );
430  return;
431  }
432 
433  QString query = queryElem.text();
434 
435  //todo: test if all the attributes are valid
436  QgsExpression search( query );
437  if ( search.hasParserError() )
438  {
439  QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorString() );
440  return;
441  }
442 
443  QString newQueryText = query;
444 
445 #if 0
446  // TODO: implement with visitor pattern in QgsExpression
447 
448  QStringList attributes = searchTree->referencedColumns();
449  QMap< QString, QString> attributesToReplace;
450  QStringList existingAttributes;
451 
452  //get all existing fields
453  QMap<QString, int>::const_iterator fieldIt = mFieldMap.constBegin();
454  for ( ; fieldIt != mFieldMap.constEnd(); ++fieldIt )
455  {
456  existingAttributes.push_back( fieldIt.key() );
457  }
458 
459  //if a field does not exist, ask what field should be used instead
460  QStringList::const_iterator attIt = attributes.constBegin();
461  for ( ; attIt != attributes.constEnd(); ++attIt )
462  {
463  //test if attribute is there
464  if ( !mFieldMap.contains( *attIt ) )
465  {
466  bool ok;
467  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 ),
468  existingAttributes, 0, false, &ok );
469  if ( !ok || replaceAttribute.isEmpty() )
470  {
471  return;
472  }
473  attributesToReplace.insert( *attIt, replaceAttribute );
474  }
475  }
476 
477  //Now replace all the string in the query
478  QList<QgsSearchTreeNode*> columnRefList = searchTree->columnRefNodes();
479  QList<QgsSearchTreeNode*>::iterator columnIt = columnRefList.begin();
480  for ( ; columnIt != columnRefList.end(); ++columnIt )
481  {
482  QMap< QString, QString>::const_iterator replaceIt = attributesToReplace.find(( *columnIt )->columnRef() );
483  if ( replaceIt != attributesToReplace.constEnd() )
484  {
485  ( *columnIt )->setColumnRef( replaceIt.value() );
486  }
487  }
488 
489  if ( attributesToReplace.size() > 0 )
490  {
491  newQueryText = query;
492  }
493 #endif
494 
495  txtSQL->clear();
496  txtSQL->insertText( newQueryText );
497 }
498 
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:193
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