25 #include <QDomDocument> 
   26 #include <QDomElement> 
   27 #include <QFileDialog> 
   28 #include <QInputDialog> 
   30 #include <QMessageBox> 
   32 #include <QPushButton> 
   33 #include <QTextStream> 
   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" ) );
 
   89   layerSubsetStringChanged();
 
   91   lblDataUri->setText( tr( 
"Set provider filter on %1" ).arg( layer->
name() ) );
 
   92   mTxtSql->setText( mOrigSubsetString );
 
   94   mFilterLineEdit->setShowSearchIcon( 
true );
 
   95   mFilterLineEdit->setPlaceholderText( tr( 
"Search…" ) );
 
   96   connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, 
this, &QgsQueryBuilder::onTextChanged );
 
  104   QDialog::showEvent( event );
 
  107 void QgsQueryBuilder::populateFields()
 
  110   mTxtSql->setFields( fields );
 
  111   for ( 
int idx = 0; idx < fields.
count(); ++idx )
 
  119     myItem->setData( idx );
 
  120     myItem->setEditable( 
false );
 
  121     mModelFields->insertRow( mModelFields->rowCount(), myItem );
 
  125   setupLstFieldsModel();
 
  128 void QgsQueryBuilder::setupLstFieldsModel()
 
  130   lstFields->setModel( mModelFields );
 
  133 void QgsQueryBuilder::setupGuiViews()
 
  136   mModelFields = 
new QStandardItemModel();
 
  137   mModelValues = 
new QStandardItemModel();
 
  138   mProxyValues = 
new QSortFilterProxyModel();
 
  139   mProxyValues->setSourceModel( mModelValues );
 
  141   lstFields->setViewMode( QListView::ListMode );
 
  142   lstValues->setViewMode( QListView::ListMode );
 
  143   lstFields->setSelectionBehavior( QAbstractItemView::SelectRows );
 
  144   lstValues->setSelectionBehavior( QAbstractItemView::SelectRows );
 
  146   lstFields->setUniformItemSizes( 
true );
 
  147   lstValues->setUniformItemSizes( 
true );
 
  149   lstFields->setAlternatingRowColors( 
true );
 
  150   lstValues->setAlternatingRowColors( 
true );
 
  151   lstValues->setModel( mProxyValues );
 
  154 void QgsQueryBuilder::fillValues( 
int idx, 
int limit )
 
  157   mModelValues->clear();
 
  160   QList<QVariant> values = qgis::setToList( mLayer->
uniqueValues( idx, limit ) );
 
  161   std::sort( values.begin(), values.end() );
 
  165   QgsDebugMsg( QStringLiteral( 
"nullValue: %1" ).arg( nullValue ) );
 
  167   const auto constValues = values;
 
  168   for ( 
const QVariant &var : constValues )
 
  173     else if ( var.type() == QVariant::Date && mLayer->
providerType() == QLatin1String( 
"ogr" ) && mLayer->
storageType() == QLatin1String( 
"ESRI Shapefile" ) )
 
  174       value = var.toDate().toString( QStringLiteral( 
"yyyy/MM/dd" ) );
 
  175     else if ( var.type() == QVariant::List || var.type() == QVariant::StringList )
 
  177       const QVariantList list = var.toList();
 
  178       for ( 
const QVariant &val : list )
 
  180         if ( !value.isEmpty() )
 
  181           value.append( 
", " );
 
  182         value.append( val.isNull() ? nullValue : val.toString() );
 
  186       value = var.toString();
 
  188     QStandardItem *myItem = 
new QStandardItem( value );
 
  189     myItem->setEditable( 
false );
 
  190     myItem->setData( var, Qt::UserRole + 1 );
 
  191     mModelValues->insertRow( mModelValues->rowCount(), myItem );
 
  192     QgsDebugMsg( QStringLiteral( 
"Value is null: %1\nvalue: %2" ).arg( var.isNull() ).arg( var.isNull() ? nullValue : var.toString() ) );
 
  196 void QgsQueryBuilder::btnSampleValues_clicked()
 
  198   lstValues->setCursor( Qt::WaitCursor );
 
  200   const QString prevSubsetString = mLayer->
subsetString();
 
  201   if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
 
  203     mIgnoreLayerSubsetStringChangedSignal = 
true;
 
  208   fillValues( mModelFields->data( lstFields->currentIndex(), Qt::UserRole + 1 ).toInt(), 25 );
 
  213     mIgnoreLayerSubsetStringChangedSignal = 
false;
 
  216   lstValues->setCursor( Qt::ArrowCursor );
 
  219 void QgsQueryBuilder::btnGetAllValues_clicked()
 
  221   lstValues->setCursor( Qt::WaitCursor );
 
  223   const QString prevSubsetString = mLayer->
subsetString();
 
  224   if ( mUseUnfilteredLayer->isChecked() && !prevSubsetString.isEmpty() )
 
  226     mIgnoreLayerSubsetStringChangedSignal = 
true;
 
  231   fillValues( mModelFields->data( lstFields->currentIndex(), Qt::UserRole + 1 ).toInt(), -1 );
 
  236     mIgnoreLayerSubsetStringChangedSignal = 
false;
 
  239   lstValues->setCursor( Qt::ArrowCursor );
 
  250     const long long featureCount { mLayer->
featureCount() };
 
  252     if ( featureCount < 0 )
 
  254       QMessageBox::warning( 
this,
 
  255                             tr( 
"Query Result" ),
 
  256                             tr( 
"An error occurred when executing the query, please check the expression syntax." ) );
 
  260       QMessageBox::information( 
this,
 
  261                                 tr( 
"Query Result" ),
 
  262                                 tr( 
"The where clause returned %n row(s).", 
"returned test rows", featureCount ) );
 
  267     QMessageBox::warning( 
this,
 
  268                           tr( 
"Query Result" ),
 
  269                           tr( 
"An error occurred when executing the query." )
 
  270                           + tr( 
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char( 
'\n' ) ) ) );
 
  275     QMessageBox::warning( 
this,
 
  276                           tr( 
"Query Result" ),
 
  277                           tr( 
"An error occurred when executing the query." ) );
 
  283   if ( mTxtSql->text() != mOrigSubsetString )
 
  290         QMessageBox::warning( 
this,
 
  291                               tr( 
"Query Result" ),
 
  292                               tr( 
"An error occurred when executing the query." )
 
  293                               + tr( 
"\nThe data provider said:\n%1" ).arg( mLayer->
dataProvider()->
errors().join( QLatin1Char( 
'\n' ) ) ) );
 
  298         QMessageBox::warning( 
this, tr( 
"Query Result" ), tr( 
"Error in query. The subset string could not be set." ) );
 
  316 void QgsQueryBuilder::btnEqual_clicked()
 
  318   mTxtSql->insertText( QStringLiteral( 
" = " ) );
 
  322 void QgsQueryBuilder::btnLessThan_clicked()
 
  324   mTxtSql->insertText( QStringLiteral( 
" < " ) );
 
  328 void QgsQueryBuilder::btnGreaterThan_clicked()
 
  330   mTxtSql->insertText( QStringLiteral( 
" > " ) );
 
  334 void QgsQueryBuilder::btnPct_clicked()
 
  336   mTxtSql->insertText( QStringLiteral( 
"%" ) );
 
  340 void QgsQueryBuilder::btnIn_clicked()
 
  342   mTxtSql->insertText( QStringLiteral( 
" IN " ) );
 
  346 void QgsQueryBuilder::btnNotIn_clicked()
 
  348   mTxtSql->insertText( QStringLiteral( 
" NOT IN " ) );
 
  352 void QgsQueryBuilder::btnLike_clicked()
 
  354   mTxtSql->insertText( QStringLiteral( 
" LIKE " ) );
 
  360   return mTxtSql->text();
 
  365   mTxtSql->setText( sqlStatement );
 
  368 void QgsQueryBuilder::lstFields_clicked( 
const QModelIndex &index )
 
  370   if ( mPreviousFieldRow != index.row() )
 
  372     mPreviousFieldRow = index.row();
 
  374     btnSampleValues->setEnabled( 
true );
 
  375     btnGetAllValues->setEnabled( 
true );
 
  377     mModelValues->clear();
 
  378     mFilterLineEdit->clear();
 
  382 void QgsQueryBuilder::lstFields_doubleClicked( 
const QModelIndex &index )
 
  384   mTxtSql->insertText( 
'\"' + mLayer->
fields().
at( mModelFields->data( index, Qt::UserRole + 1 ).toInt() ).
name() + 
'\"' );
 
  388 void QgsQueryBuilder::lstValues_doubleClicked( 
const QModelIndex &index )
 
  390   const QVariant value = index.data( Qt::UserRole + 1 );
 
  391   if ( value.isNull() )
 
  392     mTxtSql->insertText( QStringLiteral( 
"NULL" ) );
 
  393   else if ( value.type() == QVariant::Date && mLayer->
providerType() == QLatin1String( 
"ogr" ) && mLayer->
storageType() == QLatin1String( 
"ESRI Shapefile" ) )
 
  394     mTxtSql->insertText( 
'\'' + value.toDate().toString( QStringLiteral( 
"yyyy/MM/dd" ) ) + 
'\'' );
 
  395   else if ( value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || value.type() == QVariant::Bool )
 
  396     mTxtSql->insertText( value.toString() );
 
  398     mTxtSql->insertText( 
'\'' + value.toString().replace( 
'\'', QLatin1String( 
"''" ) ) + 
'\'' );
 
  403 void QgsQueryBuilder::btnLessEqual_clicked()
 
  405   mTxtSql->insertText( QStringLiteral( 
" <= " ) );
 
  409 void QgsQueryBuilder::btnGreaterEqual_clicked()
 
  411   mTxtSql->insertText( QStringLiteral( 
" >= " ) );
 
  415 void QgsQueryBuilder::btnNotEqual_clicked()
 
  417   mTxtSql->insertText( QStringLiteral( 
" != " ) );
 
  421 void QgsQueryBuilder::btnAnd_clicked()
 
  423   mTxtSql->insertText( QStringLiteral( 
" AND " ) );
 
  427 void QgsQueryBuilder::btnNot_clicked()
 
  429   mTxtSql->insertText( QStringLiteral( 
" NOT " ) );
 
  433 void QgsQueryBuilder::btnOr_clicked()
 
  435   mTxtSql->insertText( QStringLiteral( 
" OR " ) );
 
  439 void QgsQueryBuilder::onTextChanged( 
const QString &text )
 
  441   mProxyValues->setFilterCaseSensitivity( Qt::CaseInsensitive );
 
  442   mProxyValues->setFilterWildcard( text );
 
  451 void QgsQueryBuilder::btnILike_clicked()
 
  453   mTxtSql->insertText( QStringLiteral( 
" ILIKE " ) );
 
  459   lblDataUri->setText( uri );
 
  462 void QgsQueryBuilder::showHelp()
 
  464   QgsHelp::openHelp( QStringLiteral( 
"working_with_vector/vector_properties.html#query-builder" ) );
 
  476   const QString lastQueryFileDir = s.
value( QStringLiteral( 
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
 
  478   QString saveFileName = QFileDialog::getSaveFileName( 
nullptr, tr( 
"Save Query to File" ), lastQueryFileDir, tr( 
"Query files (*.qqf *.QQF)" ) );
 
  479   if ( saveFileName.isNull() )
 
  484   if ( !saveFileName.endsWith( QLatin1String( 
".qqf" ), Qt::CaseInsensitive ) )
 
  486     saveFileName += QLatin1String( 
".qqf" );
 
  489   QFile saveFile( saveFileName );
 
  490   if ( !saveFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
 
  492     QMessageBox::critical( 
nullptr, tr( 
"Save Query to File" ), tr( 
"Could not open file for writing." ) );
 
  497   QDomElement queryElem = xmlDoc.createElement( QStringLiteral( 
"Query" ) );
 
  498   const QDomText queryTextNode = xmlDoc.createTextNode( subset );
 
  499   queryElem.appendChild( queryTextNode );
 
  500   xmlDoc.appendChild( queryElem );
 
  502   QTextStream fileStream( &saveFile );
 
  503   xmlDoc.save( fileStream, 2 );
 
  505   const QFileInfo fi( saveFile );
 
  506   s.
setValue( QStringLiteral( 
"/UI/lastQueryFileDir" ), fi.absolutePath() );
 
  516     mTxtSql->insertText( subset );
 
  523   const QString lastQueryFileDir = s.
value( QStringLiteral( 
"/UI/lastQueryFileDir" ), QDir::homePath() ).toString();
 
  525   const QString queryFileName = QFileDialog::getOpenFileName( 
nullptr, tr( 
"Load Query from File" ), lastQueryFileDir, tr( 
"Query files" ) + 
" (*.qqf);;" + tr( 
"All files" ) + 
" (*)" );
 
  526   if ( queryFileName.isNull() )
 
  531   QFile queryFile( queryFileName );
 
  532   if ( !queryFile.open( QIODevice::ReadOnly ) )
 
  534     QMessageBox::critical( 
nullptr, tr( 
"Load Query from File" ), tr( 
"Could not open file for reading." ) );
 
  537   QDomDocument queryDoc;
 
  538   if ( !queryDoc.setContent( &queryFile ) )
 
  540     QMessageBox::critical( 
nullptr, tr( 
"Load Query from File" ), tr( 
"File is not a valid xml document." ) );
 
  544   const QDomElement queryElem = queryDoc.firstChildElement( QStringLiteral( 
"Query" ) );
 
  545   if ( queryElem.isNull() )
 
  547     QMessageBox::critical( 
nullptr, tr( 
"Load Query from File" ), tr( 
"File is not a valid query document." ) );
 
  551   subset = queryElem.text();
 
  555 void QgsQueryBuilder::layerSubsetStringChanged()
 
  557   if ( mIgnoreLayerSubsetStringChangedSignal )
 
  559   mUseUnfilteredLayer->setDisabled( mLayer->
subsetString().isEmpty() );