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 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,
260 tr(
"Query Result" ),
261 tr(
"An error occurred when executing the query, please check the expression syntax." ) );
265 QMessageBox::information(
this,
266 tr(
"Query Result" ),
267 tr(
"The where clause returned %n row(s).",
"returned test rows", featureCount ) );
272 QMessageBox::warning(
this,
273 tr(
"Query Result" ),
274 tr(
"An error occurred when executing the query." )
275 + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char(
'\n' ) ) ) );
280 QMessageBox::warning(
this,
281 tr(
"Query Result" ),
282 tr(
"An error occurred when executing the query." ) );
288 if ( mTxtSql->text() != mOrigSubsetString )
295 QMessageBox::warning(
this,
296 tr(
"Query Result" ),
297 tr(
"An error occurred when executing the query." )
298 + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char(
'\n' ) ) ) );
303 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
321void QgsQueryBuilder::btnEqual_clicked()
323 mTxtSql->insertText( QStringLiteral(
" = " ) );
327void QgsQueryBuilder::btnLessThan_clicked()
329 mTxtSql->insertText( QStringLiteral(
" < " ) );
333void QgsQueryBuilder::btnGreaterThan_clicked()
335 mTxtSql->insertText( QStringLiteral(
" > " ) );
339void QgsQueryBuilder::btnPct_clicked()
341 mTxtSql->insertText( QStringLiteral(
"%" ) );
345void QgsQueryBuilder::btnIn_clicked()
347 mTxtSql->insertText( QStringLiteral(
" IN " ) );
351void QgsQueryBuilder::btnNotIn_clicked()
353 mTxtSql->insertText( QStringLiteral(
" NOT IN " ) );
357void QgsQueryBuilder::btnLike_clicked()
359 mTxtSql->insertText( QStringLiteral(
" LIKE " ) );
365 return mTxtSql->text();
370 mTxtSql->setText( sqlStatement );
373void QgsQueryBuilder::lstFields_clicked(
const QModelIndex &index )
375 if ( mPreviousFieldRow != index.row() )
377 mPreviousFieldRow = index.row();
379 btnSampleValues->setEnabled(
true );
380 btnGetAllValues->setEnabled(
true );
382 mModelValues->clear();
383 mFilterLineEdit->clear();
387void QgsQueryBuilder::lstFields_doubleClicked(
const QModelIndex &index )
393void QgsQueryBuilder::lstValues_doubleClicked(
const QModelIndex &index )
395 const QVariant value = index.data( Qt::UserRole + 1 );
397 mTxtSql->insertText( QStringLiteral(
"NULL" ) );
398 else if ( value.userType() == QMetaType::Type::QDate && mLayer->
providerType() == QLatin1String(
"ogr" ) && mLayer->
storageType() == QLatin1String(
"ESRI Shapefile" ) )
399 mTxtSql->insertText(
'\'' + value.toDate().toString( QStringLiteral(
"yyyy/MM/dd" ) ) +
'\'' );
400 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
401 mTxtSql->insertText( value.toString() );
403 mTxtSql->insertText(
'\'' + value.toString().replace(
'\'', QLatin1String(
"''" ) ) +
'\'' );
408void QgsQueryBuilder::btnLessEqual_clicked()
410 mTxtSql->insertText( QStringLiteral(
" <= " ) );
414void QgsQueryBuilder::btnGreaterEqual_clicked()
416 mTxtSql->insertText( QStringLiteral(
" >= " ) );
420void QgsQueryBuilder::btnNotEqual_clicked()
422 mTxtSql->insertText( QStringLiteral(
" != " ) );
426void QgsQueryBuilder::btnAnd_clicked()
428 mTxtSql->insertText( QStringLiteral(
" AND " ) );
432void QgsQueryBuilder::btnNot_clicked()
434 mTxtSql->insertText( QStringLiteral(
" NOT " ) );
438void QgsQueryBuilder::btnOr_clicked()
440 mTxtSql->insertText( QStringLiteral(
" OR " ) );
444void QgsQueryBuilder::onTextChanged(
const QString &text )
446 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
447 mProxyValues->setFilterWildcard( text );
456void QgsQueryBuilder::btnILike_clicked()
458 mTxtSql->insertText( QStringLiteral(
" ILIKE " ) );
464 lblDataUri->setText( uri );
467void QgsQueryBuilder::showHelp()
469 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#query-builder" ) );
481 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
483 QString saveFileName = QFileDialog::getSaveFileName(
nullptr, tr(
"Save Query to File" ), lastQueryFileDir, tr(
"Query files (*.qqf *.QQF)" ) );
484 if ( saveFileName.isNull() )
489 if ( !saveFileName.endsWith( QLatin1String(
".qqf" ), Qt::CaseInsensitive ) )
491 saveFileName += QLatin1String(
".qqf" );
494 QFile saveFile( saveFileName );
495 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
497 QMessageBox::critical(
nullptr, tr(
"Save Query to File" ), tr(
"Could not open file for writing." ) );
502 QDomElement queryElem = xmlDoc.createElement( QStringLiteral(
"Query" ) );
503 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
504 queryElem.appendChild( queryTextNode );
505 xmlDoc.appendChild( queryElem );
507 QTextStream fileStream( &saveFile );
508 xmlDoc.save( fileStream, 2 );
510 const QFileInfo fi( saveFile );
511 s.
setValue( QStringLiteral(
"/UI/lastQueryFileDir" ), fi.absolutePath() );
521 mTxtSql->insertText( subset );
528 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
530 const QString queryFileName = QFileDialog::getOpenFileName(
nullptr, tr(
"Load Query from File" ), lastQueryFileDir, tr(
"Query files" ) +
" (*.qqf);;" + tr(
"All files" ) +
" (*)" );
531 if ( queryFileName.isNull() )
536 QFile queryFile( queryFileName );
537 if ( !queryFile.open( QIODevice::ReadOnly ) )
539 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"Could not open file for reading." ) );
542 QDomDocument queryDoc;
543 if ( !queryDoc.setContent( &queryFile ) )
545 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid xml document." ) );
549 const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral(
"Query" ) );
550 if ( queryElem.isNull() )
552 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid query document." ) );
556 subset = queryElem.text();
560void QgsQueryBuilder::layerSubsetStringChanged()
562 if ( mIgnoreLayerSubsetStringChangedSignal )
564 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)