QGIS API Documentation 3.37.0-Master (614ebf5f6b2)
Loading...
Searching...
No Matches
qgsquerybuilder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsquerybuilder.cpp - Query Builder
3 --------------------------------------
4 Date : 2004-11-19
5 Copyright : (C) 2004 by Gary E.Sherman
6 Email : sherman at mrcc.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#include "qgsquerybuilder.h"
16#include "qgslogger.h"
17#include "qgssettings.h"
18#include "qgsvectorlayer.h"
20#include "qgsapplication.h"
21#include "qgshelp.h"
22#include "qgsgui.h"
23#include "qgsfieldproxymodel.h"
24#include "qgsfieldmodel.h"
25
26#include <QDomDocument>
27#include <QDomElement>
28#include <QFileDialog>
29#include <QInputDialog>
30#include <QListView>
31#include <QMessageBox>
32#include <QPushButton>
33#include <QTextStream>
34
35
36// constructor used when the query builder must make its own
37// connection to the database
39 QWidget *parent, Qt::WindowFlags fl )
40 : QgsSubsetStringEditorInterface( parent, fl )
41 , mPreviousFieldRow( -1 )
42 , mLayer( layer )
43{
44 setupUi( this );
46 connect( btnEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnEqual_clicked );
47 connect( btnLessThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessThan_clicked );
48 connect( btnGreaterThan, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterThan_clicked );
49 connect( btnPct, &QPushButton::clicked, this, &QgsQueryBuilder::btnPct_clicked );
50 connect( btnIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnIn_clicked );
51 connect( btnNotIn, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotIn_clicked );
52 connect( btnLike, &QPushButton::clicked, this, &QgsQueryBuilder::btnLike_clicked );
53 connect( btnILike, &QPushButton::clicked, this, &QgsQueryBuilder::btnILike_clicked );
54 connect( lstFields, &QListView::clicked, this, &QgsQueryBuilder::lstFields_clicked );
55 connect( lstFields, &QListView::doubleClicked, this, &QgsQueryBuilder::lstFields_doubleClicked );
56 connect( lstValues, &QListView::doubleClicked, this, &QgsQueryBuilder::lstValues_doubleClicked );
57 connect( btnLessEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnLessEqual_clicked );
58 connect( btnGreaterEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnGreaterEqual_clicked );
59 connect( btnNotEqual, &QPushButton::clicked, this, &QgsQueryBuilder::btnNotEqual_clicked );
60 connect( btnAnd, &QPushButton::clicked, this, &QgsQueryBuilder::btnAnd_clicked );
61 connect( btnNot, &QPushButton::clicked, this, &QgsQueryBuilder::btnNot_clicked );
62 connect( btnOr, &QPushButton::clicked, this, &QgsQueryBuilder::btnOr_clicked );
63 connect( btnGetAllValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnGetAllValues_clicked );
64 connect( btnSampleValues, &QPushButton::clicked, this, &QgsQueryBuilder::btnSampleValues_clicked );
65 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsQueryBuilder::showHelp );
66
67 QPushButton *pbn = new QPushButton( tr( "&Test" ) );
68 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
69 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::test );
70
71 pbn = new QPushButton( tr( "&Clear" ) );
72 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
73 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::clear );
74
75 pbn = new QPushButton( tr( "&Save…" ) );
76 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
77 pbn->setToolTip( tr( "Save query to QQF file" ) );
78 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::saveQuery );
79
80 pbn = new QPushButton( tr( "&Load…" ) );
81 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
82 pbn->setToolTip( tr( "Load query from QQF file" ) );
83 connect( pbn, &QAbstractButton::clicked, this, &QgsQueryBuilder::loadQuery );
84
85 setupGuiViews();
86
87 mModelFields = new QgsFieldProxyModel();
89 mModelFields->sourceFieldModel()->setLayer( layer );
90 lstFields->setModel( mModelFields );
91
92 mOrigSubsetString = layer->subsetString();
93 connect( layer, &QgsVectorLayer::subsetStringChanged, this, &QgsQueryBuilder::layerSubsetStringChanged );
94 layerSubsetStringChanged();
95
96 lblDataUri->setText( tr( "Set provider filter on %1" ).arg( layer->name() ) );
97 mTxtSql->setText( mOrigSubsetString );
98
99 mFilterLineEdit->setShowSearchIcon( true );
100 mFilterLineEdit->setPlaceholderText( tr( "Search…" ) );
101 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsQueryBuilder::onTextChanged );
102}
103
104void QgsQueryBuilder::showEvent( QShowEvent *event )
105{
106 mTxtSql->setFocus();
107 QDialog::showEvent( event );
108}
109
110void QgsQueryBuilder::setupGuiViews()
111{
112 //Initialize the models
113 mModelValues = new QStandardItemModel();
114 mProxyValues = new QSortFilterProxyModel();
115 mProxyValues->setSourceModel( mModelValues );
116 // Modes
117 lstFields->setViewMode( QListView::ListMode );
118 lstValues->setViewMode( QListView::ListMode );
119 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
120 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
121 // Performance tip since Qt 4.1
122 lstFields->setUniformItemSizes( true );
123 lstValues->setUniformItemSizes( true );
124 // Colored rows
125 lstFields->setAlternatingRowColors( true );
126 lstValues->setAlternatingRowColors( true );
127 lstValues->setModel( mProxyValues );
128}
129
130void QgsQueryBuilder::fillValues( const QString &field, int limit )
131{
132 // clear the model
133 mModelValues->clear();
134
135 const int fieldIndex = mLayer->fields().lookupField( field );
136
137 // determine the field type
138 QList<QVariant> values = qgis::setToList( mLayer->uniqueValues( fieldIndex, limit ) );
139 std::sort( values.begin(), values.end() );
140
141 const QString nullValue = QgsApplication::nullRepresentation();
142
143 QgsDebugMsgLevel( QStringLiteral( "nullValue: %1" ).arg( nullValue ), 2 );
144
145 const auto constValues = values;
146 for ( const QVariant &var : constValues )
147 {
148 QString value;
149 if ( QgsVariantUtils::isNull( var ) )
150 value = nullValue;
151 else if ( var.userType() == QMetaType::Type::QDate && mLayer->providerType() == QLatin1String( "ogr" ) && mLayer->storageType() == QLatin1String( "ESRI Shapefile" ) )
152 value = var.toDate().toString( QStringLiteral( "yyyy/MM/dd" ) );
153 else if ( var.userType() == QMetaType::Type::QVariantList || var.userType() == QMetaType::Type::QStringList )
154 {
155 const QVariantList list = var.toList();
156 for ( const QVariant &val : list )
157 {
158 if ( !value.isEmpty() )
159 value.append( ", " );
160 value.append( QgsVariantUtils::isNull( val ) ? nullValue : val.toString() );
161 }
162 }
163 else
164 value = var.toString();
165
166 QStandardItem *myItem = new QStandardItem( value );
167 myItem->setEditable( false );
168 myItem->setData( var, Qt::UserRole + 1 );
169 mModelValues->insertRow( mModelValues->rowCount(), myItem );
170 QgsDebugMsgLevel( QStringLiteral( "Value is null: %1\nvalue: %2" ).arg( QgsVariantUtils::isNull( var ) ).arg( QgsVariantUtils::isNull( var ) ? nullValue : var.toString() ), 2 );
171 }
172}
173
174void QgsQueryBuilder::btnSampleValues_clicked()
175{
176 lstValues->setCursor( Qt::WaitCursor );
177
178 const QString prevSubsetString = mLayer->subsetString();
179 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
180 {
181 mIgnoreLayerSubsetStringChangedSignal = true;
182 mLayer->setSubsetString( QString() );
183 }
184
185 //Clear and fill the mModelValues
186 fillValues( mModelFields->data( lstFields->currentIndex(), static_cast< int >( QgsFieldModel::CustomRole::FieldName ) ).toString(), 25 );
187
188 if ( prevSubsetString != mLayer->subsetString() )
189 {
190 mLayer->setSubsetString( prevSubsetString );
191 mIgnoreLayerSubsetStringChangedSignal = false;
192 }
193
194 lstValues->setCursor( Qt::ArrowCursor );
195}
196
197void QgsQueryBuilder::btnGetAllValues_clicked()
198{
199 lstValues->setCursor( Qt::WaitCursor );
200
201 const QString prevSubsetString = mLayer->subsetString();
202 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
203 {
204 mIgnoreLayerSubsetStringChangedSignal = true;
205 mLayer->setSubsetString( QString() );
206 }
207
208 //Clear and fill the mModelValues
209 fillValues( mModelFields->data( lstFields->currentIndex(), static_cast< int >( QgsFieldModel::CustomRole::FieldName ) ).toString(), -1 );
210
211 if ( prevSubsetString != mLayer->subsetString() )
212 {
213 mLayer->setSubsetString( prevSubsetString );
214 mIgnoreLayerSubsetStringChangedSignal = false;
215 }
216
217 lstValues->setCursor( Qt::ArrowCursor );
218}
219
221{
222 // test the sql statement to see if it works
223 // by counting the number of records that would be
224 // returned
225
226 if ( mLayer->setSubsetString( mTxtSql->text() ) )
227 {
228 const long long featureCount { mLayer->featureCount() };
229 // Check for errors
230 if ( featureCount < 0 )
231 {
232 QMessageBox::warning( this,
233 tr( "Query Result" ),
234 tr( "An error occurred when executing the query, please check the expression syntax." ) );
235 }
236 else
237 {
238 QMessageBox::information( this,
239 tr( "Query Result" ),
240 tr( "The where clause returned %n row(s).", "returned test rows", featureCount ) );
241 }
242 }
243 else if ( mLayer->dataProvider()->hasErrors() )
244 {
245 QMessageBox::warning( this,
246 tr( "Query Result" ),
247 tr( "An error occurred when executing the query." )
248 + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
249 mLayer->dataProvider()->clearErrors();
250 }
251 else
252 {
253 QMessageBox::warning( this,
254 tr( "Query Result" ),
255 tr( "An error occurred when executing the query." ) );
256 }
257}
258
260{
261 if ( mTxtSql->text() != mOrigSubsetString )
262 {
263 if ( !mLayer->setSubsetString( mTxtSql->text() ) )
264 {
265 //error in query - show the problem
266 if ( mLayer->dataProvider()->hasErrors() )
267 {
268 QMessageBox::warning( this,
269 tr( "Query Result" ),
270 tr( "An error occurred when executing the query." )
271 + tr( "\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char( '\n' ) ) ) );
272 mLayer->dataProvider()->clearErrors();
273 }
274 else
275 {
276 QMessageBox::warning( this, tr( "Query Result" ), tr( "Error in query. The subset string could not be set." ) );
277 }
278
279 return;
280 }
281 }
282
283 QDialog::accept();
284}
285
287{
288 if ( mLayer->subsetString() != mOrigSubsetString )
289 mLayer->setSubsetString( mOrigSubsetString );
290
291 QDialog::reject();
292}
293
294void QgsQueryBuilder::btnEqual_clicked()
295{
296 mTxtSql->insertText( QStringLiteral( " = " ) );
297 mTxtSql->setFocus();
298}
299
300void QgsQueryBuilder::btnLessThan_clicked()
301{
302 mTxtSql->insertText( QStringLiteral( " < " ) );
303 mTxtSql->setFocus();
304}
305
306void QgsQueryBuilder::btnGreaterThan_clicked()
307{
308 mTxtSql->insertText( QStringLiteral( " > " ) );
309 mTxtSql->setFocus();
310}
311
312void QgsQueryBuilder::btnPct_clicked()
313{
314 mTxtSql->insertText( QStringLiteral( "%" ) );
315 mTxtSql->setFocus();
316}
317
318void QgsQueryBuilder::btnIn_clicked()
319{
320 mTxtSql->insertText( QStringLiteral( " IN " ) );
321 mTxtSql->setFocus();
322}
323
324void QgsQueryBuilder::btnNotIn_clicked()
325{
326 mTxtSql->insertText( QStringLiteral( " NOT IN " ) );
327 mTxtSql->setFocus();
328}
329
330void QgsQueryBuilder::btnLike_clicked()
331{
332 mTxtSql->insertText( QStringLiteral( " LIKE " ) );
333 mTxtSql->setFocus();
334}
335
336QString QgsQueryBuilder::sql() const
337{
338 return mTxtSql->text();
339}
340
341void QgsQueryBuilder::setSql( const QString &sqlStatement )
342{
343 mTxtSql->setText( sqlStatement );
344}
345
346void QgsQueryBuilder::lstFields_clicked( const QModelIndex &index )
347{
348 if ( mPreviousFieldRow != index.row() )
349 {
350 mPreviousFieldRow = index.row();
351
352 btnSampleValues->setEnabled( true );
353 btnGetAllValues->setEnabled( true );
354
355 mModelValues->clear();
356 mFilterLineEdit->clear();
357 }
358}
359
360void QgsQueryBuilder::lstFields_doubleClicked( const QModelIndex &index )
361{
362 mTxtSql->insertText( '\"' + mModelFields->data( index, static_cast< int >( QgsFieldModel::CustomRole::FieldName ) ).toString() + '\"' );
363 mTxtSql->setFocus();
364}
365
366void QgsQueryBuilder::lstValues_doubleClicked( const QModelIndex &index )
367{
368 const QVariant value = index.data( Qt::UserRole + 1 );
369 if ( QgsVariantUtils::isNull( value ) )
370 mTxtSql->insertText( QStringLiteral( "NULL" ) );
371 else if ( value.userType() == QMetaType::Type::QDate && mLayer->providerType() == QLatin1String( "ogr" ) && mLayer->storageType() == QLatin1String( "ESRI Shapefile" ) )
372 mTxtSql->insertText( '\'' + value.toDate().toString( QStringLiteral( "yyyy/MM/dd" ) ) + '\'' );
373 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
374 mTxtSql->insertText( value.toString() );
375 else
376 mTxtSql->insertText( '\'' + value.toString().replace( '\'', QLatin1String( "''" ) ) + '\'' );
377
378 mTxtSql->setFocus();
379}
380
381void QgsQueryBuilder::btnLessEqual_clicked()
382{
383 mTxtSql->insertText( QStringLiteral( " <= " ) );
384 mTxtSql->setFocus();
385}
386
387void QgsQueryBuilder::btnGreaterEqual_clicked()
388{
389 mTxtSql->insertText( QStringLiteral( " >= " ) );
390 mTxtSql->setFocus();
391}
392
393void QgsQueryBuilder::btnNotEqual_clicked()
394{
395 mTxtSql->insertText( QStringLiteral( " != " ) );
396 mTxtSql->setFocus();
397}
398
399void QgsQueryBuilder::btnAnd_clicked()
400{
401 mTxtSql->insertText( QStringLiteral( " AND " ) );
402 mTxtSql->setFocus();
403}
404
405void QgsQueryBuilder::btnNot_clicked()
406{
407 mTxtSql->insertText( QStringLiteral( " NOT " ) );
408 mTxtSql->setFocus();
409}
410
411void QgsQueryBuilder::btnOr_clicked()
412{
413 mTxtSql->insertText( QStringLiteral( " OR " ) );
414 mTxtSql->setFocus();
415}
416
417void QgsQueryBuilder::onTextChanged( const QString &text )
418{
419 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
420 mProxyValues->setFilterWildcard( text );
421}
422
424{
425 mTxtSql->clear();
426 mLayer->setSubsetString( QString() );
427}
428
429void QgsQueryBuilder::btnILike_clicked()
430{
431 mTxtSql->insertText( QStringLiteral( " ILIKE " ) );
432 mTxtSql->setFocus();
433}
434
436{
437 lblDataUri->setText( uri );
438}
439
440void QgsQueryBuilder::showHelp()
441{
442 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#query-builder" ) );
443}
444
446{
447 const bool ok = saveQueryToFile( mTxtSql->text() );
448 Q_UNUSED( ok )
449}
450
451bool QgsQueryBuilder::saveQueryToFile( const QString &subset )
452{
453 QgsSettings s;
454 const QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
455 //save as qqf (QGIS query file)
456 QString saveFileName = QFileDialog::getSaveFileName( nullptr, tr( "Save Query to File" ), lastQueryFileDir, tr( "Query files (*.qqf *.QQF)" ) );
457 if ( saveFileName.isNull() )
458 {
459 return false;
460 }
461
462 if ( !saveFileName.endsWith( QLatin1String( ".qqf" ), Qt::CaseInsensitive ) )
463 {
464 saveFileName += QLatin1String( ".qqf" );
465 }
466
467 QFile saveFile( saveFileName );
468 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
469 {
470 QMessageBox::critical( nullptr, tr( "Save Query to File" ), tr( "Could not open file for writing." ) );
471 return false ;
472 }
473
474 QDomDocument xmlDoc;
475 QDomElement queryElem = xmlDoc.createElement( QStringLiteral( "Query" ) );
476 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
477 queryElem.appendChild( queryTextNode );
478 xmlDoc.appendChild( queryElem );
479
480 QTextStream fileStream( &saveFile );
481 xmlDoc.save( fileStream, 2 );
482
483 const QFileInfo fi( saveFile );
484 s.setValue( QStringLiteral( "/UI/lastQueryFileDir" ), fi.absolutePath() );
485 return true;
486}
487
489{
490 QString subset;
491 if ( loadQueryFromFile( subset ) )
492 {
493 mTxtSql->clear();
494 mTxtSql->insertText( subset );
495 }
496}
497
499{
500 const QgsSettings s;
501 const QString lastQueryFileDir = s.value( QStringLiteral( "/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
502
503 const QString queryFileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Query from File" ), lastQueryFileDir, tr( "Query files" ) + " (*.qqf);;" + tr( "All files" ) + " (*)" );
504 if ( queryFileName.isNull() )
505 {
506 return false;
507 }
508
509 QFile queryFile( queryFileName );
510 if ( !queryFile.open( QIODevice::ReadOnly ) )
511 {
512 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "Could not open file for reading." ) );
513 return false;
514 }
515 QDomDocument queryDoc;
516 if ( !queryDoc.setContent( &queryFile ) )
517 {
518 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid xml document." ) );
519 return false;
520 }
521
522 const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral( "Query" ) );
523 if ( queryElem.isNull() )
524 {
525 QMessageBox::critical( nullptr, tr( "Load Query from File" ), tr( "File is not a valid query document." ) );
526 return false;
527 }
528
529 subset = queryElem.text();
530 return true;
531}
532
533void QgsQueryBuilder::layerSubsetStringChanged()
534{
535 if ( mIgnoreLayerSubsetStringChangedSignal )
536 return;
537 mUseUnfilteredLayer->setDisabled( mLayer->subsetString().isEmpty() );
538}
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
@ FieldName
Return field name if index corresponds to a field.
void setLayer(QgsVectorLayer *layer)
Set the layer from which fields are displayed.
The QgsFieldProxyModel class provides an easy to use model to display the list of fields of a layer.
QgsFieldModel * sourceFieldModel()
Returns the QgsFieldModel used in this QSortFilterProxyModel.
@ AllTypes
All field types.
@ OriginProvider
Fields with a provider origin, since QGIS 3.38.
QgsFieldProxyModel * setFilters(QgsFieldProxyModel::Filters filters)
Set flags that affect how fields are filtered in the model.
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:194
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:39
QString name
Definition qgsmaplayer.h:79
QString providerType() const
Returns the provider type (provider key) for this layer.
static bool loadQueryFromFile(QString &subset)
Load query from the XML file.
void loadQuery()
Load query from the XML file.
void saveQuery()
Save query to the XML file.
void accept() override
void reject() override
void setDatasourceDescription(const QString &uri)
void setSql(const QString &sqlStatement)
Set the sql statement to display in the dialog.
virtual void test()
The default implementation tests that the constructed sql statement to see if the vector layer data p...
static bool saveQueryToFile(const QString &subset)
Save query to the XML file.
void showEvent(QShowEvent *event) override
QgsQueryBuilder(QgsVectorLayer *layer, QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
This constructor is used when the query builder is called from the vector layer properties dialog.
QString sql() const
Returns the sql statement entered in the dialog.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Interface for a dialog that can edit subset strings.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
void clearErrors()
Clear recorded errors.
QStringList errors() const
Gets recorded errors.
bool hasErrors() const
Provider has errors to report.
Represents a vector layer which manages a vector based data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
QString storageType() const
Returns the permanent storage type for this layer as a friendly name.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
virtual bool setSubsetString(const QString &subset)
Sets the string (typically sql) used to define a subset of the layer.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39