Skip to content

Commit

Permalink
Message bar and other improvements: working!
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso committed Jul 6, 2021
1 parent d914a50 commit 079264e
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 12 deletions.
8 changes: 8 additions & 0 deletions python/core/auto_generated/qgsqueryresultmodel.sip.in
Expand Up @@ -56,6 +56,14 @@ Triggered when ``newRows`` have been fetched and can be added to the model
Cancels the row fetching.
%End


signals:

void fetchingComplete();
%Docstring
Emitted when all rows have been fetched or when the fetching has been stopped
%End

};

/************************************************************************
Expand Down
5 changes: 5 additions & 0 deletions python/gui/auto_generated/codeeditors/qgscodeeditorsql.sip.in
Expand Up @@ -43,6 +43,11 @@ Set field names to be added to the lexer API.
Set field names to ``fieldNames`` to be added to the lexer API.

.. versionadded:: 3.18
%End

QStringList fieldNames() const;
%Docstring
Return field names from the lexer API.
%End

protected:
Expand Down
84 changes: 84 additions & 0 deletions python/gui/auto_generated/qgsqueryresultwidget.sip.in
@@ -0,0 +1,84 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsqueryresultwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/







class QgsQueryResultWidget: QWidget
{
%Docstring
The QgsQueryResultWidget class allow users to enter and run an SQL query on a
DB connection (an instance of QgsAbstractDatabaseProviderConnection).

Query results are displayed in a table view.
Query execution and result
fetching can be interrupted by pressing the "Stop" push button.

.. note::

the ownership of the connection is transferred to the widget.

.. versionadded:: 3.20
%End

%TypeHeaderCode
#include "qgsqueryresultwidget.h"
%End
public:

QgsQueryResultWidget( QWidget *parent = 0, QgsAbstractDatabaseProviderConnection* connection /Transfer/ = 0 );
%Docstring
Creates a QgsQueryResultWidget with the given ``connection``, ownership is transferred to the widget.
%End

virtual ~QgsQueryResultWidget();

void setConnection(QgsAbstractDatabaseProviderConnection* connection);
%Docstring
Set the connection to ``connection``, ownership is transferred to the widget.
%End

void setQuery( const QString& sql );
%Docstring
Convenience method to set the SQL editor test to ``sql``.
%End

public slots:

void executeQuery();
%Docstring
Executes the query
%End

void updateButtons();
%Docstring
Updates buttons status
%End

void showError( const QString &title, const QString &message);
%Docstring
Hides the result table and shows the error ``title`` and ``message`` in the message bar.
%End

void tokensReady(const QStringList &tokens );
%Docstring
Triggered when the threaded API fetcher has new ``tokens`` to add.
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsqueryresultwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
8 changes: 8 additions & 0 deletions src/core/qgsqueryresultmodel.cpp
Expand Up @@ -27,6 +27,8 @@ QgsQueryResultModel::QgsQueryResultModel( const QgsAbstractDatabaseProviderConne
mWorker->moveToThread( &mWorkerThread );
connect( &mWorkerThread, &QThread::started, mWorker, &QgsQueryResultFetcher::fetchRows );
connect( mWorker, &QgsQueryResultFetcher::rowsReady, this, &QgsQueryResultModel::rowsReady );
// Forward signal
connect( mWorker, &QgsQueryResultFetcher::fetchingComplete, this, &QgsQueryResultModel::fetchingComplete );
mWorkerThread.start();
}
}
Expand Down Expand Up @@ -55,6 +57,10 @@ QgsQueryResultModel::~QgsQueryResultModel()
mWorkerThread.wait();
mWorker->deleteLater();
}
else
{
emit fetchingComplete();
}
}

int QgsQueryResultModel::rowCount( const QModelIndex &parent ) const
Expand Down Expand Up @@ -124,6 +130,8 @@ void QgsQueryResultFetcher::fetchRows()
{
emit rowsReady( newRows );
}

emit fetchingComplete();
}

void QgsQueryResultFetcher::stopFetching()
Expand Down
9 changes: 9 additions & 0 deletions src/core/qgsqueryresultmodel.h
Expand Up @@ -53,6 +53,9 @@ class QgsQueryResultFetcher: public QObject
//! Emitted when \a newRows have been fetched
void rowsReady( const QList<QList<QVariant>> &newRows );

//! Emitted when all rows have been fetched or when the fetching has been stopped
void fetchingComplete();

private:

const QgsAbstractDatabaseProviderConnection::QueryResult *mQueryResult = nullptr;
Expand Down Expand Up @@ -105,6 +108,12 @@ class CORE_EXPORT QgsQueryResultModel : public QAbstractTableModel
*/
void cancel();


signals:

//! Emitted when all rows have been fetched or when the fetching has been stopped
void fetchingComplete();

private:

QgsAbstractDatabaseProviderConnection::QueryResult mQueryResult;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/codeeditors/qgscodeeditorsql.cpp
Expand Up @@ -96,6 +96,11 @@ void QgsCodeEditorSQL::updateApis()
mSqlLexer->setAPIs( mApis );
}

QStringList QgsCodeEditorSQL::fieldNames() const
{
return mFieldNames;
}

void QgsCodeEditorSQL::setFieldNames( const QStringList &fieldNames )
{
mFieldNames = fieldNames;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/codeeditors/qgscodeeditorsql.h
Expand Up @@ -53,6 +53,11 @@ class GUI_EXPORT QgsCodeEditorSQL : public QgsCodeEditor
*/
void setFieldNames( const QStringList &fieldNames );

/**
* Return field names from the lexer API.
*/
QStringList fieldNames() const;

protected:
void initializeLexer() override;

Expand Down
11 changes: 10 additions & 1 deletion src/ui/qgsqueryresultwidgetbase.ui
Expand Up @@ -14,11 +14,14 @@
<string notr="true">Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QgsMessageBar" name="mMessageBar" native="true"/>
</item>
<item>
<widget class="QgsCodeEditorSQL" name="mCodeEditor" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
Expand Down Expand Up @@ -73,6 +76,12 @@
<header>qgscodeeditorsql.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsMessageBar</class>
<extends>QWidget</extends>
<header>qgsmessagebar.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
Expand Down
67 changes: 58 additions & 9 deletions tests/src/gui/testqgsqueryresultwidget.cpp
Expand Up @@ -35,27 +35,39 @@ class TestQgsQueryResultWidget: public QObject
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

public slots:
void testWidgetCrash();
void testWidget();
void testWidgetInvalid();
private slots:
void testCodeEditorApis();

private:

QgsAbstractDatabaseProviderConnection *makeConn();

std::unique_ptr<QgsAbstractDatabaseProviderConnection> mConn;

};

QgsAbstractDatabaseProviderConnection *TestQgsQueryResultWidget::makeConn()
{
return static_cast<QgsAbstractDatabaseProviderConnection *>( QgsProviderRegistry::instance( )->providerMetadata( QStringLiteral( "postgres" ) )->createConnection( qgetenv( "QGIS_PGTEST_DB" ), QVariantMap() ) );
}

void TestQgsQueryResultWidget::initTestCase()
{
QgsApplication::initQgis();
auto conn { QgsProviderRegistry::instance( )->providerMetadata( QStringLiteral( "postgres" ) )->createConnection( qgetenv( "QGIS_PGTEST_DB" ), QVariantMap() ) };
mConn.reset( static_cast<QgsAbstractDatabaseProviderConnection *>( conn ) );
mConn.reset( makeConn() );
// Prepare data for fetching test
mConn->execSql( QStringLiteral( "DROP TABLE IF EXISTS qgis_test.random_big_data" ) );
mConn->execSql( QStringLiteral( "SELECT * INTO qgis_test.random_big_data FROM generate_series(1,1000000) AS id, md5(random()::text) AS descr" ) );
mConn->execSql( QStringLiteral( "SELECT * INTO qgis_test.random_big_data FROM generate_series(1,100000) AS id, md5(random()::text) AS descr" ) );
}

void TestQgsQueryResultWidget::cleanupTestCase()
{
mConn.reset( makeConn() );
mConn->execSql( QStringLiteral( "DROP TABLE IF EXISTS qgis_test.random_big_data" ) );
}

void TestQgsQueryResultWidget::init()
Expand All @@ -70,6 +82,7 @@ void TestQgsQueryResultWidget::cleanup()
void TestQgsQueryResultWidget::testWidgetCrash()
{
// Make a copy
mConn.reset( makeConn() );
auto res = new QgsAbstractDatabaseProviderConnection::QueryResult( mConn->execSql( QStringLiteral( "SELECT * FROM qgis_test.random_big_data" ) ) );
auto model = new QgsQueryResultModel( *res );
bool exited { false };
Expand All @@ -78,30 +91,66 @@ void TestQgsQueryResultWidget::testWidgetCrash()
while ( ! exited )
QgsApplication::processEvents();
const auto rowCount { model->rowCount( model->index( -1, -1 ) ) };
QVERIFY( rowCount > 0 && rowCount < 1000000 );
QVERIFY( rowCount > 0 && rowCount < 100000 );
delete model;

// Test widget closed while fetching
auto d = qgis::make_unique<QDialog>( );
QVBoxLayout *l = new QVBoxLayout();
QgsQueryResultWidget *w = new QgsQueryResultWidget( d.get(), makeConn() );
w->setQuery( QStringLiteral( "SELECT * FROM qgis_test.random_big_data" ) );
l->addWidget( w );
d->setLayout( l );
w->executeQuery();
exited = false;
QTimer::singleShot( 1, d.get(), [ & ] { exited = true; } );
while ( ! exited )
QgsApplication::processEvents();
}


void TestQgsQueryResultWidget::testWidget()
{
auto d = qgis::make_unique<QDialog>( );
auto l = new QVBoxLayout();
auto w = new QgsQueryResultWidget( d.get(), mConn.release() );
QVBoxLayout *l = new QVBoxLayout();
QgsQueryResultWidget *w = new QgsQueryResultWidget( d.get(), makeConn() );
w->setQuery( QStringLiteral( "SELECT * FROM qgis_test.random_big_data" ) );
l->addWidget( w );
d->setLayout( l );
// For interactive testing
//d->exec();
w->executeQuery();
QTimer::singleShot( 0, d.get(), [ = ]
QTimer::singleShot( 1, d.get(), [ = ]
{
QTest::mousePress( w->mStopButton, Qt::MouseButton::LeftButton );
} );
QgsApplication::processEvents();
bool exited = false;
connect( w->mApiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, d.get(), [ & ] { exited = true; } );
while ( ! exited )
QgsApplication::processEvents();
const auto rowCount { w->mModel->rowCount( w->mModel->index( -1, -1 ) ) };
QVERIFY( rowCount > 0 && rowCount < 1000000 );
QVERIFY( rowCount > 0 && rowCount < 100000 );
}

void TestQgsQueryResultWidget::testWidgetInvalid()
{
QgsQueryResultWidget w( nullptr, nullptr );
Q_UNUSED( w )
}

void TestQgsQueryResultWidget::testCodeEditorApis()
{
auto w = qgis::make_unique<QgsQueryResultWidget>( nullptr, makeConn() );
bool exited = false;
connect( w->mApiFetcher, &QgsConnectionsApiFetcher::fetchingFinished, w.get(), [ & ] { exited = true; } );
while ( ! exited )
QgsApplication::processEvents();
qDebug() << w->mCodeEditor->fieldNames();
QVERIFY( w->mCodeEditor->fieldNames().contains( QStringLiteral( "qgis_test" ) ) );
QVERIFY( w->mCodeEditor->fieldNames().contains( QStringLiteral( "random_big_data" ) ) );
QVERIFY( w->mCodeEditor->fieldNames().contains( QStringLiteral( "descr" ) ) );
}


QGSTEST_MAIN( TestQgsQueryResultWidget )
#include "testqgsqueryresultwidget.moc"
1 change: 0 additions & 1 deletion tests/src/python/test_qgsqueryresultmodel.py
Expand Up @@ -58,7 +58,6 @@ def tearDownClass(cls):
@classmethod
def _deleteBigData(cls):

return
try:
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(cls.uri, {})
Expand Down
6 changes: 5 additions & 1 deletion tests/src/python/test_qgsqueryresultwidget.py
Expand Up @@ -72,9 +72,13 @@ def test_widget(self):
l = QVBoxLayout(d)
d.setLayout(l)
l.addWidget(widget)

d.exec_()

def test_widget_invalid(self):
"""Test it does not crash"""

QgsQueryResultWidget(None, None)


if __name__ == '__main__':
unittest.main()

0 comments on commit 079264e

Please sign in to comment.