mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
[feature] Saving/loading SQL queries from Execute SQL/Update SQL dialogs
This adds support for saving and loading SQL queries to a .sql text file to the Execute SQL dialog and Update SQL dialogs. Effectively, it ports this functionality from the DB Manager plugin over to the core browser-based database connection facilities. The UX has been designed to mimic the same functionality from other standard parts of QGIS, eg the Processing Script Editor. Toolbar actions are used accordingly, instead of the old text button approach used in DB Manager. Sponsored by City of Canning
This commit is contained in:
parent
feb0dd789d
commit
090e9fb671
@ -35,6 +35,7 @@ be used in different contexts like when updating the SQL of an existing query la
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
enum class QueryWidgetMode /BaseType=IntFlag/
|
||||
{
|
||||
SqlQueryMode,
|
||||
@ -68,6 +69,7 @@ Sets the connection to ``connection``, ownership is transferred to the widget.
|
||||
Convenience method to set the SQL editor text to ``sql``.
|
||||
%End
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void notify( const QString &title, const QString &text, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
|
||||
@ -125,6 +127,79 @@ Emitted when the first batch of results has been fetched.
|
||||
If the query returns no results this signal is not emitted.
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
class QgsQueryResultDialog : QDialog
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A dialog which allows users to enter and run an SQL query on a
|
||||
DB connection (an instance of :py:class:`QgsAbstractDatabaseProviderConnection`).
|
||||
|
||||
.. note::
|
||||
|
||||
the ownership of the connection is transferred to the dialog.
|
||||
|
||||
.. seealso:: :py:class:`QgsQueryResultWidget`
|
||||
|
||||
.. versionadded:: 3.44
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
QgsQueryResultDialog( QgsAbstractDatabaseProviderConnection *connection /Transfer/ = 0, QWidget *parent = 0 );
|
||||
%Docstring
|
||||
Constructor for QgsQueryResultDialog.
|
||||
|
||||
Ownership of the ``connection`` is transferred to the dialog.
|
||||
%End
|
||||
|
||||
QgsQueryResultWidget *resultWidget();
|
||||
%Docstring
|
||||
Returns the :py:class:`QgsQueryResultWidget` shown in the dialog.
|
||||
%End
|
||||
|
||||
virtual void closeEvent( QCloseEvent *event );
|
||||
|
||||
|
||||
};
|
||||
|
||||
class QgsQueryResultMainWindow : QMainWindow
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A main window which allows users to enter and run an SQL query on a
|
||||
DB connection (an instance of :py:class:`QgsAbstractDatabaseProviderConnection`).
|
||||
|
||||
.. note::
|
||||
|
||||
the ownership of the connection is transferred to the window.
|
||||
|
||||
.. seealso:: :py:class:`QgsQueryResultWidget`
|
||||
|
||||
.. versionadded:: 3.44
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
QgsQueryResultMainWindow( QgsAbstractDatabaseProviderConnection *connection /Transfer/ = 0, const QString &identifierName = QString() );
|
||||
%Docstring
|
||||
Constructor for QgsQueryResultMainWindow.
|
||||
|
||||
Ownership of the ``connection`` is transferred to the window.
|
||||
%End
|
||||
|
||||
QgsQueryResultWidget *resultWidget();
|
||||
%Docstring
|
||||
Returns the :py:class:`QgsQueryResultWidget` shown in the window.
|
||||
%End
|
||||
|
||||
virtual void closeEvent( QCloseEvent *event );
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
||||
@ -35,6 +35,7 @@ be used in different contexts like when updating the SQL of an existing query la
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
enum class QueryWidgetMode
|
||||
{
|
||||
SqlQueryMode,
|
||||
@ -68,6 +69,7 @@ Sets the connection to ``connection``, ownership is transferred to the widget.
|
||||
Convenience method to set the SQL editor text to ``sql``.
|
||||
%End
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void notify( const QString &title, const QString &text, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
|
||||
@ -125,6 +127,79 @@ Emitted when the first batch of results has been fetched.
|
||||
If the query returns no results this signal is not emitted.
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
class QgsQueryResultDialog : QDialog
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A dialog which allows users to enter and run an SQL query on a
|
||||
DB connection (an instance of :py:class:`QgsAbstractDatabaseProviderConnection`).
|
||||
|
||||
.. note::
|
||||
|
||||
the ownership of the connection is transferred to the dialog.
|
||||
|
||||
.. seealso:: :py:class:`QgsQueryResultWidget`
|
||||
|
||||
.. versionadded:: 3.44
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
QgsQueryResultDialog( QgsAbstractDatabaseProviderConnection *connection /Transfer/ = 0, QWidget *parent = 0 );
|
||||
%Docstring
|
||||
Constructor for QgsQueryResultDialog.
|
||||
|
||||
Ownership of the ``connection`` is transferred to the dialog.
|
||||
%End
|
||||
|
||||
QgsQueryResultWidget *resultWidget();
|
||||
%Docstring
|
||||
Returns the :py:class:`QgsQueryResultWidget` shown in the dialog.
|
||||
%End
|
||||
|
||||
virtual void closeEvent( QCloseEvent *event );
|
||||
|
||||
|
||||
};
|
||||
|
||||
class QgsQueryResultMainWindow : QMainWindow
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A main window which allows users to enter and run an SQL query on a
|
||||
DB connection (an instance of :py:class:`QgsAbstractDatabaseProviderConnection`).
|
||||
|
||||
.. note::
|
||||
|
||||
the ownership of the connection is transferred to the window.
|
||||
|
||||
.. seealso:: :py:class:`QgsQueryResultWidget`
|
||||
|
||||
.. versionadded:: 3.44
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsqueryresultwidget.h"
|
||||
%End
|
||||
public:
|
||||
QgsQueryResultMainWindow( QgsAbstractDatabaseProviderConnection *connection /Transfer/ = 0, const QString &identifierName = QString() );
|
||||
%Docstring
|
||||
Constructor for QgsQueryResultMainWindow.
|
||||
|
||||
Ownership of the ``connection`` is transferred to the window.
|
||||
%End
|
||||
|
||||
QgsQueryResultWidget *resultWidget();
|
||||
%Docstring
|
||||
Returns the :py:class:`QgsQueryResultWidget` shown in the window.
|
||||
%End
|
||||
|
||||
virtual void closeEvent( QCloseEvent *event );
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
||||
@ -2010,25 +2010,13 @@ void QgsDatabaseItemGuiProvider::openSqlDialog( const QString &connectionUri, co
|
||||
|
||||
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn( qgis::down_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( connectionUri, QVariantMap() ) ) );
|
||||
|
||||
// Create the SQL dialog: this might become an independent class dialog in the future, for now
|
||||
// we are still prototyping the features that this dialog will have.
|
||||
|
||||
QMainWindow *dialog = new QMainWindow();
|
||||
dialog->setObjectName( QStringLiteral( "SQLCommandsDialog" ) );
|
||||
if ( !identifierName.isEmpty() )
|
||||
dialog->setWindowTitle( tr( "%1 — Execute SQL" ).arg( identifierName ) );
|
||||
else
|
||||
dialog->setWindowTitle( tr( "Execute SQL" ) );
|
||||
|
||||
QgsGui::enableAutoGeometryRestore( dialog );
|
||||
QgsQueryResultMainWindow *dialog = new QgsQueryResultMainWindow( conn.release(), identifierName );
|
||||
dialog->setAttribute( Qt::WA_DeleteOnClose );
|
||||
dialog->setStyleSheet( QgisApp::instance()->styleSheet() );
|
||||
|
||||
QgsQueryResultWidget *widget { new QgsQueryResultWidget( nullptr, conn.release() ) };
|
||||
widget->setQuery( query );
|
||||
dialog->setCentralWidget( widget );
|
||||
dialog->resultWidget()->setQuery( query );
|
||||
|
||||
connect( widget, &QgsQueryResultWidget::createSqlVectorLayer, widget, [provider, connectionUri, context]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
connect( dialog->resultWidget(), &QgsQueryResultWidget::createSqlVectorLayer, dialog, [provider, connectionUri, context]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( provider ) };
|
||||
if ( !md )
|
||||
return;
|
||||
|
||||
@ -253,20 +253,32 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
|
||||
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { QgsMapLayerUtils::databaseConnection( layer ) };
|
||||
if ( conn2 )
|
||||
{
|
||||
QgsDialog dialog;
|
||||
dialog.setObjectName( QStringLiteral( "SqlUpdateDialog" ) );
|
||||
dialog.setWindowTitle( tr( "%1 — Update SQL" ).arg( layer->name() ) );
|
||||
QgsGui::enableAutoGeometryRestore( &dialog );
|
||||
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { conn2->sqlOptions( layer->source() ) };
|
||||
options.layerName = layer->name();
|
||||
QgsQueryResultWidget *queryResultWidget { new QgsQueryResultWidget( &dialog, conn2.release() ) };
|
||||
queryResultWidget->setWidgetMode( QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode );
|
||||
queryResultWidget->setSqlVectorLayerOptions( options );
|
||||
queryResultWidget->executeQuery();
|
||||
queryResultWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
|
||||
dialog.layout()->addWidget( queryResultWidget );
|
||||
|
||||
connect( queryResultWidget, &QgsQueryResultWidget::createSqlVectorLayer, queryResultWidget, [queryResultWidget, layer, this]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
QgsQueryResultDialog dialog( conn2.release() );
|
||||
dialog.setObjectName( QStringLiteral( "SqlUpdateDialog" ) );
|
||||
dialog.setStyleSheet( QgisApp::instance()->styleSheet() );
|
||||
|
||||
const QString layerName = layer->name();
|
||||
dialog.setWindowTitle( tr( "%1 — Update SQL" ).arg( layerName ) );
|
||||
QgsGui::enableAutoGeometryRestore( &dialog );
|
||||
dialog.resultWidget()->setWidgetMode( QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode );
|
||||
dialog.resultWidget()->setSqlVectorLayerOptions( options );
|
||||
dialog.resultWidget()->executeQuery();
|
||||
|
||||
connect( dialog.resultWidget(), &QgsQueryResultWidget::requestDialogTitleUpdate, &dialog, [&dialog, layerName]( const QString &fileName ) {
|
||||
if ( fileName.isEmpty() )
|
||||
{
|
||||
dialog.setWindowTitle( tr( "%1 — Update SQL" ).arg( layerName ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
dialog.setWindowTitle( tr( "%1 — %2 — Update SQL" ).arg( fileName, layerName ) );
|
||||
}
|
||||
} );
|
||||
|
||||
connect( dialog.resultWidget(), &QgsQueryResultWidget::createSqlVectorLayer, &dialog, [&dialog, layer, this]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
( void ) this;
|
||||
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn3 { QgsMapLayerUtils::databaseConnection( layer ) };
|
||||
if ( conn3 )
|
||||
@ -277,7 +289,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
|
||||
if ( sqlLayer->isValid() )
|
||||
{
|
||||
layer->setDataSource( sqlLayer->source(), sqlLayer->name(), sqlLayer->dataProvider()->name(), QgsDataProvider::ProviderOptions() );
|
||||
queryResultWidget->notify( QObject::tr( "Layer Update Success" ), QObject::tr( "The SQL layer was updated successfully" ), Qgis::MessageLevel::Success );
|
||||
dialog.resultWidget()->notify( QObject::tr( "Layer Update Success" ), QObject::tr( "The SQL layer was updated successfully" ), Qgis::MessageLevel::Success );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -286,12 +298,12 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
|
||||
{
|
||||
error = QObject::tr( "layer is not valid, check the log messages for more information" );
|
||||
}
|
||||
queryResultWidget->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( error ), Qgis::MessageLevel::Critical );
|
||||
dialog.resultWidget()->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( error ), Qgis::MessageLevel::Critical );
|
||||
}
|
||||
}
|
||||
catch ( QgsProviderConnectionException &ex )
|
||||
{
|
||||
queryResultWidget->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( ex.what() ), Qgis::MessageLevel::Critical );
|
||||
dialog.resultWidget()->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( ex.what() ), Qgis::MessageLevel::Critical );
|
||||
}
|
||||
}
|
||||
} );
|
||||
@ -306,19 +318,28 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
|
||||
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { QgsMapLayerUtils::databaseConnection( layer ) };
|
||||
if ( conn2 )
|
||||
{
|
||||
QgsDialog dialog;
|
||||
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { conn2->sqlOptions( layer->source() ) };
|
||||
|
||||
QgsQueryResultDialog dialog( conn2.release() );
|
||||
dialog.setObjectName( QStringLiteral( "SqlExecuteDialog" ) );
|
||||
dialog.setStyleSheet( QgisApp::instance()->styleSheet() );
|
||||
dialog.setWindowTitle( tr( "Execute SQL" ) );
|
||||
QgsGui::enableAutoGeometryRestore( &dialog );
|
||||
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { conn2->sqlOptions( layer->source() ) };
|
||||
QgsQueryResultWidget *queryResultWidget { new QgsQueryResultWidget( &dialog, conn2.release() ) };
|
||||
queryResultWidget->setSqlVectorLayerOptions( options );
|
||||
queryResultWidget->executeQuery();
|
||||
queryResultWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
|
||||
dialog.layout()->addWidget( queryResultWidget );
|
||||
dialog.setStyleSheet( QgisApp::instance()->styleSheet() );
|
||||
dialog.resultWidget()->setSqlVectorLayerOptions( options );
|
||||
dialog.resultWidget()->executeQuery();
|
||||
|
||||
connect( queryResultWidget, &QgsQueryResultWidget::createSqlVectorLayer, queryResultWidget, [queryResultWidget, layer, this]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
connect( dialog.resultWidget(), &QgsQueryResultWidget::requestDialogTitleUpdate, &dialog, [&dialog]( const QString &fileName ) {
|
||||
if ( fileName.isEmpty() )
|
||||
{
|
||||
dialog.setWindowTitle( tr( "Execute SQL" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
dialog.setWindowTitle( tr( "%1 — Execute SQL" ).arg( fileName ) );
|
||||
}
|
||||
} );
|
||||
|
||||
connect( dialog.resultWidget(), &QgsQueryResultWidget::createSqlVectorLayer, &dialog, [&dialog, layer, this]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options ) {
|
||||
( void ) this;
|
||||
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn3 { QgsMapLayerUtils::databaseConnection( layer ) };
|
||||
if ( conn3 )
|
||||
@ -330,7 +351,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
|
||||
}
|
||||
catch ( QgsProviderConnectionException &ex )
|
||||
{
|
||||
queryResultWidget->notify( QObject::tr( "New SQL Layer Creation Error" ), QObject::tr( "Error creating the SQL layer: %1" ).arg( ex.what() ), Qgis::MessageLevel::Critical );
|
||||
dialog.resultWidget()->notify( QObject::tr( "New SQL Layer Creation Error" ), QObject::tr( "Error creating the SQL layer: %1" ).arg( ex.what() ), Qgis::MessageLevel::Critical );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
@ -28,9 +28,16 @@
|
||||
#include "qgsproviderregistry.h"
|
||||
#include "qgsprovidermetadata.h"
|
||||
#include "qgscodeeditorwidget.h"
|
||||
#include "qgsfileutils.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QShortcut>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
///@cond PRIVATE
|
||||
const QgsSettingsEntryString *QgsQueryResultWidget::settingLastSourceFolder = new QgsSettingsEntryString( QStringLiteral( "last-source-folder" ), sTreeSqlQueries, QString(), QStringLiteral( "Last used folder for SQL source files" ) );
|
||||
///@endcond PRIVATE
|
||||
|
||||
QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabaseProviderConnection *connection )
|
||||
: QWidget( parent )
|
||||
@ -56,6 +63,10 @@ QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabase
|
||||
vl->addWidget( mCodeEditorWidget );
|
||||
mSqlEditorContainer->setLayout( vl );
|
||||
|
||||
connect( mActionOpenQuery, &QAction::triggered, this, &QgsQueryResultWidget::openQuery );
|
||||
connect( mActionSaveQuery, &QAction::triggered, this, [this] { saveQuery( false ); } );
|
||||
connect( mActionSaveQueryAs, &QAction::triggered, this, [this] { saveQuery( true ); } );
|
||||
|
||||
connect( mActionCut, &QAction::triggered, mSqlEditor, &QgsCodeEditor::cut );
|
||||
connect( mActionCopy, &QAction::triggered, mSqlEditor, &QgsCodeEditor::copy );
|
||||
connect( mActionPaste, &QAction::triggered, mSqlEditor, &QgsCodeEditor::paste );
|
||||
@ -66,6 +77,7 @@ QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabase
|
||||
|
||||
connect( mActionFindReplace, &QAction::toggled, mCodeEditorWidget, &QgsCodeEditorWidget::setSearchBarVisible );
|
||||
connect( mCodeEditorWidget, &QgsCodeEditorWidget::searchBarToggled, mActionFindReplace, &QAction::setChecked );
|
||||
connect( mSqlEditor, &QgsCodeEditor::modificationChanged, this, &QgsQueryResultWidget::setHasChanged );
|
||||
|
||||
connect( mExecuteButton, &QPushButton::pressed, this, &QgsQueryResultWidget::executeQuery );
|
||||
|
||||
@ -139,6 +151,7 @@ QgsQueryResultWidget::QgsQueryResultWidget( QWidget *parent, QgsAbstractDatabase
|
||||
connect( copySelection, &QShortcut::activated, this, &QgsQueryResultWidget::copySelection );
|
||||
|
||||
setConnection( connection );
|
||||
setHasChanged( false );
|
||||
}
|
||||
|
||||
QgsQueryResultWidget::~QgsQueryResultWidget()
|
||||
@ -517,9 +530,72 @@ void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn,
|
||||
}
|
||||
}
|
||||
|
||||
void QgsQueryResultWidget::openQuery()
|
||||
{
|
||||
if ( !mCodeEditorWidget->filePath().isEmpty() && mHasChangedFileContents )
|
||||
{
|
||||
if ( QMessageBox::warning( this, tr( "Unsaved Changes" ), tr( "There are unsaved changes in the query. Continue?" ), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::No ) == QMessageBox::StandardButton::No )
|
||||
return;
|
||||
}
|
||||
|
||||
QString initialDir = settingLastSourceFolder->value();
|
||||
if ( initialDir.isEmpty() )
|
||||
initialDir = QDir::homePath();
|
||||
|
||||
const QString fileName = QFileDialog::getOpenFileName( this, tr( "Open Query" ), initialDir, tr( "SQL queries (*.sql *.SQL)" ) + QStringLiteral( ";;" ) + QObject::tr( "All files" ) + QStringLiteral( " (*.*)" ) );
|
||||
|
||||
if ( fileName.isEmpty() )
|
||||
return;
|
||||
|
||||
QFileInfo fi( fileName );
|
||||
settingLastSourceFolder->setValue( fi.path() );
|
||||
|
||||
QgsTemporaryCursorOverride cursor( Qt::CursorShape::WaitCursor );
|
||||
|
||||
mCodeEditorWidget->loadFile( fileName );
|
||||
setHasChanged( false );
|
||||
}
|
||||
|
||||
void QgsQueryResultWidget::saveQuery( bool saveAs )
|
||||
{
|
||||
if ( mCodeEditorWidget->filePath().isEmpty() || saveAs )
|
||||
{
|
||||
QString selectedFilter;
|
||||
|
||||
QString initialDir = settingLastSourceFolder->value();
|
||||
if ( initialDir.isEmpty() )
|
||||
initialDir = QDir::homePath();
|
||||
|
||||
QString newPath = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
tr( "Save Query" ),
|
||||
initialDir,
|
||||
tr( "SQL queries (*.sql *.SQL)" ) + QStringLiteral( ";;" ) + QObject::tr( "All files" ) + QStringLiteral( " (*.*)" ),
|
||||
&selectedFilter
|
||||
);
|
||||
|
||||
if ( !newPath.isEmpty() )
|
||||
{
|
||||
QFileInfo fi( newPath );
|
||||
settingLastSourceFolder->setValue( fi.path() );
|
||||
|
||||
if ( !selectedFilter.contains( QStringLiteral( "*.*)" ) ) )
|
||||
newPath = QgsFileUtils::ensureFileNameHasExtension( newPath, { QStringLiteral( "sql" ) } );
|
||||
mCodeEditorWidget->save( newPath );
|
||||
setHasChanged( false );
|
||||
}
|
||||
}
|
||||
else if ( !mCodeEditorWidget->filePath().isEmpty() )
|
||||
{
|
||||
mCodeEditorWidget->save();
|
||||
setHasChanged( false );
|
||||
}
|
||||
}
|
||||
|
||||
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultWidget::sqlVectorLayerOptions() const
|
||||
{
|
||||
mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( QRegularExpression( ";\\s*$" ), QString() );
|
||||
const thread_local QRegularExpression rx( QStringLiteral( ";\\s*$" ) );
|
||||
mSqlVectorLayerOptions.sql = mSqlEditor->text().replace( rx, QString() );
|
||||
mSqlVectorLayerOptions.filter = mFilterLineEdit->text();
|
||||
mSqlVectorLayerOptions.primaryKeyColumns = mPkColumnsComboBox->checkedItems();
|
||||
mSqlVectorLayerOptions.geometryColumn = mGeometryColumnComboBox->currentText();
|
||||
@ -586,12 +662,73 @@ void QgsQueryResultWidget::setQuery( const QString &sql )
|
||||
mActionRedo->setEnabled( false );
|
||||
}
|
||||
|
||||
|
||||
bool QgsQueryResultWidget::promptUnsavedChanges()
|
||||
{
|
||||
if ( !mCodeEditorWidget->filePath().isEmpty() && mHasChangedFileContents )
|
||||
{
|
||||
const QMessageBox::StandardButton ret = QMessageBox::question(
|
||||
this,
|
||||
tr( "Save Query?" ),
|
||||
tr(
|
||||
"There are unsaved changes in this query. Do you want to save those?"
|
||||
),
|
||||
QMessageBox::StandardButton::Save
|
||||
| QMessageBox::StandardButton::Cancel
|
||||
| QMessageBox::StandardButton::Discard,
|
||||
QMessageBox::StandardButton::Cancel
|
||||
);
|
||||
|
||||
if ( ret == QMessageBox::StandardButton::Save )
|
||||
{
|
||||
saveQuery( false );
|
||||
return true;
|
||||
}
|
||||
else if ( ret == QMessageBox::StandardButton::Discard )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QgsQueryResultWidget::notify( const QString &title, const QString &text, Qgis::MessageLevel level )
|
||||
{
|
||||
mMessageBar->pushMessage( title, text, level );
|
||||
}
|
||||
|
||||
|
||||
void QgsQueryResultWidget::setHasChanged( bool hasChanged )
|
||||
{
|
||||
mHasChangedFileContents = hasChanged;
|
||||
mActionSaveQuery->setEnabled( hasChanged );
|
||||
updateDialogTitle();
|
||||
}
|
||||
|
||||
void QgsQueryResultWidget::updateDialogTitle()
|
||||
{
|
||||
QString fileName;
|
||||
if ( !mCodeEditorWidget->filePath().isEmpty() )
|
||||
{
|
||||
const QFileInfo fi( mCodeEditorWidget->filePath() );
|
||||
fileName = fi.fileName();
|
||||
if ( mHasChangedFileContents )
|
||||
{
|
||||
fileName.prepend( '*' );
|
||||
}
|
||||
}
|
||||
|
||||
emit requestDialogTitleUpdate( fileName );
|
||||
}
|
||||
|
||||
///@cond private
|
||||
|
||||
void QgsConnectionsApiFetcher::fetchTokens()
|
||||
@ -732,3 +869,81 @@ QString QgsQueryResultItemDelegate::displayText( const QVariant &value, const QL
|
||||
}
|
||||
|
||||
///@endcond private
|
||||
|
||||
//
|
||||
// QgsQueryResultDialog
|
||||
//
|
||||
|
||||
QgsQueryResultDialog::QgsQueryResultDialog( QgsAbstractDatabaseProviderConnection *connection, QWidget *parent )
|
||||
: QDialog( parent )
|
||||
{
|
||||
setObjectName( QStringLiteral( "QgsQueryResultDialog" ) );
|
||||
QgsGui::enableAutoGeometryRestore( this );
|
||||
|
||||
mWidget = new QgsQueryResultWidget( this, connection );
|
||||
QVBoxLayout *l = new QVBoxLayout();
|
||||
l->setContentsMargins( 0, 0, 0, 0 );
|
||||
l->addWidget( mWidget );
|
||||
setLayout( l );
|
||||
}
|
||||
|
||||
void QgsQueryResultDialog::closeEvent( QCloseEvent *event )
|
||||
{
|
||||
if ( !mWidget->promptUnsavedChanges() )
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// QgsQueryResultMainWindow
|
||||
//
|
||||
|
||||
QgsQueryResultMainWindow::QgsQueryResultMainWindow( QgsAbstractDatabaseProviderConnection *connection, const QString &identifierName )
|
||||
: mIdentifierName( identifierName )
|
||||
{
|
||||
setObjectName( QStringLiteral( "SQLCommandsDialog" ) );
|
||||
|
||||
QgsGui::enableAutoGeometryRestore( this );
|
||||
|
||||
mWidget = new QgsQueryResultWidget( nullptr, connection );
|
||||
setCentralWidget( mWidget );
|
||||
|
||||
connect( mWidget, &QgsQueryResultWidget::requestDialogTitleUpdate, this, &QgsQueryResultMainWindow::updateWindowTitle );
|
||||
|
||||
updateWindowTitle( QString() );
|
||||
}
|
||||
|
||||
void QgsQueryResultMainWindow::closeEvent( QCloseEvent *event )
|
||||
{
|
||||
if ( !mWidget->promptUnsavedChanges() )
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsQueryResultMainWindow::updateWindowTitle( const QString &fileName )
|
||||
{
|
||||
if ( fileName.isEmpty() )
|
||||
{
|
||||
if ( !mIdentifierName.isEmpty() )
|
||||
setWindowTitle( tr( "%1 — Execute SQL" ).arg( mIdentifierName ) );
|
||||
else
|
||||
setWindowTitle( tr( "Execute SQL" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !mIdentifierName.isEmpty() )
|
||||
setWindowTitle( tr( "%1 — %2 — Execute SQL" ).arg( fileName, mIdentifierName ) );
|
||||
else
|
||||
setWindowTitle( tr( "%1 — Execute SQL" ).arg( fileName ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
#include <QThread>
|
||||
#include <QtConcurrent>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QDialog>
|
||||
#include <QMainWindow>
|
||||
|
||||
class QgsCodeEditorWidget;
|
||||
|
||||
@ -108,6 +110,13 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
#ifndef SIP_RUN
|
||||
///@cond PRIVATE
|
||||
static inline QgsSettingsTreeNode *sTreeSqlQueries = QgsSettingsTree::sTreeGui->createChildNode( QStringLiteral( "sql-queries" ) );
|
||||
static const QgsSettingsEntryString *settingLastSourceFolder;
|
||||
///@endcond PRIVATE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief The QueryWidgetMode enum represents various modes for the widget appearance.
|
||||
*/
|
||||
@ -145,6 +154,8 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
*/
|
||||
void setQuery( const QString &sql );
|
||||
|
||||
SIP_SKIP bool promptUnsavedChanges();
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
@ -198,6 +209,8 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
*/
|
||||
void firstResultBatchFetched();
|
||||
|
||||
SIP_SKIP void requestDialogTitleUpdate( const QString &filename );
|
||||
|
||||
private slots:
|
||||
|
||||
/**
|
||||
@ -206,8 +219,10 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
void updateButtons();
|
||||
|
||||
void showCellContextMenu( QPoint point );
|
||||
|
||||
void copySelection();
|
||||
void openQuery();
|
||||
void saveQuery( bool saveAs );
|
||||
void setHasChanged( bool hasChanged );
|
||||
|
||||
private:
|
||||
QgsCodeEditorWidget *mCodeEditorWidget = nullptr;
|
||||
@ -229,6 +244,8 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
QueryWidgetMode mQueryWidgetMode = QueryWidgetMode::SqlQueryMode;
|
||||
long long mCurrentHistoryEntryId = -1;
|
||||
|
||||
bool mHasChangedFileContents = false;
|
||||
|
||||
/**
|
||||
* Updates SQL layer columns.
|
||||
*/
|
||||
@ -254,8 +271,81 @@ class GUI_EXPORT QgsQueryResultWidget : public QWidget, private Ui::QgsQueryResu
|
||||
*/
|
||||
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions sqlVectorLayerOptions() const;
|
||||
|
||||
void updateDialogTitle();
|
||||
|
||||
|
||||
friend class TestQgsQueryResultWidget;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
* \brief A dialog which allows users to enter and run an SQL query on a
|
||||
* DB connection (an instance of QgsAbstractDatabaseProviderConnection).
|
||||
*
|
||||
* \note the ownership of the connection is transferred to the dialog.
|
||||
*
|
||||
* \see QgsQueryResultWidget
|
||||
*
|
||||
* \since QGIS 3.44
|
||||
*/
|
||||
class GUI_EXPORT QgsQueryResultDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for QgsQueryResultDialog.
|
||||
*
|
||||
* Ownership of the \a connection is transferred to the dialog.
|
||||
*/
|
||||
QgsQueryResultDialog( QgsAbstractDatabaseProviderConnection *connection SIP_TRANSFER = nullptr, QWidget *parent = nullptr );
|
||||
|
||||
/**
|
||||
* Returns the QgsQueryResultWidget shown in the dialog.
|
||||
*/
|
||||
QgsQueryResultWidget *resultWidget() { return mWidget; }
|
||||
|
||||
void closeEvent( QCloseEvent *event ) override;
|
||||
|
||||
private:
|
||||
QgsQueryResultWidget *mWidget = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
* \brief A main window which allows users to enter and run an SQL query on a
|
||||
* DB connection (an instance of QgsAbstractDatabaseProviderConnection).
|
||||
*
|
||||
* \note the ownership of the connection is transferred to the window.
|
||||
*
|
||||
* \see QgsQueryResultWidget
|
||||
*
|
||||
* \since QGIS 3.44
|
||||
*/
|
||||
class GUI_EXPORT QgsQueryResultMainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for QgsQueryResultMainWindow.
|
||||
*
|
||||
* Ownership of the \a connection is transferred to the window.
|
||||
*/
|
||||
QgsQueryResultMainWindow( QgsAbstractDatabaseProviderConnection *connection SIP_TRANSFER = nullptr, const QString &identifierName = QString() );
|
||||
|
||||
/**
|
||||
* Returns the QgsQueryResultWidget shown in the window.
|
||||
*/
|
||||
QgsQueryResultWidget *resultWidget() { return mWidget; }
|
||||
|
||||
void closeEvent( QCloseEvent *event ) override;
|
||||
|
||||
private:
|
||||
QgsQueryResultWidget *mWidget = nullptr;
|
||||
QString mIdentifierName;
|
||||
|
||||
void updateWindowTitle( const QString &fileName );
|
||||
};
|
||||
|
||||
#endif // QGSQUERYRESULTWIDGET_H
|
||||
|
||||
@ -39,6 +39,10 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolBar" name="mToolBar">
|
||||
<addaction name="mActionOpenQuery"/>
|
||||
<addaction name="mActionSaveQuery"/>
|
||||
<addaction name="mActionSaveQueryAs"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="mActionCut"/>
|
||||
<addaction name="mActionCopy"/>
|
||||
<addaction name="mActionPaste"/>
|
||||
@ -350,6 +354,51 @@
|
||||
<enum>QAction::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionOpenQuery">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open Query…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open Query</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionSaveQuery">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileSave.svg</normaloff>:/images/themes/default/mActionFileSave.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Query…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Save Query</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionSaveQueryAs">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileSaveAs.svg</normaloff>:/images/themes/default/mActionFileSaveAs.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Query as…</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Save Query as</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+S</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user