16#include "moc_qgsquerybuilder.cpp"
27#include <QDomDocument>
30#include <QInputDialog>
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 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();
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;
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;
241 mIgnoreLayerSubsetStringChangedSignal =
false;
244 lstValues->setCursor( Qt::ArrowCursor );
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 ) );
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' ) ) ) );
273 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"An error occurred when executing the query." ) );
279 if ( mTxtSql->text() != mOrigSubsetString )
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' ) ) ) );
291 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
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 );
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 )
552 mUseUnfilteredLayer->setDisabled( mLayer->
subsetString().isEmpty() );
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.
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)