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 | |
49 | DataOutputWidget::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 | |
107 | DataOutputWidget::~DataOutputWidget() |
108 | { |
109 | } |
110 | |
111 | |
112 | void 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 | |
132 | void 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 | |
151 | void DataOutputWidget::resizeColumnsToContents() |
152 | { |
153 | if (m_model->rowCount() == 0) |
154 | return; |
155 | |
156 | m_view->resizeColumnsToContents(); |
157 | } |
158 | |
159 | |
160 | void 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 | |
174 | void DataOutputWidget::slotToggleLocale() |
175 | { |
176 | m_model->setUseSystemLocale(!m_model->useSystemLocale()); |
177 | } |
178 | |
179 | |
180 | void 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 | |
201 | void 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 | |
284 | void 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 | |