1/*
2 Copyright (C) 2010 Marco Mentasti <marcomentasti@gmail.com>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*/
18
19#include "dataoutputwidget.h"
20#include "dataoutputmodel.h"
21#include "dataoutputview.h"
22#include "exportwizard.h"
23
24#include <kate/application.h>
25#include <kate/mainwindow.h>
26#include <kate/documentmanager.h>
27#include <ktexteditor/document.h>
28#include <ktexteditor/view.h>
29#include <kfiledialog.h>
30#include <kapplication.h>
31#include <ktoolbar.h>
32#include <ktoggleaction.h>
33#include <kaction.h>
34#include <kicon.h>
35#include <klocale.h>
36#include <kmessagebox.h>
37#include <kdebug.h>
38
39#include <qheaderview.h>
40#include <qlayout.h>
41#include <qsqlquery.h>
42#include <qsqlerror.h>
43#include <qsize.h>
44#include <qclipboard.h>
45#include <qtextstream.h>
46#include <qfile.h>
47#include <qtimer.h>
48
49DataOutputWidget::DataOutputWidget(QWidget *parent)
50: QWidget(parent)
51, m_model(new DataOutputModel(this))
52, m_view(new DataOutputView(this))
53, m_isEmpty(true)
54{
55 m_view->setModel(m_model);
56
57 QHBoxLayout *layout = new QHBoxLayout(this);
58 m_dataLayout = new QVBoxLayout();
59
60 KToolBar *toolbar = new KToolBar(this);
61 toolbar->setOrientation(Qt::Vertical);
62 toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
63 toolbar->setIconSize(QSize(16, 16));
64
65 /// TODO: disable actions if no results are displayed or selected
66
67 KAction *action;
68
69 action = new KAction( KIcon("distribute-horizontal-x"), i18nc("@action:intoolbar", "Resize columns to contents"), this);
70 toolbar->addAction(action);
71 connect(action, SIGNAL(triggered()), this, SLOT(resizeColumnsToContents()));
72
73 action = new KAction( KIcon("distribute-vertical-y"), i18nc("@action:intoolbar", "Resize rows to contents"), this);
74 toolbar->addAction(action);
75 connect(action, SIGNAL(triggered()), this, SLOT(resizeRowsToContents()));
76
77 action = new KAction( KIcon("edit-copy"), i18nc("@action:intoolbar", "Copy"), this);
78 toolbar->addAction(action);
79 m_view->addAction(action);
80 connect(action, SIGNAL(triggered()), this, SLOT(slotCopySelected()));
81
82 action = new KAction( KIcon("document-export-table"), i18nc("@action:intoolbar", "Export..."), this);
83 toolbar->addAction(action);
84 m_view->addAction(action);
85 connect(action, SIGNAL(triggered()), this, SLOT(slotExport()));
86
87 action = new KAction( KIcon("edit-clear"), i18nc("@action:intoolbar", "Clear"), this);
88 toolbar->addAction(action);
89 connect(action, SIGNAL(triggered()), this, SLOT(clearResults()));
90
91 toolbar->addSeparator();
92
93 KToggleAction *toggleAction = new KToggleAction( KIcon("applications-education-language"), i18nc("@action:intoolbar", "Use system locale"), this);
94 toolbar->addAction(toggleAction);
95 connect(toggleAction, SIGNAL(triggered()), this, SLOT(slotToggleLocale()));
96
97 m_dataLayout->addWidget(m_view);
98
99 layout->addWidget(toolbar);
100 layout->addLayout(m_dataLayout);
101 layout->setContentsMargins(0, 0, 0, 0);
102
103 setLayout(layout);
104}
105
106
107DataOutputWidget::~DataOutputWidget()
108{
109}
110
111
112void DataOutputWidget::showQueryResultSets(QSqlQuery &query)
113{
114 /// TODO: loop resultsets if > 1
115 /// NOTE from Qt Documentation:
116 /// When one of the statements is a non-select statement a count of affected rows
117 /// may be available instead of a result set.
118
119 if (!query.isSelect() || query.lastError().isValid())
120 return;
121
122 m_model->setQuery(query);
123
124 m_isEmpty = false;
125
126 QTimer::singleShot(0, this, SLOT(resizeColumnsToContents()));
127
128 raise();
129}
130
131
132void DataOutputWidget::clearResults()
133{
134 // avoid crash when calling QSqlQueryModel::clear() after removing connection from the QSqlDatabase list
135 if (m_isEmpty)
136 return;
137
138 m_model->clear();
139
140 m_isEmpty = true;
141
142 /// HACK needed to refresh headers. please correct if there's a better way
143 m_view->horizontalHeader()->hide();
144 m_view->verticalHeader()->hide();
145
146 m_view->horizontalHeader()->show();
147 m_view->verticalHeader()->show();
148}
149
150
151void DataOutputWidget::resizeColumnsToContents()
152{
153 if (m_model->rowCount() == 0)
154 return;
155
156 m_view->resizeColumnsToContents();
157}
158
159
160void DataOutputWidget::resizeRowsToContents()
161{
162 if (m_model->rowCount() == 0)
163 return;
164
165 m_view->resizeRowsToContents();
166
167 int h = m_view->rowHeight(0);
168
169 if (h > 0)
170 m_view->verticalHeader()->setDefaultSectionSize(h);
171}
172
173
174void DataOutputWidget::slotToggleLocale()
175{
176 m_model->setUseSystemLocale(!m_model->useSystemLocale());
177}
178
179
180void DataOutputWidget::slotCopySelected()
181{
182 if (m_model->rowCount() <= 0)
183 return;
184
185 while (m_model->canFetchMore())
186 m_model->fetchMore();
187
188 if (!m_view->selectionModel()->hasSelection())
189 m_view->selectAll();
190
191 QString text;
192 QTextStream stream(&text);
193
194 exportData(stream);
195
196 if (!text.isEmpty())
197 kapp->clipboard()->setText(text);
198}
199
200
201void DataOutputWidget::slotExport()
202{
203 if (m_model->rowCount() <= 0)
204 return;
205
206 while (m_model->canFetchMore())
207 m_model->fetchMore();
208
209 if (!m_view->selectionModel()->hasSelection())
210 m_view->selectAll();
211
212 ExportWizard wizard(this);
213
214 if (wizard.exec() != QDialog::Accepted)
215 return;
216
217 bool outputInDocument = wizard.field("outDocument").toBool();
218 bool outputInClipboard = wizard.field("outClipboard").toBool();
219 bool outputInFile = wizard.field("outFile").toBool();
220
221 bool exportColumnNames = wizard.field("exportColumnNames").toBool();
222 bool exportLineNumbers = wizard.field("exportLineNumbers").toBool();
223
224 Options opt = NoOptions;
225
226 if (exportColumnNames)
227 opt |= ExportColumnNames;
228 if (exportLineNumbers)
229 opt |= ExportLineNumbers;
230
231 bool quoteStrings = wizard.field("checkQuoteStrings").toBool();
232 bool quoteNumbers = wizard.field("checkQuoteNumbers").toBool();
233
234 QChar stringsQuoteChar = (quoteStrings) ? wizard.field("quoteStringsChar").toString().at(0) : '\0';
235 QChar numbersQuoteChar = (quoteNumbers) ? wizard.field("quoteNumbersChar").toString().at(0) : '\0';
236
237 QString fieldDelimiter = wizard.field("fieldDelimiter").toString();
238
239 if (outputInDocument)
240 {
241 Kate::MainWindow *mw = Kate::application()->activeMainWindow();
242 KTextEditor::View *kv = mw->activeView();
243
244 if (!kv)
245 return;
246
247 QString text;
248 QTextStream stream(&text);
249
250 exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
251
252 kv->insertText(text);
253 kv->setFocus();
254 }
255 else if (outputInClipboard)
256 {
257 QString text;
258 QTextStream stream(&text);
259
260 exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
261
262 kapp->clipboard()->setText(text);
263 }
264 else if (outputInFile)
265 {
266 QString url = wizard.field("outFileUrl").toString();
267 QFile data(url);
268 if (data.open(QFile::WriteOnly | QFile::Truncate))
269 {
270 QTextStream stream(&data);
271
272 exportData(stream, stringsQuoteChar, numbersQuoteChar, fieldDelimiter, opt);
273
274 stream.flush();
275 }
276 else
277 {
278 KMessageBox::error(this, i18nc("@info", "Unable to open file <filename>%1</filename>", url));
279 }
280 }
281}
282
283
284void DataOutputWidget::exportData(QTextStream &stream,
285 const QChar stringsQuoteChar,
286 const QChar numbersQuoteChar,
287 const QString fieldDelimiter,
288 const Options opt)
289{
290 QItemSelectionModel *selectionModel = m_view->selectionModel();
291
292 if (!selectionModel->hasSelection())
293 return;
294
295 QString fixedFieldDelimiter = fieldDelimiter;
296
297 /// FIXME: ugly workaround...
298 fixedFieldDelimiter = fixedFieldDelimiter.replace("\\t", "\t");
299 fixedFieldDelimiter = fixedFieldDelimiter.replace("\\r", "\r");
300 fixedFieldDelimiter = fixedFieldDelimiter.replace("\\n", "\n");
301
302 QTime t;
303 t.start();
304
305 QSet<int> columns;
306 QSet<int> rows;
307 QHash<QPair<int,int>,QString> snapshot;
308
309 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
310
311 snapshot.reserve(selectedIndexes.count());
312
313 foreach (const QModelIndex& index, selectedIndexes)
314 {
315 const QVariant data = index.data(Qt::UserRole);
316
317 const int col = index.column();
318 const int row = index.row();
319
320 if (!columns.contains(col))
321 columns.insert(col);
322 if (!rows.contains(row))
323 rows.insert(row);
324
325 if (data.type() < 7) // is numeric or boolean
326 {
327 if (numbersQuoteChar != '\0')
328 snapshot[qMakePair(row,col)] = numbersQuoteChar + data.toString() + numbersQuoteChar;
329 else
330 snapshot[qMakePair(row,col)] = data.toString();
331 }
332 else
333 {
334 if (stringsQuoteChar != '\0')
335 snapshot[qMakePair(row,col)] = stringsQuoteChar + data.toString() + stringsQuoteChar;
336 else
337 snapshot[qMakePair(row,col)] = data.toString();
338 }
339 }
340
341 if (opt.testFlag(ExportColumnNames))
342 {
343 if (opt.testFlag(ExportLineNumbers))
344 stream << fixedFieldDelimiter;
345
346 QSetIterator<int> j(columns);
347 while (j.hasNext())
348 {
349 const QVariant data = m_model->headerData(j.next(), Qt::Horizontal);
350
351 if (stringsQuoteChar != '\0')
352 stream << stringsQuoteChar + data.toString() + stringsQuoteChar;
353 else
354 stream << data.toString();
355
356 if (j.hasNext())
357 stream << fixedFieldDelimiter;
358 }
359 stream << "\n";
360 }
361
362 foreach(const int row, rows)
363 {
364 if (opt.testFlag(ExportLineNumbers))
365 stream << row + 1 << fixedFieldDelimiter;
366
367 QSetIterator<int> j(columns);
368 while (j.hasNext())
369 {
370 stream << snapshot.value(qMakePair(row,j.next()));
371
372 if (j.hasNext())
373 stream << fixedFieldDelimiter;
374 }
375 stream << "\n";
376 }
377
378 kDebug() << "Export in" << t.elapsed() << "msecs";
379}
380