27#include <QDomDocument>
30#include <QInputDialog>
36#include "moc_qgsquerybuilder.cpp"
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" ) );
89 mModelFields->sourceFieldModel()->setLayer( layer );
90 lstFields->setModel( mModelFields );
94 layerSubsetStringChanged();
96 QString subsetStringDialect;
97 QString subsetStringHelpUrl;
101 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), provider->name() ) );
102 subsetStringDialect = provider->subsetStringDialect();
103 subsetStringHelpUrl = provider->subsetStringHelpUrl();
107 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), layer->
providerType() ) );
110 if ( !subsetStringDialect.isEmpty() && !subsetStringHelpUrl.isEmpty() )
112 lblProviderFilterInfo->setOpenExternalLinks(
true );
113 lblProviderFilterInfo->setText( tr(
"Enter a <a href=\"%1\">%2</a> to filter the layer" ).arg( subsetStringHelpUrl ).arg( subsetStringDialect ) );
115 else if ( !subsetStringDialect.isEmpty() )
117 lblProviderFilterInfo->setText( tr(
"Enter a %1 to filter the layer" ).arg( subsetStringDialect ) );
121 lblProviderFilterInfo->hide();
124 mTxtSql->setText( mOrigSubsetString );
126 mFilterLineEdit->setShowSearchIcon(
true );
127 mFilterLineEdit->setPlaceholderText( tr(
"Search…" ) );
128 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, &QgsQueryBuilder::onTextChanged );
134 QDialog::showEvent( event );
137void QgsQueryBuilder::setupGuiViews()
140 mModelValues =
new QStandardItemModel();
141 mProxyValues =
new QSortFilterProxyModel();
142 mProxyValues->setSourceModel( mModelValues );
144 lstFields->setViewMode( QListView::ListMode );
145 lstValues->setViewMode( QListView::ListMode );
146 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
147 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
149 lstFields->setUniformItemSizes(
true );
150 lstValues->setUniformItemSizes(
true );
152 lstFields->setAlternatingRowColors(
true );
153 lstValues->setAlternatingRowColors(
true );
154 lstValues->setModel( mProxyValues );
157void QgsQueryBuilder::fillValues(
const QString &field,
int limit )
160 mModelValues->clear();
162 const int fieldIndex = mLayer->fields().lookupField( field );
165 QList<QVariant> values = qgis::setToList( mLayer->uniqueValues( fieldIndex, limit ) );
166 std::sort( values.begin(), values.end() );
172 const auto constValues = values;
173 for (
const QVariant &var : constValues )
178 else if ( var.userType() == QMetaType::Type::QDate && mLayer->providerType() == QLatin1String(
"ogr" ) && mLayer->storageType() == QLatin1String(
"ESRI Shapefile" ) )
179 value = var.toDate().toString( QStringLiteral(
"yyyy/MM/dd" ) );
180 else if ( var.userType() == QMetaType::Type::QVariantList || var.userType() == QMetaType::Type::QStringList )
182 const QVariantList list = var.toList();
183 for (
const QVariant &val : list )
185 if ( !value.isEmpty() )
186 value.append(
", " );
191 value = var.toString();
193 QStandardItem *myItem =
new QStandardItem( value );
194 myItem->setEditable(
false );
195 myItem->setData( var, Qt::UserRole + 1 );
196 mModelValues->insertRow( mModelValues->rowCount(), myItem );
201void QgsQueryBuilder::btnSampleValues_clicked()
203 lstValues->setCursor( Qt::WaitCursor );
205 const QString prevSubsetString = mLayer->subsetString();
206 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
208 mIgnoreLayerSubsetStringChangedSignal =
true;
209 mLayer->setSubsetString( QString() );
215 if ( prevSubsetString != mLayer->subsetString() )
217 mLayer->setSubsetString( prevSubsetString );
218 mIgnoreLayerSubsetStringChangedSignal =
false;
221 lstValues->setCursor( Qt::ArrowCursor );
224void QgsQueryBuilder::btnGetAllValues_clicked()
226 lstValues->setCursor( Qt::WaitCursor );
228 const QString prevSubsetString = mLayer->subsetString();
229 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
231 mIgnoreLayerSubsetStringChangedSignal =
true;
232 mLayer->setSubsetString( QString() );
238 if ( prevSubsetString != mLayer->subsetString() )
240 mLayer->setSubsetString( prevSubsetString );
241 mIgnoreLayerSubsetStringChangedSignal =
false;
244 lstValues->setCursor( Qt::ArrowCursor );
253 if ( mLayer->setSubsetString( mTxtSql->text() ) )
255 const long long featureCount { mLayer->featureCount() };
257 if ( featureCount < 0 )
259 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query, please check the expression syntax." ) );
263 QMessageBox::information(
this, tr(
"Query Result" ), tr(
"The where clause returned %n row(s).",
"returned test rows", featureCount ) );
266 else if ( mLayer->dataProvider()->hasErrors() )
268 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query." ) + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char(
'\n' ) ) ) );
269 mLayer->dataProvider()->clearErrors();
273 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query." ) );
279 if ( mTxtSql->text() != mOrigSubsetString )
281 if ( !mLayer->setSubsetString( mTxtSql->text() ) )
284 if ( mLayer->dataProvider()->hasErrors() )
286 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query." ) + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->dataProvider()->errors().join( QLatin1Char(
'\n' ) ) ) );
287 mLayer->dataProvider()->clearErrors();
291 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
303 if ( mLayer->subsetString() != mOrigSubsetString )
304 mLayer->setSubsetString( mOrigSubsetString );
309void QgsQueryBuilder::btnEqual_clicked()
311 mTxtSql->insertText( QStringLiteral(
" = " ) );
315void QgsQueryBuilder::btnLessThan_clicked()
317 mTxtSql->insertText( QStringLiteral(
" < " ) );
321void QgsQueryBuilder::btnGreaterThan_clicked()
323 mTxtSql->insertText( QStringLiteral(
" > " ) );
327void QgsQueryBuilder::btnPct_clicked()
329 mTxtSql->insertText( QStringLiteral(
"%" ) );
333void QgsQueryBuilder::btnIn_clicked()
335 mTxtSql->insertText( QStringLiteral(
" IN " ) );
339void QgsQueryBuilder::btnNotIn_clicked()
341 mTxtSql->insertText( QStringLiteral(
" NOT IN " ) );
345void QgsQueryBuilder::btnLike_clicked()
347 mTxtSql->insertText( QStringLiteral(
" LIKE " ) );
353 return mTxtSql->text();
358 mTxtSql->setText( sqlStatement );
361void QgsQueryBuilder::lstFields_clicked(
const QModelIndex &index )
363 if ( mPreviousFieldRow != index.row() )
365 mPreviousFieldRow = index.row();
367 btnSampleValues->setEnabled(
true );
368 btnGetAllValues->setEnabled(
true );
370 mModelValues->clear();
371 mFilterLineEdit->clear();
375void QgsQueryBuilder::lstFields_doubleClicked(
const QModelIndex &index )
381void QgsQueryBuilder::lstValues_doubleClicked(
const QModelIndex &index )
383 const QVariant value = index.data( Qt::UserRole + 1 );
385 mTxtSql->insertText( QStringLiteral(
"NULL" ) );
386 else if ( value.userType() == QMetaType::Type::QDate && mLayer->providerType() == QLatin1String(
"ogr" ) && mLayer->storageType() == QLatin1String(
"ESRI Shapefile" ) )
387 mTxtSql->insertText(
'\'' + value.toDate().toString( QStringLiteral(
"yyyy/MM/dd" ) ) +
'\'' );
388 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
389 mTxtSql->insertText( value.toString() );
391 mTxtSql->insertText(
'\'' + value.toString().replace(
'\'', QLatin1String(
"''" ) ) +
'\'' );
396void QgsQueryBuilder::btnLessEqual_clicked()
398 mTxtSql->insertText( QStringLiteral(
" <= " ) );
402void QgsQueryBuilder::btnGreaterEqual_clicked()
404 mTxtSql->insertText( QStringLiteral(
" >= " ) );
408void QgsQueryBuilder::btnNotEqual_clicked()
410 mTxtSql->insertText( QStringLiteral(
" != " ) );
414void QgsQueryBuilder::btnAnd_clicked()
416 mTxtSql->insertText( QStringLiteral(
" AND " ) );
420void QgsQueryBuilder::btnNot_clicked()
422 mTxtSql->insertText( QStringLiteral(
" NOT " ) );
426void QgsQueryBuilder::btnOr_clicked()
428 mTxtSql->insertText( QStringLiteral(
" OR " ) );
432void QgsQueryBuilder::onTextChanged(
const QString &text )
434 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
435 mProxyValues->setFilterWildcard( text );
441 mLayer->setSubsetString( QString() );
444void QgsQueryBuilder::btnILike_clicked()
446 mTxtSql->insertText( QStringLiteral(
" ILIKE " ) );
452 lblDataUri->setText( uri );
455void QgsQueryBuilder::showHelp()
457 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#query-builder" ) );
469 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
471 QString saveFileName = QFileDialog::getSaveFileName(
nullptr, tr(
"Save Query to File" ), lastQueryFileDir, tr(
"Query files (*.qqf *.QQF)" ) );
472 if ( saveFileName.isNull() )
477 if ( !saveFileName.endsWith( QLatin1String(
".qqf" ), Qt::CaseInsensitive ) )
479 saveFileName += QLatin1String(
".qqf" );
482 QFile saveFile( saveFileName );
483 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
485 QMessageBox::critical(
nullptr, tr(
"Save Query to File" ), tr(
"Could not open file for writing." ) );
490 QDomElement queryElem = xmlDoc.createElement( QStringLiteral(
"Query" ) );
491 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
492 queryElem.appendChild( queryTextNode );
493 xmlDoc.appendChild( queryElem );
495 QTextStream fileStream( &saveFile );
496 xmlDoc.save( fileStream, 2 );
498 const QFileInfo fi( saveFile );
499 s.
setValue( QStringLiteral(
"/UI/lastQueryFileDir" ), fi.absolutePath() );
509 mTxtSql->insertText( subset );
516 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
518 const QString queryFileName = QFileDialog::getOpenFileName(
nullptr, tr(
"Load Query from File" ), lastQueryFileDir, tr(
"Query files" ) +
" (*.qqf);;" + tr(
"All files" ) +
" (*)" );
519 if ( queryFileName.isNull() )
524 QFile queryFile( queryFileName );
525 if ( !queryFile.open( QIODevice::ReadOnly ) )
527 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"Could not open file for reading." ) );
530 QDomDocument queryDoc;
531 if ( !queryDoc.setContent( &queryFile ) )
533 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid xml document." ) );
537 const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral(
"Query" ) );
538 if ( queryElem.isNull() )
540 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid query document." ) );
544 subset = queryElem.text();
548void QgsQueryBuilder::layerSubsetStringChanged()
550 if ( mIgnoreLayerSubsetStringChangedSignal )
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
Abstract base class for spatial data provider implementations.
@ FieldName
Return field name if index corresponds to a field.
A proxy model to filter the list of fields of a layer.
@ AllTypes
All field types.
@ OriginProvider
Fields with a provider origin, since QGIS 3.38.
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.
Stores settings for use within QGIS.
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.
QgsSubsetStringEditorInterface(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
bool isSqlQuery() const
Returns true if the layer is a query (SQL) layer.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
#define QgsDebugMsgLevel(str, level)