Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Allow a range of cells to be copied from SQL results window
Allows selection of a range of cells to copy to clipboard. Results
are copied as both plain text and html, so can be pasted easily
into spreadsheet apps/etc as tables
  • Loading branch information
nyalldawson committed Apr 18, 2023
1 parent 4d11993 commit 7a0c9a3
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 2 deletions.
14 changes: 14 additions & 0 deletions python/gui/auto_generated/qgsqueryresultwidget.sip.in
Expand Up @@ -91,6 +91,20 @@ in the SQL error panel is ``isSqlError`` is set.
void tokensReady( const QStringList &tokens );
%Docstring
Triggered when the threaded API fetcher has new ``tokens`` to add.
%End

void copyResults();
%Docstring
Copies the query results to the clipboard, as a formatted table.

.. versionadded:: 3.32
%End

void copyResults( int fromRow, int toRow, int fromColumn, int toColumn );
%Docstring
Copies a range of the query results to the clipboard, as a formatted table.

.. versionadded:: 3.32
%End

signals:
Expand Down
86 changes: 84 additions & 2 deletions src/gui/qgsqueryresultwidget.cpp
Expand Up @@ -228,8 +228,33 @@ void QgsQueryResultWidget::showCellContextMenu( QPoint point )

menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, [ = ]
{
const QString text = mModel->data( modelIndex, Qt::DisplayRole ).toString();
QApplication::clipboard()->setText( text );
const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
int minRow = -1;
int maxRow = -1;
int minCol = -1;
int maxCol = -1;
for ( const QModelIndex &index : selection )
{
if ( minRow == -1 || index.row() < minRow )
minRow = index.row();
if ( maxRow == -1 || index.row() > maxRow )
maxRow = index.row();
if ( minCol == -1 || index.column() < minCol )
minCol = index.column();
if ( maxCol == -1 || index.column() > maxCol )
maxCol = index.column();
}

if ( minRow == maxRow && minCol == maxCol )
{
// copy only one cell
const QString text = mModel->data( modelIndex, Qt::DisplayRole ).toString();
QApplication::clipboard()->setText( text );
}
else
{
copyResults( minRow, maxRow, minCol, maxCol );
}
}, QKeySequence::Copy );

menu->exec( mQueryResultsTableView->viewport()->mapToGlobal( point ) );
Expand Down Expand Up @@ -388,6 +413,63 @@ void QgsQueryResultWidget::tokensReady( const QStringList &tokens )
mSqlErrorText->setExtraKeywords( mSqlErrorText->extraKeywords() + tokens );
}

void QgsQueryResultWidget::copyResults()
{
const int rowCount = mModel->rowCount( QModelIndex() );
const int columnCount = mModel->columnCount( QModelIndex() );
copyResults( 0, rowCount - 1, 0, columnCount - 1 );
}

void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
{
QStringList rowStrings;
QStringList columnStrings;

const int rowCount = mModel->rowCount( QModelIndex() );
const int columnCount = mModel->columnCount( QModelIndex() );

toRow = std::min( toRow, rowCount - 1 );
toColumn = std::min( toColumn, columnCount - 1 );

rowStrings.reserve( toRow - fromRow );

// add titles first
for ( int col = fromColumn; col <= toColumn; col++ )
{
columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
}
rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
columnStrings.clear();

for ( int row = fromRow; row <= toRow; row++ )
{
for ( int col = fromColumn; col <= toColumn; col++ )
{
columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
}
rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
columnStrings.clear();
}

if ( !rowStrings.isEmpty() )
{
const QString text = rowStrings.join( QLatin1Char( '\n' ) );
QString html = QStringLiteral( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/></head><body><table border=\"1\"><tr><td>%1</td></tr></table></body></html>" ).arg( text );
html.replace( QLatin1String( "\t" ), QLatin1String( "</td><td>" ) ).replace( QLatin1String( "\n" ), QLatin1String( "</td></tr><tr><td>" ) );

QMimeData *mdata = new QMimeData();
mdata->setData( QStringLiteral( "text/html" ), html.toUtf8() );
if ( !text.isEmpty() )
{
mdata->setText( text );
}
// Transfers ownership to the clipboard object
#ifdef Q_OS_LINUX
QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
#endif
QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
}
}

QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultWidget::sqlVectorLayerOptions() const
{
Expand Down
14 changes: 14 additions & 0 deletions src/gui/qgsqueryresultwidget.h
Expand Up @@ -170,6 +170,20 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
*/
void tokensReady( const QStringList &tokens );

/**
* Copies the query results to the clipboard, as a formatted table.
*
* \since QGIS 3.32
*/
void copyResults();

/**
* Copies a range of the query results to the clipboard, as a formatted table.
*
* \since QGIS 3.32
*/
void copyResults( int fromRow, int toRow, int fromColumn, int toColumn );

signals:

/**
Expand Down

0 comments on commit 7a0c9a3

Please sign in to comment.