Skip to content

Commit 7a0c9a3

Browse files
committedApr 18, 2023
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
1 parent 4d11993 commit 7a0c9a3

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed
 

‎python/gui/auto_generated/qgsqueryresultwidget.sip.in

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ in the SQL error panel is ``isSqlError`` is set.
9191
void tokensReady( const QStringList &tokens );
9292
%Docstring
9393
Triggered when the threaded API fetcher has new ``tokens`` to add.
94+
%End
95+
96+
void copyResults();
97+
%Docstring
98+
Copies the query results to the clipboard, as a formatted table.
99+
100+
.. versionadded:: 3.32
101+
%End
102+
103+
void copyResults( int fromRow, int toRow, int fromColumn, int toColumn );
104+
%Docstring
105+
Copies a range of the query results to the clipboard, as a formatted table.
106+
107+
.. versionadded:: 3.32
94108
%End
95109

96110
signals:

‎src/gui/qgsqueryresultwidget.cpp

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,33 @@ void QgsQueryResultWidget::showCellContextMenu( QPoint point )
228228

229229
menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, [ = ]
230230
{
231-
const QString text = mModel->data( modelIndex, Qt::DisplayRole ).toString();
232-
QApplication::clipboard()->setText( text );
231+
const QModelIndexList selection = mQueryResultsTableView->selectionModel()->selectedIndexes();
232+
int minRow = -1;
233+
int maxRow = -1;
234+
int minCol = -1;
235+
int maxCol = -1;
236+
for ( const QModelIndex &index : selection )
237+
{
238+
if ( minRow == -1 || index.row() < minRow )
239+
minRow = index.row();
240+
if ( maxRow == -1 || index.row() > maxRow )
241+
maxRow = index.row();
242+
if ( minCol == -1 || index.column() < minCol )
243+
minCol = index.column();
244+
if ( maxCol == -1 || index.column() > maxCol )
245+
maxCol = index.column();
246+
}
247+
248+
if ( minRow == maxRow && minCol == maxCol )
249+
{
250+
// copy only one cell
251+
const QString text = mModel->data( modelIndex, Qt::DisplayRole ).toString();
252+
QApplication::clipboard()->setText( text );
253+
}
254+
else
255+
{
256+
copyResults( minRow, maxRow, minCol, maxCol );
257+
}
233258
}, QKeySequence::Copy );
234259

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

416+
void QgsQueryResultWidget::copyResults()
417+
{
418+
const int rowCount = mModel->rowCount( QModelIndex() );
419+
const int columnCount = mModel->columnCount( QModelIndex() );
420+
copyResults( 0, rowCount - 1, 0, columnCount - 1 );
421+
}
422+
423+
void QgsQueryResultWidget::copyResults( int fromRow, int toRow, int fromColumn, int toColumn )
424+
{
425+
QStringList rowStrings;
426+
QStringList columnStrings;
427+
428+
const int rowCount = mModel->rowCount( QModelIndex() );
429+
const int columnCount = mModel->columnCount( QModelIndex() );
430+
431+
toRow = std::min( toRow, rowCount - 1 );
432+
toColumn = std::min( toColumn, columnCount - 1 );
433+
434+
rowStrings.reserve( toRow - fromRow );
435+
436+
// add titles first
437+
for ( int col = fromColumn; col <= toColumn; col++ )
438+
{
439+
columnStrings += mModel->headerData( col, Qt::Horizontal, Qt::DisplayRole ).toString();
440+
}
441+
rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
442+
columnStrings.clear();
443+
444+
for ( int row = fromRow; row <= toRow; row++ )
445+
{
446+
for ( int col = fromColumn; col <= toColumn; col++ )
447+
{
448+
columnStrings += mModel->data( mModel->index( row, col ), Qt::DisplayRole ).toString();
449+
}
450+
rowStrings += columnStrings.join( QLatin1Char( '\t' ) );
451+
columnStrings.clear();
452+
}
453+
454+
if ( !rowStrings.isEmpty() )
455+
{
456+
const QString text = rowStrings.join( QLatin1Char( '\n' ) );
457+
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 );
458+
html.replace( QLatin1String( "\t" ), QLatin1String( "</td><td>" ) ).replace( QLatin1String( "\n" ), QLatin1String( "</td></tr><tr><td>" ) );
459+
460+
QMimeData *mdata = new QMimeData();
461+
mdata->setData( QStringLiteral( "text/html" ), html.toUtf8() );
462+
if ( !text.isEmpty() )
463+
{
464+
mdata->setText( text );
465+
}
466+
// Transfers ownership to the clipboard object
467+
#ifdef Q_OS_LINUX
468+
QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection );
469+
#endif
470+
QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard );
471+
}
472+
}
391473

392474
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsQueryResultWidget::sqlVectorLayerOptions() const
393475
{

‎src/gui/qgsqueryresultwidget.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
170170
*/
171171
void tokensReady( const QStringList &tokens );
172172

173+
/**
174+
* Copies the query results to the clipboard, as a formatted table.
175+
*
176+
* \since QGIS 3.32
177+
*/
178+
void copyResults();
179+
180+
/**
181+
* Copies a range of the query results to the clipboard, as a formatted table.
182+
*
183+
* \since QGIS 3.32
184+
*/
185+
void copyResults( int fromRow, int toRow, int fromColumn, int toColumn );
186+
173187
signals:
174188

175189
/**

0 commit comments

Comments
 (0)
Please sign in to comment.