QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
17
18#include "qgsexpression.h"
20#include "qgsfeature.h"
21#include "qgsfeatureiterator.h"
22#include "qgsfields.h"
23#include "qgshelp.h"
24#include "qgsquerybuilder.h"
25#include "qgsvectorlayer.h"
26
27#include <QDomDocument>
28#include <QDomElement>
29#include <QFileDialog>
30#include <QFileInfo>
31#include <QInputDialog>
32#include <QListView>
33#include <QMessageBox>
34#include <QStandardItem>
35#include <QTextStream>
36
37#include "moc_qgssearchquerybuilder.cpp"
38
39QgsSearchQueryBuilder::QgsSearchQueryBuilder( QgsVectorLayer *layer, QWidget *parent, Qt::WindowFlags fl )
40 : QDialog( parent, fl )
41 , mLayer( layer )
42{
43 setupUi( this );
44 connect( btnEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnEqual_clicked );
45 connect( btnLessThan, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLessThan_clicked );
46 connect( btnGreaterThan, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGreaterThan_clicked );
47 connect( btnLike, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLike_clicked );
48 connect( btnILike, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnILike_clicked );
49 connect( btnPct, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnPct_clicked );
50 connect( btnIn, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnIn_clicked );
51 connect( btnNotIn, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNotIn_clicked );
52 connect( lstFields, &QListView::doubleClicked, this, &QgsSearchQueryBuilder::lstFields_doubleClicked );
53 connect( lstValues, &QListView::doubleClicked, this, &QgsSearchQueryBuilder::lstValues_doubleClicked );
54 connect( btnLessEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnLessEqual_clicked );
55 connect( btnGreaterEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGreaterEqual_clicked );
56 connect( btnNotEqual, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNotEqual_clicked );
57 connect( btnAnd, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnAnd_clicked );
58 connect( btnNot, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnNot_clicked );
59 connect( btnOr, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnOr_clicked );
60 connect( btnGetAllValues, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnGetAllValues_clicked );
61 connect( btnSampleValues, &QPushButton::clicked, this, &QgsSearchQueryBuilder::btnSampleValues_clicked );
62 setupListViews();
63 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsSearchQueryBuilder::showHelp );
64
65 setWindowTitle( tr( "Search Query Builder" ) );
66
67 QPushButton *pbn = new QPushButton( tr( "&Test" ) );
68 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
69 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::btnTest_clicked );
70
71 pbn = new QPushButton( tr( "&Clear" ) );
72 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
73 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::btnClear_clicked );
74
75 pbn = new QPushButton( tr( "&Save…" ) );
76 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
77 pbn->setToolTip( tr( "Save query to an xml file" ) );
78 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::saveQuery );
79
80 pbn = new QPushButton( tr( "&Load…" ) );
81 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
82 pbn->setToolTip( tr( "Load query from xml file" ) );
83 connect( pbn, &QAbstractButton::clicked, this, &QgsSearchQueryBuilder::loadQuery );
84
85 if ( layer )
86 lblDataUri->setText( layer->name() );
87 populateFields();
88}
89
90void QgsSearchQueryBuilder::populateFields()
91{
92 if ( !mLayer )
93 return;
94
95 const QgsFields &fields = mLayer->fields();
96 for ( int idx = 0; idx < fields.count(); ++idx )
97 {
98 const QString fieldName = fields.at( idx ).name();
99 mFieldMap[fieldName] = idx;
100 QStandardItem *myItem = new QStandardItem( fieldName );
101 myItem->setEditable( false );
102 mModelFields->insertRow( mModelFields->rowCount(), myItem );
103 }
104}
105
106void QgsSearchQueryBuilder::setupListViews()
107{
108 //Models
109 mModelFields = new QStandardItemModel();
110 mModelValues = new QStandardItemModel();
111 lstFields->setModel( mModelFields );
112 lstValues->setModel( mModelValues );
113 // Modes
114 lstFields->setViewMode( QListView::ListMode );
115 lstValues->setViewMode( QListView::ListMode );
116 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
117 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
118 // Performance tip since Qt 4.1
119 lstFields->setUniformItemSizes( true );
120 lstValues->setUniformItemSizes( true );
121}
122
123void QgsSearchQueryBuilder::getFieldValues( int limit )
124{
125 if ( !mLayer )
126 {
127 return;
128 }
129 // clear the values list
130 mModelValues->clear();
131
132 // determine the field type
133 const QString fieldName = mModelFields->data( lstFields->currentIndex() ).toString();
134 const int fieldIndex = mFieldMap[fieldName];
135 const QgsField field = mLayer->fields().at( fieldIndex ); //provider->fields().at( fieldIndex );
136 const bool numeric = ( field.type() == QMetaType::Type::Int || field.type() == QMetaType::Type::Double );
137
138 QgsFeature feat;
139 QString value;
140
141 QgsAttributeList attrs;
142 attrs.append( fieldIndex );
143
144 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setSubsetOfAttributes( attrs ) );
145
146 lstValues->setCursor( Qt::WaitCursor );
147 // Block for better performance
148 mModelValues->blockSignals( true );
149 lstValues->setUpdatesEnabled( false );
150
151 // MH: keep already inserted values in a set. Querying is much faster compared to QStandardItemModel::findItems
152 QSet<QString> insertedValues;
153
154 while ( fit.nextFeature( feat ) && ( limit == 0 || mModelValues->rowCount() != limit ) )
155 {
156 value = feat.attribute( fieldIndex ).toString();
157
158 if ( !numeric )
159 {
160 // put string in single quotes and escape single quotes in the string
161 value = '\'' + value.replace( '\'', QLatin1String( "''" ) ) + '\'';
162 }
163
164 // add item only if it's not there already
165 if ( !insertedValues.contains( value ) )
166 {
167 QStandardItem *myItem = new QStandardItem( value );
168 myItem->setEditable( false );
169 mModelValues->insertRow( mModelValues->rowCount(), myItem );
170 insertedValues.insert( value );
171 }
172 }
173 // Unblock for normal use
174 mModelValues->blockSignals( false );
175 lstValues->setUpdatesEnabled( true );
176 // TODO: already sorted, signal emit to refresh model
177 mModelValues->sort( 0 );
178 lstValues->setCursor( Qt::ArrowCursor );
179}
180
181void QgsSearchQueryBuilder::btnSampleValues_clicked()
182{
183 getFieldValues( 25 );
184}
185
186void QgsSearchQueryBuilder::btnGetAllValues_clicked()
187{
188 getFieldValues( 0 );
189}
190
191void QgsSearchQueryBuilder::btnTest_clicked()
192{
193 const long count = countRecords( mTxtSql->text() );
194
195 // error?
196 if ( count == -1 )
197 return;
198
199 QMessageBox::information( this, tr( "Test Query" ), tr( "Found %n matching feature(s).", "test result", count ) );
200}
201
202// This method tests the number of records that would be returned
203long QgsSearchQueryBuilder::countRecords( const QString &searchString )
204{
205 QgsExpression search( searchString );
206 if ( search.hasParserError() )
207 {
208 QMessageBox::critical( this, tr( "Query Result" ), search.parserErrorString() );
209 return -1;
210 }
211
212 if ( !mLayer )
213 return -1;
214
215 const bool fetchGeom = search.needsGeometry();
216
217 int count = 0;
218 QgsFeature feat;
219
220 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
221
222 if ( !search.prepare( &context ) )
223 {
224 QMessageBox::critical( this, tr( "Query Result" ), search.evalErrorString() );
225 return -1;
226 }
227
228 QApplication::setOverrideCursor( Qt::WaitCursor );
229
230 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFlags( fetchGeom ? Qgis::FeatureRequestFlag::NoFlags : Qgis::FeatureRequestFlag::NoGeometry ) );
231
232 while ( fit.nextFeature( feat ) )
233 {
234 context.setFeature( feat );
235 const QVariant value = search.evaluate( &context );
236 if ( value.toInt() != 0 )
237 {
238 count++;
239 }
240
241 // check if there were errors during evaluating
242 if ( search.hasEvalError() )
243 break;
244 }
245
246 QApplication::restoreOverrideCursor();
247
248 if ( search.hasEvalError() )
249 {
250 QMessageBox::critical( this, tr( "Query Result" ), search.evalErrorString() );
251 return -1;
252 }
253
254 return count;
255}
256
257
258void QgsSearchQueryBuilder::btnOk_clicked()
259{
260 // if user hits OK and there is no query, skip the validation
261 if ( mTxtSql->text().trimmed().length() > 0 )
262 {
263 accept();
264 return;
265 }
266
267 // test the query to see if it will result in a valid layer
268 const long numRecs = countRecords( mTxtSql->text() );
269 if ( numRecs == -1 )
270 {
271 // error shown in countRecords
272 }
273 else if ( numRecs == 0 )
274 {
275 QMessageBox::warning( this, tr( "Query Result" ), tr( "The query you specified results in zero records being returned." ) );
276 }
277 else
278 {
279 accept();
280 }
281}
282
283void QgsSearchQueryBuilder::btnEqual_clicked()
284{
285 mTxtSql->insertText( QStringLiteral( " = " ) );
286}
287
288void QgsSearchQueryBuilder::btnLessThan_clicked()
289{
290 mTxtSql->insertText( QStringLiteral( " < " ) );
291}
292
293void QgsSearchQueryBuilder::btnGreaterThan_clicked()
294{
295 mTxtSql->insertText( QStringLiteral( " > " ) );
296}
297
298void QgsSearchQueryBuilder::btnPct_clicked()
299{
300 mTxtSql->insertText( QStringLiteral( "%" ) );
301}
302
303void QgsSearchQueryBuilder::btnIn_clicked()
304{
305 mTxtSql->insertText( QStringLiteral( " IN " ) );
306}
307
308void QgsSearchQueryBuilder::btnNotIn_clicked()
309{
310 mTxtSql->insertText( QStringLiteral( " NOT IN " ) );
311}
312
313void QgsSearchQueryBuilder::btnLike_clicked()
314{
315 mTxtSql->insertText( QStringLiteral( " LIKE " ) );
316}
317
319{
320 return mTxtSql->text();
321}
322
324{
325 mTxtSql->setText( searchString );
326}
327
328void QgsSearchQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
329{
330 mTxtSql->insertText( QgsExpression::quotedColumnRef( mModelFields->data( index ).toString() ) );
331}
332
333void QgsSearchQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
334{
335 mTxtSql->insertText( mModelValues->data( index ).toString() );
336}
337
338void QgsSearchQueryBuilder::btnLessEqual_clicked()
339{
340 mTxtSql->insertText( QStringLiteral( " <= " ) );
341}
342
343void QgsSearchQueryBuilder::btnGreaterEqual_clicked()
344{
345 mTxtSql->insertText( QStringLiteral( " >= " ) );
346}
347
348void QgsSearchQueryBuilder::btnNotEqual_clicked()
349{
350 mTxtSql->insertText( QStringLiteral( " != " ) );
351}
352
353void QgsSearchQueryBuilder::btnAnd_clicked()
354{
355 mTxtSql->insertText( QStringLiteral( " AND " ) );
356}
357
358void QgsSearchQueryBuilder::btnNot_clicked()
359{
360 mTxtSql->insertText( QStringLiteral( " NOT " ) );
361}
362
363void QgsSearchQueryBuilder::btnOr_clicked()
364{
365 mTxtSql->insertText( QStringLiteral( " OR " ) );
366}
367
368void QgsSearchQueryBuilder::btnClear_clicked()
369{
370 mTxtSql->clear();
371}
372
373void QgsSearchQueryBuilder::btnILike_clicked()
374{
375 mTxtSql->insertText( QStringLiteral( " ILIKE " ) );
376}
377
382
384{
385 QString query;
387 {
388 mTxtSql->clear();
389 mTxtSql->insertText( query );
390 }
391}
392
393void QgsSearchQueryBuilder::showHelp()
394{
395 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#query-builder" ) );
396}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
@ NoFlags
No flags are set.
Definition qgis.h:2195
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes).
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
QMetaType::Type type
Definition qgsfield.h:61
QString name
Definition qgsfield.h:63
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:38
QString name
Definition qgsmaplayer.h:84
static bool loadQueryFromFile(QString &subset)
Load query from the XML file.
static bool saveQueryToFile(const QString &subset)
Save query to the XML file.
QgsSearchQueryBuilder(QgsVectorLayer *layer, QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor - takes pointer to vector layer as a parameter.
void setSearchString(const QString &searchString)
change search string shown in text field
QString searchString()
returns newly created search string
Represents a vector layer which manages a vector based dataset.
QList< int > QgsAttributeList
Definition qgsfield.h:28