26#include <QDomDocument>
29#include <QInputDialog>
39 QWidget *parent, Qt::WindowFlags fl )
41 , mPreviousFieldRow( -1 )
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 );
67 QPushButton *pbn =
new QPushButton( tr(
"&Test" ) );
68 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
71 pbn =
new QPushButton( tr(
"&Clear" ) );
72 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
75 pbn =
new QPushButton( tr(
"&Save…" ) );
76 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
77 pbn->setToolTip( tr(
"Save query to QQF file" ) );
80 pbn =
new QPushButton( tr(
"&Load…" ) );
81 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
82 pbn->setToolTip( tr(
"Load query from QQF file" ) );
90 lstFields->setModel( mModelFields );
94 layerSubsetStringChanged();
96 lblDataUri->setText( tr(
"Set provider filter on %1" ).arg( layer->
name() ) );
97 mTxtSql->setText( mOrigSubsetString );
99 mFilterLineEdit->setShowSearchIcon(
true );
100 mFilterLineEdit->setPlaceholderText( tr(
"Search…" ) );
101 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, &QgsQueryBuilder::onTextChanged );
107 QDialog::showEvent( event );
110void QgsQueryBuilder::setupGuiViews()
113 mModelValues =
new QStandardItemModel();
114 mProxyValues =
new QSortFilterProxyModel();
115 mProxyValues->setSourceModel( mModelValues );
117 lstFields->setViewMode( QListView::ListMode );
118 lstValues->setViewMode( QListView::ListMode );
119 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
120 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
122 lstFields->setUniformItemSizes(
true );
123 lstValues->setUniformItemSizes(
true );
125 lstFields->setAlternatingRowColors(
true );
126 lstValues->setAlternatingRowColors(
true );
127 lstValues->setModel( mProxyValues );
130void QgsQueryBuilder::fillValues(
const QString &field,
int limit )
133 mModelValues->clear();
138 QList<QVariant> values = qgis::setToList( mLayer->
uniqueValues( fieldIndex, limit ) );
139 std::sort( values.begin(), values.end() );
145 const auto constValues = values;
146 for (
const QVariant &var : constValues )
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 )
155 const QVariantList list = var.toList();
156 for (
const QVariant &val : list )
158 if ( !value.isEmpty() )
159 value.append(
", " );
164 value = var.toString();
166 QStandardItem *myItem =
new QStandardItem( value );
167 myItem->setEditable(
false );
168 myItem->setData( var, Qt::UserRole + 1 );
169 mModelValues->insertRow( mModelValues->rowCount(), myItem );
174void QgsQueryBuilder::btnSampleValues_clicked()
176 lstValues->setCursor( Qt::WaitCursor );
178 const QString prevSubsetString = mLayer->
subsetString();
179 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
181 mIgnoreLayerSubsetStringChangedSignal =
true;
191 mIgnoreLayerSubsetStringChangedSignal =
false;
194 lstValues->setCursor( Qt::ArrowCursor );
197void QgsQueryBuilder::btnGetAllValues_clicked()
199 lstValues->setCursor( Qt::WaitCursor );
201 const QString prevSubsetString = mLayer->
subsetString();
202 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
204 mIgnoreLayerSubsetStringChangedSignal =
true;
214 mIgnoreLayerSubsetStringChangedSignal =
false;
217 lstValues->setCursor( Qt::ArrowCursor );
228 const long long featureCount { mLayer->
featureCount() };
230 if ( featureCount < 0 )
232 QMessageBox::warning(
this,
233 tr(
"Query Result" ),
234 tr(
"An error occurred when executing the query, please check the expression syntax." ) );
238 QMessageBox::information(
this,
239 tr(
"Query Result" ),
240 tr(
"The where clause returned %n row(s).",
"returned test rows", featureCount ) );
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' ) ) ) );
253 QMessageBox::warning(
this,
254 tr(
"Query Result" ),
255 tr(
"An error occurred when executing the query." ) );
261 if ( mTxtSql->text() != mOrigSubsetString )
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' ) ) ) );
276 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
294void QgsQueryBuilder::btnEqual_clicked()
296 mTxtSql->insertText( QStringLiteral(
" = " ) );
300void QgsQueryBuilder::btnLessThan_clicked()
302 mTxtSql->insertText( QStringLiteral(
" < " ) );
306void QgsQueryBuilder::btnGreaterThan_clicked()
308 mTxtSql->insertText( QStringLiteral(
" > " ) );
312void QgsQueryBuilder::btnPct_clicked()
314 mTxtSql->insertText( QStringLiteral(
"%" ) );
318void QgsQueryBuilder::btnIn_clicked()
320 mTxtSql->insertText( QStringLiteral(
" IN " ) );
324void QgsQueryBuilder::btnNotIn_clicked()
326 mTxtSql->insertText( QStringLiteral(
" NOT IN " ) );
330void QgsQueryBuilder::btnLike_clicked()
332 mTxtSql->insertText( QStringLiteral(
" LIKE " ) );
338 return mTxtSql->text();
343 mTxtSql->setText( sqlStatement );
346void QgsQueryBuilder::lstFields_clicked(
const QModelIndex &index )
348 if ( mPreviousFieldRow != index.row() )
350 mPreviousFieldRow = index.row();
352 btnSampleValues->setEnabled(
true );
353 btnGetAllValues->setEnabled(
true );
355 mModelValues->clear();
356 mFilterLineEdit->clear();
360void QgsQueryBuilder::lstFields_doubleClicked(
const QModelIndex &index )
366void QgsQueryBuilder::lstValues_doubleClicked(
const QModelIndex &index )
368 const QVariant value = index.data( Qt::UserRole + 1 );
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() );
376 mTxtSql->insertText(
'\'' + value.toString().replace(
'\'', QLatin1String(
"''" ) ) +
'\'' );
381void QgsQueryBuilder::btnLessEqual_clicked()
383 mTxtSql->insertText( QStringLiteral(
" <= " ) );
387void QgsQueryBuilder::btnGreaterEqual_clicked()
389 mTxtSql->insertText( QStringLiteral(
" >= " ) );
393void QgsQueryBuilder::btnNotEqual_clicked()
395 mTxtSql->insertText( QStringLiteral(
" != " ) );
399void QgsQueryBuilder::btnAnd_clicked()
401 mTxtSql->insertText( QStringLiteral(
" AND " ) );
405void QgsQueryBuilder::btnNot_clicked()
407 mTxtSql->insertText( QStringLiteral(
" NOT " ) );
411void QgsQueryBuilder::btnOr_clicked()
413 mTxtSql->insertText( QStringLiteral(
" OR " ) );
417void QgsQueryBuilder::onTextChanged(
const QString &text )
419 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
420 mProxyValues->setFilterWildcard( text );
429void QgsQueryBuilder::btnILike_clicked()
431 mTxtSql->insertText( QStringLiteral(
" ILIKE " ) );
437 lblDataUri->setText( uri );
440void QgsQueryBuilder::showHelp()
442 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#query-builder" ) );
454 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
456 QString saveFileName = QFileDialog::getSaveFileName(
nullptr, tr(
"Save Query to File" ), lastQueryFileDir, tr(
"Query files (*.qqf *.QQF)" ) );
457 if ( saveFileName.isNull() )
462 if ( !saveFileName.endsWith( QLatin1String(
".qqf" ), Qt::CaseInsensitive ) )
464 saveFileName += QLatin1String(
".qqf" );
467 QFile saveFile( saveFileName );
468 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
470 QMessageBox::critical(
nullptr, tr(
"Save Query to File" ), tr(
"Could not open file for writing." ) );
475 QDomElement queryElem = xmlDoc.createElement( QStringLiteral(
"Query" ) );
476 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
477 queryElem.appendChild( queryTextNode );
478 xmlDoc.appendChild( queryElem );
480 QTextStream fileStream( &saveFile );
481 xmlDoc.save( fileStream, 2 );
483 const QFileInfo fi( saveFile );
484 s.
setValue( QStringLiteral(
"/UI/lastQueryFileDir" ), fi.absolutePath() );
494 mTxtSql->insertText( subset );
501 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
503 const QString queryFileName = QFileDialog::getOpenFileName(
nullptr, tr(
"Load Query from File" ), lastQueryFileDir, tr(
"Query files" ) +
" (*.qqf);;" + tr(
"All files" ) +
" (*)" );
504 if ( queryFileName.isNull() )
509 QFile queryFile( queryFileName );
510 if ( !queryFile.open( QIODevice::ReadOnly ) )
512 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"Could not open file for reading." ) );
515 QDomDocument queryDoc;
516 if ( !queryDoc.setContent( &queryFile ) )
518 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid xml document." ) );
522 const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral(
"Query" ) );
523 if ( queryElem.isNull() )
525 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid query document." ) );
529 subset = queryElem.text();
533void QgsQueryBuilder::layerSubsetStringChanged()
535 if ( mIgnoreLayerSubsetStringChangedSignal )
537 mUseUnfilteredLayer->setDisabled( mLayer->
subsetString().isEmpty() );
static QString nullRepresentation()
Returns the string 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...
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
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 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:
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)