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"