16#include "moc_qgsquerybuilder.cpp"
27#include <QDomDocument>
30#include <QInputDialog>
40 QWidget *parent, Qt::WindowFlags fl )
42 , mPreviousFieldRow( -1 )
47 connect( btnEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnEqual_clicked );
48 connect( btnLessThan, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLessThan_clicked );
49 connect( btnGreaterThan, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGreaterThan_clicked );
50 connect( btnPct, &QPushButton::clicked,
this, &QgsQueryBuilder::btnPct_clicked );
51 connect( btnIn, &QPushButton::clicked,
this, &QgsQueryBuilder::btnIn_clicked );
52 connect( btnNotIn, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNotIn_clicked );
53 connect( btnLike, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLike_clicked );
54 connect( btnILike, &QPushButton::clicked,
this, &QgsQueryBuilder::btnILike_clicked );
55 connect( lstFields, &QListView::clicked,
this, &QgsQueryBuilder::lstFields_clicked );
56 connect( lstFields, &QListView::doubleClicked,
this, &QgsQueryBuilder::lstFields_doubleClicked );
57 connect( lstValues, &QListView::doubleClicked,
this, &QgsQueryBuilder::lstValues_doubleClicked );
58 connect( btnLessEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnLessEqual_clicked );
59 connect( btnGreaterEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGreaterEqual_clicked );
60 connect( btnNotEqual, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNotEqual_clicked );
61 connect( btnAnd, &QPushButton::clicked,
this, &QgsQueryBuilder::btnAnd_clicked );
62 connect( btnNot, &QPushButton::clicked,
this, &QgsQueryBuilder::btnNot_clicked );
63 connect( btnOr, &QPushButton::clicked,
this, &QgsQueryBuilder::btnOr_clicked );
64 connect( btnGetAllValues, &QPushButton::clicked,
this, &QgsQueryBuilder::btnGetAllValues_clicked );
65 connect( btnSampleValues, &QPushButton::clicked,
this, &QgsQueryBuilder::btnSampleValues_clicked );
66 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsQueryBuilder::showHelp );
68 QPushButton *pbn =
new QPushButton( tr(
"&Test" ) );
69 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
72 pbn =
new QPushButton( tr(
"&Clear" ) );
73 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
76 pbn =
new QPushButton( tr(
"&Save…" ) );
77 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
78 pbn->setToolTip( tr(
"Save query to QQF file" ) );
81 pbn =
new QPushButton( tr(
"&Load…" ) );
82 buttonBox->addButton( pbn, QDialogButtonBox::ActionRole );
83 pbn->setToolTip( tr(
"Load query from QQF file" ) );
91 lstFields->setModel( mModelFields );
95 layerSubsetStringChanged();
97 QString subsetStringDialect;
98 QString subsetStringHelpUrl;
102 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), provider->name() ) );
103 subsetStringDialect = provider->subsetStringDialect();
104 subsetStringHelpUrl = provider->subsetStringHelpUrl();
108 lblDataUri->setText( tr(
"Set provider filter on %1 (provider: %2)" ).arg( layer->
name(), layer->
providerType() ) );
111 if ( !subsetStringDialect.isEmpty() && !subsetStringHelpUrl.isEmpty() )
113 lblProviderFilterInfo->setOpenExternalLinks(
true );
114 lblProviderFilterInfo->setText( tr(
"Enter a <a href=\"%1\">%2</a> to filter the layer" ).arg( subsetStringHelpUrl ).arg( subsetStringDialect ) ) ;
116 else if ( !subsetStringDialect.isEmpty() )
118 lblProviderFilterInfo->setText( tr(
"Enter a %1 to filter the layer" ).arg( subsetStringDialect ) ) ;
122 lblProviderFilterInfo->hide();
125 mTxtSql->setText( mOrigSubsetString );
127 mFilterLineEdit->setShowSearchIcon(
true );
128 mFilterLineEdit->setPlaceholderText( tr(
"Search…" ) );
129 connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, &QgsQueryBuilder::onTextChanged );
135 QDialog::showEvent( event );
138void QgsQueryBuilder::setupGuiViews()
141 mModelValues =
new QStandardItemModel();
142 mProxyValues =
new QSortFilterProxyModel();
143 mProxyValues->setSourceModel( mModelValues );
145 lstFields->setViewMode( QListView::ListMode );
146 lstValues->setViewMode( QListView::ListMode );
147 lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
148 lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
150 lstFields->setUniformItemSizes(
true );
151 lstValues->setUniformItemSizes(
true );
153 lstFields->setAlternatingRowColors(
true );
154 lstValues->setAlternatingRowColors(
true );
155 lstValues->setModel( mProxyValues );
158void QgsQueryBuilder::fillValues(
const QString &field,
int limit )
161 mModelValues->clear();
166 QList<QVariant> values = qgis::setToList( mLayer->
uniqueValues( fieldIndex, limit ) );
167 std::sort( values.begin(), values.end() );
173 const auto constValues = values;
174 for (
const QVariant &var : constValues )
179 else if ( var.userType() == QMetaType::Type::QDate && mLayer->
providerType() == QLatin1String(
"ogr" ) && mLayer->
storageType() == QLatin1String(
"ESRI Shapefile" ) )
180 value = var.toDate().toString( QStringLiteral(
"yyyy/MM/dd" ) );
181 else if ( var.userType() == QMetaType::Type::QVariantList || var.userType() == QMetaType::Type::QStringList )
183 const QVariantList list = var.toList();
184 for (
const QVariant &val : list )
186 if ( !value.isEmpty() )
187 value.append(
", " );
192 value = var.toString();
194 QStandardItem *myItem =
new QStandardItem( value );
195 myItem->setEditable(
false );
196 myItem->setData( var, Qt::UserRole + 1 );
197 mModelValues->insertRow( mModelValues->rowCount(), myItem );
202void QgsQueryBuilder::btnSampleValues_clicked()
204 lstValues->setCursor( Qt::WaitCursor );
206 const QString prevSubsetString = mLayer->
subsetString();
207 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
209 mIgnoreLayerSubsetStringChangedSignal =
true;
219 mIgnoreLayerSubsetStringChangedSignal =
false;
222 lstValues->setCursor( Qt::ArrowCursor );
225void QgsQueryBuilder::btnGetAllValues_clicked()
227 lstValues->setCursor( Qt::WaitCursor );
229 const QString prevSubsetString = mLayer->
subsetString();
230 if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
232 mIgnoreLayerSubsetStringChangedSignal =
true;
242 mIgnoreLayerSubsetStringChangedSignal =
false;
245 lstValues->setCursor( Qt::ArrowCursor );
256 const long long featureCount { mLayer->
featureCount() };
258 if ( featureCount < 0 )
260 QMessageBox::warning(
this,
261 tr(
"Query Result" ),
262 tr(
"An error occurred when executing the query, please check the expression syntax." ) );
266 QMessageBox::information(
this,
267 tr(
"Query Result" ),
268 tr(
"The where clause returned %n row(s).",
"returned test rows", featureCount ) );
273 QMessageBox::warning(
this,
274 tr(
"Query Result" ),
275 tr(
"An error occurred when executing the query." )
276 + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char(
'\n' ) ) ) );
281 QMessageBox::warning(
this,
282 tr(
"Query Result" ),
283 tr(
"An error occurred when executing the query." ) );
289 if ( mTxtSql->text() != mOrigSubsetString )
296 QMessageBox::warning(
this,
297 tr(
"Query Result" ),
298 tr(
"An error occurred when executing the query." )
299 + tr(
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char(
'\n' ) ) ) );
304 QMessageBox::warning(
this, tr(
"Query Result" ), tr(
"Error in query. The subset string could not be set." ) );
322void QgsQueryBuilder::btnEqual_clicked()
324 mTxtSql->insertText( QStringLiteral(
" = " ) );
328void QgsQueryBuilder::btnLessThan_clicked()
330 mTxtSql->insertText( QStringLiteral(
" < " ) );
334void QgsQueryBuilder::btnGreaterThan_clicked()
336 mTxtSql->insertText( QStringLiteral(
" > " ) );
340void QgsQueryBuilder::btnPct_clicked()
342 mTxtSql->insertText( QStringLiteral(
"%" ) );
346void QgsQueryBuilder::btnIn_clicked()
348 mTxtSql->insertText( QStringLiteral(
" IN " ) );
352void QgsQueryBuilder::btnNotIn_clicked()
354 mTxtSql->insertText( QStringLiteral(
" NOT IN " ) );
358void QgsQueryBuilder::btnLike_clicked()
360 mTxtSql->insertText( QStringLiteral(
" LIKE " ) );
366 return mTxtSql->text();
371 mTxtSql->setText( sqlStatement );
374void QgsQueryBuilder::lstFields_clicked(
const QModelIndex &index )
376 if ( mPreviousFieldRow != index.row() )
378 mPreviousFieldRow = index.row();
380 btnSampleValues->setEnabled(
true );
381 btnGetAllValues->setEnabled(
true );
383 mModelValues->clear();
384 mFilterLineEdit->clear();
388void QgsQueryBuilder::lstFields_doubleClicked(
const QModelIndex &index )
394void QgsQueryBuilder::lstValues_doubleClicked(
const QModelIndex &index )
396 const QVariant value = index.data( Qt::UserRole + 1 );
398 mTxtSql->insertText( QStringLiteral(
"NULL" ) );
399 else if ( value.userType() == QMetaType::Type::QDate && mLayer->
providerType() == QLatin1String(
"ogr" ) && mLayer->
storageType() == QLatin1String(
"ESRI Shapefile" ) )
400 mTxtSql->insertText(
'\'' + value.toDate().toString( QStringLiteral(
"yyyy/MM/dd" ) ) +
'\'' );
401 else if ( value.userType() == QMetaType::Type::Int || value.userType() == QMetaType::Type::Double || value.userType() == QMetaType::Type::LongLong || value.userType() == QMetaType::Type::Bool )
402 mTxtSql->insertText( value.toString() );
404 mTxtSql->insertText(
'\'' + value.toString().replace(
'\'', QLatin1String(
"''" ) ) +
'\'' );
409void QgsQueryBuilder::btnLessEqual_clicked()
411 mTxtSql->insertText( QStringLiteral(
" <= " ) );
415void QgsQueryBuilder::btnGreaterEqual_clicked()
417 mTxtSql->insertText( QStringLiteral(
" >= " ) );
421void QgsQueryBuilder::btnNotEqual_clicked()
423 mTxtSql->insertText( QStringLiteral(
" != " ) );
427void QgsQueryBuilder::btnAnd_clicked()
429 mTxtSql->insertText( QStringLiteral(
" AND " ) );
433void QgsQueryBuilder::btnNot_clicked()
435 mTxtSql->insertText( QStringLiteral(
" NOT " ) );
439void QgsQueryBuilder::btnOr_clicked()
441 mTxtSql->insertText( QStringLiteral(
" OR " ) );
445void QgsQueryBuilder::onTextChanged(
const QString &text )
447 mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
448 mProxyValues->setFilterWildcard( text );
457void QgsQueryBuilder::btnILike_clicked()
459 mTxtSql->insertText( QStringLiteral(
" ILIKE " ) );
465 lblDataUri->setText( uri );
468void QgsQueryBuilder::showHelp()
470 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/vector_properties.html#query-builder" ) );
482 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
484 QString saveFileName = QFileDialog::getSaveFileName(
nullptr, tr(
"Save Query to File" ), lastQueryFileDir, tr(
"Query files (*.qqf *.QQF)" ) );
485 if ( saveFileName.isNull() )
490 if ( !saveFileName.endsWith( QLatin1String(
".qqf" ), Qt::CaseInsensitive ) )
492 saveFileName += QLatin1String(
".qqf" );
495 QFile saveFile( saveFileName );
496 if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
498 QMessageBox::critical(
nullptr, tr(
"Save Query to File" ), tr(
"Could not open file for writing." ) );
503 QDomElement queryElem = xmlDoc.createElement( QStringLiteral(
"Query" ) );
504 const QDomText queryTextNode = xmlDoc.createTextNode( subset );
505 queryElem.appendChild( queryTextNode );
506 xmlDoc.appendChild( queryElem );
508 QTextStream fileStream( &saveFile );
509 xmlDoc.save( fileStream, 2 );
511 const QFileInfo fi( saveFile );
512 s.
setValue( QStringLiteral(
"/UI/lastQueryFileDir" ), fi.absolutePath() );
522 mTxtSql->insertText( subset );
529 const QString lastQueryFileDir = s.
value( QStringLiteral(
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
531 const QString queryFileName = QFileDialog::getOpenFileName(
nullptr, tr(
"Load Query from File" ), lastQueryFileDir, tr(
"Query files" ) +
" (*.qqf);;" + tr(
"All files" ) +
" (*)" );
532 if ( queryFileName.isNull() )
537 QFile queryFile( queryFileName );
538 if ( !queryFile.open( QIODevice::ReadOnly ) )
540 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"Could not open file for reading." ) );
543 QDomDocument queryDoc;
544 if ( !queryDoc.setContent( &queryFile ) )
546 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid xml document." ) );
550 const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral(
"Query" ) );
551 if ( queryElem.isNull() )
553 QMessageBox::critical(
nullptr, tr(
"Load Query from File" ), tr(
"File is not a valid query document." ) );
557 subset = queryElem.text();
561void QgsQueryBuilder::layerSubsetStringChanged()
563 if ( mIgnoreLayerSubsetStringChangedSignal )
565 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)