27#include <QDomDocument>
30#include <QInputDialog>
37#include "moc_qgsquerybuilder.cpp"
39using namespace Qt::StringLiterals;
49 connect( btnEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnEqual_clicked );
50 connect( btnLessThan, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLessThan_clicked );
51 connect( btnGreaterThan, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGreaterThan_clicked );
52 connect( btnPct, &QPushButton::clicked,
this, &QgsQueryBuilder::btnPct_clicked );
53 connect( btnIn, &QPushButton::clicked,
this, &QgsQueryBuilder::btnIn_clicked );
54 connect( btnNotIn, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNotIn_clicked );
55 connect( btnLike, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLike_clicked );
56 connect( btnILike, &QPushButton::clicked,
this, &QgsQueryBuilder::btnILike_clicked );
57 connect( lstFields, &QListView::clicked,
this, &QgsQueryBuilder::lstFields_clicked );
58 connect( lstFields, &QListView::doubleClicked,
this, &QgsQueryBuilder::lstFields_doubleClicked );
59 connect( lstValues, &QListView::doubleClicked,
this, &QgsQueryBuilder::lstValues_doubleClicked );
60 connect( btnLessEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLessEqual_clicked );
61 connect( btnGreaterEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGreaterEqual_clicked );
62 connect( btnNotEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNotEqual_clicked );
63 connect( btnAnd, &QPushButton::clicked,
this, &QgsQueryBuilder::btnAnd_clicked );
64 connect( btnNot, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNot_clicked );
65 connect( btnOr, &QPushButton::clicked,
this, &QgsQueryBuilder::btnOr_clicked );
66 connect( btnGetAllValues, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGetAllValues_clicked );
67 connect( btnSampleValues, &QPushButton::clicked,
this, &QgsQueryBuilder::btnSampleValues_clicked );
68 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsQueryBuilder::showHelp );
70 QPushButton *pbn =
new QPushButton( tr(
"&Test" ) );
71 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
74 pbn =
new QPushButton( tr(
"&Clear" ) );
75 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
78 pbn =
new QPushButton( tr(
"&Save…" ) );
79 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
80 pbn->setToolTip( tr(
"Save query to QQF file" ) );
83 pbn =
new QPushButton( tr(
"&Load…" ) );
84 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
85 pbn->setToolTip( tr(
"Load query from QQF file" ) );
92 mModelFields->sourceFieldModel()->setLayer( layer );
93 lstFields->setModel( mModelFields );
97 layerSubsetStringChanged();
99 QString subsetStringDialect;
100 QString subsetStringHelpUrl;
104 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), provider->name() ) );
105 subsetStringDialect = provider->subsetStringDialect();
106 subsetStringHelpUrl = provider->subsetStringHelpUrl();
110 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), layer->
providerType() ) );
113 if ( !subsetStringDialect.isEmpty() && !subsetStringHelpUrl.isEmpty() )
115 lblProviderFilterInfo->setOpenExternalLinks(
true );
116 lblProviderFilterInfo->setText( tr(
"Enter a <a href=\"%1\">%2</a> to filter the layer" ).arg( subsetStringHelpUrl ).arg( subsetStringDialect ) );
118 else if ( !subsetStringDialect.isEmpty() )
120 lblProviderFilterInfo->setText( tr(
"Enter a %1 to filter the layer" ).arg( subsetStringDialect ) );
124 lblProviderFilterInfo->hide();
127 mTxtSql->setText( mOrigSubsetString );
129 mFilterLineEdit->setShowSearchIcon(
true );
130 mFilterLineEdit->setPlaceholderText( tr(
"Search…" ) );
131 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, &QgsQueryBuilder::onTextChanged );
137 QDialog::showEvent( event );
140void QgsQueryBuilder::setupGuiViews()
143 mModelValues =
new QStandardItemModel();
144 mProxyValues =
new QSortFilterProxyModel();
145 mProxyValues->setSourceModel( mModelValues );
147 lstFields->setViewMode( QListView::ListMode );
148 lstValues->setViewMode( QListView::ListMode );
149 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
150 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
152 lstFields->setUniformItemSizes(
true );
153 lstValues->setUniformItemSizes(
true );
155 lstFields->setAlternatingRowColors(
true );
156 lstValues->setAlternatingRowColors(
true );
157 lstValues->setModel( mProxyValues );
160void QgsQueryBuilder::fillValues(
const QString &field,
int limit )
163 mModelValues->clear();
165 const int fieldIndex = mLayer->fields().lookupField( field );
168 QList<QVariant> values = qgis::setToList( mLayer->uniqueValues( fieldIndex, limit ) );
169 std::sort( values.begin(), values.end() );
175 const auto constValues = values;
176 for (
const QVariant &var : constValues )
181 else if ( var.userType() == QMetaType::Type::QDate && mLayer->providerType() ==
"ogr"_L1 && mLayer->storageType() ==
"ESRI Shapefile"_L1 )
182 value = var.toDate().toString( u
"yyyy/MM/dd"_s );
183 else if ( var.userType() == QMetaType::Type::QVariantList || var.userType() == QMetaType::Type::QStringList )
185 const QVariantList list = var.toList();
186 for (
const QVariant &val : list )
188 if ( !value.isEmpty() )
189 value.append(
", " );
194 value = var.toString();
196 QStandardItem *myItem =
new QStandardItem( value );
197 myItem->setEditable(
false );
198 myItem->setData( var, Qt::UserRole + 1 );
199 mModelValues->insertRow( mModelValues->rowCount(), myItem );
204void QgsQueryBuilder::btnSampleValues_clicked()
206 lstValues->setCursor( Qt::WaitCursor );
208 const QString prevSubsetString = mLayer->subsetString();
209 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
211 mIgnoreLayerSubsetStringChangedSignal =
true;
212 mLayer->setSubsetString( QString() );
218 if ( prevSubsetString != mLayer->subsetString() )
220 mLayer->setSubsetString( prevSubsetString );
221 mIgnoreLayerSubsetStringChangedSignal =
false;
224 lstValues->setCursor( Qt::ArrowCursor );
227void QgsQueryBuilder::btnGetAllValues_clicked()
229 lstValues->setCursor( Qt::WaitCursor );
231 const QString prevSubsetString = mLayer->subsetString();
232 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
234 mIgnoreLayerSubsetStringChangedSignal =
true;
235 mLayer->setSubsetString( QString() );
241 if ( prevSubsetString != mLayer->subsetString() )
243 mLayer->setSubsetString( prevSubsetString );
244 mIgnoreLayerSubsetStringChangedSignal =
false;
247 lstValues->setCursor( Qt::ArrowCursor );
256 if ( mLayer->setSubsetString( mTxtSql->text() ) )
258 const long long featureCount { mLayer->featureCount() };
260 if ( featureCount < 0 )
262 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query, please check the expression syntax." ) );
266 QMessageBox::information(
this, tr(
"Query Result" ), tr(
"The where clause returned %n row(s).",
"returned test rows", featureCount ) );
269 else if ( mLayer->dataProvider()->hasErrors() )
271 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' ) ) ) );
272 mLayer->dataProvider()->clearErrors();
276 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query." ) );
282 if ( mTxtSql->text() != mOrigSubsetString )
284 if ( !mLayer->setSubsetString( mTxtSql->text() ) )
287 if ( mLayer->dataProvider()->hasErrors() )
289 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' ) ) ) );
290 mLayer->dataProvider()->clearErrors();
294 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
306 if ( mLayer->subsetString() != mOrigSubsetString )
307 mLayer->setSubsetString( mOrigSubsetString );
312void QgsQueryBuilder::btnEqual_clicked()
314 mTxtSql->insertText( u
" = "_s );
318void QgsQueryBuilder::btnLessThan_clicked()
320 mTxtSql->insertText( u
" < "_s );
324void QgsQueryBuilder::btnGreaterThan_clicked()
326 mTxtSql->insertText( u
" > "_s );
330void QgsQueryBuilder::btnPct_clicked()
332 mTxtSql->insertText( u
"%"_s );
336void QgsQueryBuilder::btnIn_clicked()
338 mTxtSql->insertText( u
" IN "_s );
342void QgsQueryBuilder::btnNotIn_clicked()
344 mTxtSql->insertText( u
" NOT IN "_s );
348void QgsQueryBuilder::btnLike_clicked()
350 mTxtSql->insertText( u
" LIKE "_s );
356 return mTxtSql->text();
361 mTxtSql->setText( sqlStatement );
364void QgsQueryBuilder::lstFields_clicked(
const QModelIndex &index )
366 if ( mPreviousFieldRow != index.row() )
368 mPreviousFieldRow = index.row();
370 btnSampleValues->setEnabled(
true );
371 btnGetAllValues->setEnabled(
true );
373 mModelValues->clear();
374 mFilterLineEdit->clear();
378void QgsQueryBuilder::lstFields_doubleClicked(
const QModelIndex &index )
384void QgsQueryBuilder::lstValues_doubleClicked(
const QModelIndex &index )
386 const QVariant value = index.data( Qt::UserRole + 1 );
388 mTxtSql->insertText( u
"NULL"_s );
389 else if ( value.userType() == QMetaType::Type::QDate && mLayer->providerType() ==
"ogr"_L1 && mLayer->storageType() ==
"ESRI Shapefile"_L1 )
390 mTxtSql->insertText(
'\'' + value.toDate().toString( u
"yyyy/MM/dd"_s ) +
'\'' );
391 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
392 mTxtSql->insertText( value.toString() );
394 mTxtSql->insertText(
'\'' + value.toString().replace(
'\'',
"''"_L1 ) +
'\'' );
399void QgsQueryBuilder::btnLessEqual_clicked()
401 mTxtSql->insertText( u
" <= "_s );
405void QgsQueryBuilder::btnGreaterEqual_clicked()
407 mTxtSql->insertText( u
" >= "_s );
411void QgsQueryBuilder::btnNotEqual_clicked()
413 mTxtSql->insertText( u
" != "_s );
417void QgsQueryBuilder::btnAnd_clicked()
419 mTxtSql->insertText( u
" AND "_s );
423void QgsQueryBuilder::btnNot_clicked()
425 mTxtSql->insertText( u
" NOT "_s );
429void QgsQueryBuilder::btnOr_clicked()
431 mTxtSql->insertText( u
" OR "_s );
435void QgsQueryBuilder::onTextChanged(
const QString &text )
437 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
438 mProxyValues->setFilterWildcard( text );
444 mLayer->setSubsetString( QString() );
447void QgsQueryBuilder::btnILike_clicked()
449 mTxtSql->insertText( u
" ILIKE "_s );
455 lblDataUri->setText( uri );
458void QgsQueryBuilder::showHelp()
460 QgsHelp::openHelp( u
"working_with_vector/vector_properties.html#query-builder"_s );
472 const QString lastQueryFileDir = s.
value( u
"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
474 QString saveFileName = QFileDialog::getSaveFileName(
nullptr, tr(
"Save Query to File" ), lastQueryFileDir, tr(
"Query files (*.qqf *.QQF)" ) );
475 if ( saveFileName.isNull() )
480 if ( !saveFileName.endsWith(
".qqf"_L1, Qt::CaseInsensitive ) )
482 saveFileName +=
".qqf"_L1;
485 QFile saveFile( saveFileName );
486 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
488 QMessageBox::critical(
nullptr, tr(
"Save Query to File" ), tr(
"Could not open file for writing." ) );
493 QDomElement queryElem = xmlDoc.createElement( u
"Query"_s );
494 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
495 queryElem.appendChild( queryTextNode );
496 xmlDoc.appendChild( queryElem );
498 QTextStream fileStream( &saveFile );
499 xmlDoc.save( fileStream, 2 );
501 const QFileInfo fi( saveFile );
502 s.
setValue( u
"/UI/lastQueryFileDir"_s, fi.absolutePath() );
512 mTxtSql->insertText( subset );
519 const QString lastQueryFileDir = s.
value( u
"/UI/lastQueryFileDir"_s, QDir::homePath() ).toString();
521 const QString queryFileName = QFileDialog::getOpenFileName(
nullptr, tr(
"Load Query from File" ), lastQueryFileDir, tr(
"Query files" ) +
" (*.qqf);;" + tr(
"All files" ) +
" (*)" );
522 if ( queryFileName.isNull() )
527 QFile queryFile( queryFileName );
528 if ( !queryFile.open( QIODevice::ReadOnly ) )
530 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"Could not open file for reading." ) );
533 QDomDocument queryDoc;
534 if ( !queryDoc.setContent( &queryFile ) )
536 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid xml document." ) );
540 const QDomElement queryElem = queryDoc.firstChildElement( u
"Query"_s );
541 if ( queryElem.isNull() )
543 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid query document." ) );
547 subset = queryElem.text();
551void QgsQueryBuilder::layerSubsetStringChanged()
553 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)