File indexing completed on 2024-04-28 05:48:40

0001 /*
0002    SPDX-FileCopyrightText: 2010 Marco Mentasti <marcomentasti@gmail.com>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "dataoutputwidget.h"
0008 #include "dataoutputmodel.h"
0009 #include "dataoutputview.h"
0010 #include "exportwizard.h"
0011 
0012 #include <ktexteditor/application.h>
0013 #include <ktexteditor/document.h>
0014 #include <ktexteditor/editor.h>
0015 #include <ktexteditor/mainwindow.h>
0016 #include <ktexteditor/view.h>
0017 
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <KToggleAction>
0021 #include <KToolBar>
0022 #include <QAction>
0023 
0024 #include <QApplication>
0025 #include <QClipboard>
0026 #include <QElapsedTimer>
0027 #include <QFile>
0028 #include <QHeaderView>
0029 #include <QLayout>
0030 #include <QSize>
0031 #include <QSqlError>
0032 #include <QSqlQuery>
0033 #include <QStyle>
0034 #include <QTextStream>
0035 #include <QTime>
0036 #include <QTimer>
0037 
0038 DataOutputWidget::DataOutputWidget(QWidget *parent)
0039     : QWidget(parent)
0040     , m_model(new DataOutputModel(this))
0041     , m_view(new DataOutputView(this))
0042     , m_isEmpty(true)
0043 {
0044     m_view->setModel(m_model);
0045 
0046     QHBoxLayout *layout = new QHBoxLayout(this);
0047     m_dataLayout = new QVBoxLayout();
0048 
0049     KToolBar *toolbar = new KToolBar(this);
0050     toolbar->setOrientation(Qt::Vertical);
0051     toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0052 
0053     // ensure reasonable icons sizes, like e.g. the quick-open and co. icons
0054     // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more!
0055     const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this);
0056     toolbar->setIconSize(QSize(iconSize, iconSize));
0057 
0058     /// TODO: disable actions if no results are displayed or selected
0059 
0060     QAction *action;
0061 
0062     action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18nc("@action:intoolbar", "Resize columns to contents"), this);
0063     toolbar->addAction(action);
0064     connect(action, &QAction::triggered, this, &DataOutputWidget::resizeColumnsToContents);
0065 
0066     action = new QAction(QIcon::fromTheme(QStringLiteral("distribute-vertical-y")), i18nc("@action:intoolbar", "Resize rows to contents"), this);
0067     toolbar->addAction(action);
0068     connect(action, &QAction::triggered, this, &DataOutputWidget::resizeRowsToContents);
0069 
0070     action = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:intoolbar", "Copy"), this);
0071     toolbar->addAction(action);
0072     m_view->addAction(action);
0073     connect(action, &QAction::triggered, this, &DataOutputWidget::slotCopySelected);
0074 
0075     action = new QAction(QIcon::fromTheme(QStringLiteral("document-export-table")), i18nc("@action:intoolbar", "Export..."), this);
0076     toolbar->addAction(action);
0077     m_view->addAction(action);
0078     connect(action, &QAction::triggered, this, &DataOutputWidget::slotExport);
0079 
0080     action = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18nc("@action:intoolbar", "Clear"), this);
0081     toolbar->addAction(action);
0082     connect(action, &QAction::triggered, this, &DataOutputWidget::clearResults);
0083 
0084     toolbar->addSeparator();
0085 
0086     KToggleAction *toggleAction =
0087         new KToggleAction(QIcon::fromTheme(QStringLiteral("applications-education-language")), i18nc("@action:intoolbar", "Use system locale"), this);
0088     toolbar->addAction(toggleAction);
0089     connect(toggleAction, &QAction::triggered, this, &DataOutputWidget::slotToggleLocale);
0090 
0091     m_dataLayout->addWidget(m_view);
0092 
0093     layout->addWidget(toolbar);
0094     layout->addLayout(m_dataLayout);
0095     layout->setContentsMargins(0, 0, 0, 0);
0096 
0097     setLayout(layout);
0098 }
0099 
0100 DataOutputWidget::~DataOutputWidget()
0101 {
0102 }
0103 
0104 void DataOutputWidget::showQueryResultSets(QSqlQuery &query)
0105 {
0106     /// TODO: loop resultsets if > 1
0107     /// NOTE from Qt Documentation:
0108     /// When one of the statements is a non-select statement a count of affected rows
0109     /// may be available instead of a result set.
0110 
0111     if (!query.isSelect() || query.lastError().isValid()) {
0112         return;
0113     }
0114 
0115     m_model->setQuery(query);
0116 
0117     m_isEmpty = false;
0118 
0119     QTimer::singleShot(0, this, &DataOutputWidget::resizeColumnsToContents);
0120 
0121     raise();
0122 }
0123 
0124 void DataOutputWidget::clearResults()
0125 {
0126     // avoid crash when calling QSqlQueryModel::clear() after removing connection from the QSqlDatabase list
0127     if (m_isEmpty) {
0128         return;
0129     }
0130 
0131     m_model->clear();
0132 
0133     m_isEmpty = true;
0134 
0135     /// HACK needed to refresh headers. please correct if there's a better way
0136     m_view->horizontalHeader()->hide();
0137     m_view->verticalHeader()->hide();
0138 
0139     m_view->horizontalHeader()->show();
0140     m_view->verticalHeader()->show();
0141 }
0142 
0143 void DataOutputWidget::resizeColumnsToContents()
0144 {
0145     if (m_model->rowCount() == 0) {
0146         return;
0147     }
0148 
0149     m_view->resizeColumnsToContents();
0150 }
0151 
0152 void DataOutputWidget::resizeRowsToContents()
0153 {
0154     if (m_model->rowCount() == 0) {
0155         return;
0156     }
0157 
0158     m_view->resizeRowsToContents();
0159 
0160     int h = m_view->rowHeight(0);
0161 
0162     if (h > 0) {
0163         m_view->verticalHeader()->setDefaultSectionSize(h);
0164     }
0165 }
0166 
0167 void DataOutputWidget::slotToggleLocale()
0168 {
0169     m_model->setUseSystemLocale(!m_model->useSystemLocale());
0170 }
0171 
0172 void DataOutputWidget::slotCopySelected()
0173 {
0174     if (m_model->rowCount() <= 0) {
0175         return;
0176     }
0177 
0178     while (m_model->canFetchMore()) {
0179         m_model->fetchMore();
0180     }
0181 
0182     if (!m_view->selectionModel()->hasSelection()) {
0183         m_view->selectAll();
0184     }
0185 
0186     QString text;
0187     QTextStream stream(&text);
0188 
0189     exportData(stream);
0190 
0191     if (!text.isEmpty()) {
0192         QApplication::clipboard()->setText(text);
0193     }
0194 }
0195 
0196 void DataOutputWidget::slotExport()
0197 {
0198     if (m_model->rowCount() <= 0) {
0199         return;
0200     }
0201 
0202     while (m_model->canFetchMore()) {
0203         m_model->fetchMore();
0204     }
0205 
0206     if (!m_view->selectionModel()->hasSelection()) {
0207         m_view->selectAll();
0208     }
0209 
0210     ExportWizard wizard(this);
0211 
0212     if (wizard.exec() != QDialog::Accepted) {
0213         return;
0214     }
0215 
0216     bool outputInDocument = wizard.field(QStringLiteral("outDocument")).toBool();
0217     bool outputInClipboard = wizard.field(QStringLiteral("outClipboard")).toBool();
0218     bool outputInFile = wizard.field(QStringLiteral("outFile")).toBool();
0219 
0220     bool exportColumnNames = wizard.field(QStringLiteral("exportColumnNames")).toBool();
0221     bool exportLineNumbers = wizard.field(QStringLiteral("exportLineNumbers")).toBool();
0222 
0223     Options opt = NoOptions;
0224 
0225     if (exportColumnNames) {
0226         opt |= ExportColumnNames;
0227     }
0228     if (exportLineNumbers) {
0229         opt |= ExportLineNumbers;
0230     }
0231 
0232     bool quoteStrings = wizard.field(QStringLiteral("checkQuoteStrings")).toBool();
0233     bool quoteNumbers = wizard.field(QStringLiteral("checkQuoteNumbers")).toBool();
0234 
0235     QChar stringsQuoteChar = (quoteStrings) ? wizard.field(QStringLiteral("quoteStringsChar")).toString().at(0) : QLatin1Char('\0');
0236     QChar numbersQuoteChar = (quoteNumbers) ? wizard.field(QStringLiteral("quoteNumbersChar")).toString().at(0) : QLatin1Char('\0');
0237 
0238     QString fieldDelimiter = wizard.field(QStringLiteral("fieldDelimiter")).toString();
0239 
0240     if (outputInDocument) {
0241         KTextEditor::MainWindow *mw = KTextEditor::Editor::instance()->application()->activeMainWindow();
0242         KTextEditor::View *kv = mw->activeView();
0243 
0244         if (!kv) {
0245             return;
0246         }
0247 
0248         QString text;
0249         QTextStream stream(&text);
0250 
0251         exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
0252 
0253         kv->insertText(text);
0254         kv->setFocus();
0255     } else if (outputInClipboard) {
0256         QString text;
0257         QTextStream stream(&text);
0258 
0259         exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
0260 
0261         QApplication::clipboard()->setText(text);
0262     } else if (outputInFile) {
0263         QString url = wizard.field(QStringLiteral("outFileUrl")).toString();
0264         QFile data(url);
0265         if (data.open(QFile::WriteOnly | QFile::Truncate)) {
0266             QTextStream stream(&data);
0267 
0268             exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
0269 
0270             stream.flush();
0271         } else {
0272             KMessageBox::error(this, xi18nc("@info", "Unable to open file <filename>%1</filename>", url));
0273         }
0274     }
0275 }
0276 
0277 void DataOutputWidget::exportData(QTextStream &stream,
0278                                   const QChar stringsQuoteChar,
0279                                   const QChar numbersQuoteChar,
0280                                   const QString &fieldDelimiter,
0281                                   const Options opt)
0282 {
0283     QItemSelectionModel *selectionModel = m_view->selectionModel();
0284 
0285     if (!selectionModel->hasSelection()) {
0286         return;
0287     }
0288 
0289     QString fixedFieldDelimiter = fieldDelimiter;
0290 
0291     /// FIXME: ugly workaround...
0292     fixedFieldDelimiter.replace(QLatin1String("\\t"), QLatin1String("\t"));
0293     fixedFieldDelimiter.replace(QLatin1String("\\r"), QLatin1String("\r"));
0294     fixedFieldDelimiter.replace(QLatin1String("\\n"), QLatin1String("\n"));
0295 
0296     QElapsedTimer t;
0297     t.start();
0298 
0299     QSet<int> columns;
0300     QSet<int> rows;
0301     QHash<QPair<int, int>, QString> snapshot;
0302 
0303     const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
0304 
0305     snapshot.reserve(selectedIndexes.count());
0306 
0307     for (const QModelIndex &index : selectedIndexes) {
0308         const QVariant data = index.data(Qt::UserRole);
0309 
0310         const int col = index.column();
0311         const int row = index.row();
0312 
0313         if (!columns.contains(col)) {
0314             columns.insert(col);
0315         }
0316         if (!rows.contains(row)) {
0317             rows.insert(row);
0318         }
0319 
0320         if (data.type() < 7) // is numeric or boolean
0321         {
0322             if (numbersQuoteChar != QLatin1Char('\0')) {
0323                 snapshot[qMakePair(row, col)] = numbersQuoteChar + data.toString() + numbersQuoteChar;
0324             } else {
0325                 snapshot[qMakePair(row, col)] = data.toString();
0326             }
0327         } else {
0328             if (stringsQuoteChar != QLatin1Char('\0')) {
0329                 snapshot[qMakePair(row, col)] = stringsQuoteChar + data.toString() + stringsQuoteChar;
0330             } else {
0331                 snapshot[qMakePair(row, col)] = data.toString();
0332             }
0333         }
0334     }
0335 
0336     if (opt.testFlag(ExportColumnNames)) {
0337         if (opt.testFlag(ExportLineNumbers)) {
0338             stream << fixedFieldDelimiter;
0339         }
0340 
0341         QSetIterator<int> j(columns);
0342         while (j.hasNext()) {
0343             const QVariant data = m_model->headerData(j.next(), Qt::Horizontal);
0344 
0345             if (stringsQuoteChar != QLatin1Char('\0')) {
0346                 stream << stringsQuoteChar + data.toString() + stringsQuoteChar;
0347             } else {
0348                 stream << data.toString();
0349             }
0350 
0351             if (j.hasNext()) {
0352                 stream << fixedFieldDelimiter;
0353             }
0354         }
0355         stream << "\n";
0356     }
0357 
0358     for (const int row : qAsConst(rows)) {
0359         if (opt.testFlag(ExportLineNumbers)) {
0360             stream << row + 1 << fixedFieldDelimiter;
0361         }
0362 
0363         QSetIterator<int> j(columns);
0364         while (j.hasNext()) {
0365             stream << snapshot.value(qMakePair(row, j.next()));
0366 
0367             if (j.hasNext()) {
0368                 stream << fixedFieldDelimiter;
0369             }
0370         }
0371         stream << "\n";
0372     }
0373 
0374     qDebug() << "Export in" << t.elapsed() << "msecs";
0375 }
0376 
0377 #include "moc_dataoutputwidget.cpp"