1 | /* |
2 | Copyright (C) 2014 by Elvis Angelaccio <elvis.angelaccio@kdemail.net> |
3 | |
4 | This file is part of Kronometer. |
5 | |
6 | Kronometer is free software: you can redistribute it and/or modify |
7 | it under the terms of the GNU General Public License as published by |
8 | the Free Software Foundation, either version 2 of the License, or |
9 | (at your option) any later version. |
10 | |
11 | Kronometer is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | GNU General Public License for more details. |
15 | |
16 | You should have received a copy of the GNU General Public License |
17 | along with Kronometer. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "mainwindow.h" |
21 | |
22 | #include <KApplication> |
23 | #include <KLocale> |
24 | #include <KAction> |
25 | #include <KActionCollection> |
26 | #include <KStatusBar> |
27 | #include <KConfigDialog> |
28 | #include <KFileDialog> |
29 | #include <KMessageBox> |
30 | #include <KIO/NetAccess> |
31 | #include <KSaveFile> |
32 | |
33 | #include <QTableView> |
34 | #include <QSplitter> |
35 | #include <QClipboard> |
36 | #include <QSortFilterProxyModel> |
37 | #include <QDomDocument> |
38 | #include <QPointer> |
39 | |
40 | #include "stopwatch.h" |
41 | #include "timedisplay.h" |
42 | #include "lapmodel.h" |
43 | #include "settings.h" |
44 | #include "widgets/generalsettings.h" |
45 | #include "widgets/fontsettings.h" |
46 | #include "widgets/colorsettings.h" |
47 | #include "widgets/savesettings.h" |
48 | #include "widgets/guisettings.h" |
49 | |
50 | namespace |
51 | { |
52 | const QString START_KEY = "start" ; |
53 | const QString PAUSE_KEY = "pause" ; |
54 | const QString RESET_KEY = "reset" ; |
55 | const QString LAP_KEY = "lap" ; |
56 | const QString EXPORT_KEY = "export_laps" ; |
57 | |
58 | const QString WINDOW_TITLE = "Kronometer" ; /** Default Window title */ |
59 | const QString QT_PLACE_HOLDER = "[*]" ; /** Qt standard placeholder for setWindowModified() */ |
60 | |
61 | const QString XML_MIMETYPE = "application/xml" ; |
62 | const QString CSV_MIMETYPE = "text/csv" ; |
63 | const QString XML_EXTENSION = ".xml" ; |
64 | const QString CSV_EXTENSION = ".csv" ; |
65 | |
66 | // kronometerui.rc states |
67 | const QString INACTIVE_STATE = "inactive" ; |
68 | const QString RUNNING_STATE = "running" ; |
69 | const QString PAUSED_STATE = "paused" ; |
70 | const QString PAUSED_FILE_STATE = "pausedFile" ; /** An open file has been paused */ |
71 | |
72 | // XML strings |
73 | const QString STOPWATCH_TAG = "stopwatch" ; |
74 | const QString LAPS_TAG = "laps" ; |
75 | const QString LAP_TAG = "lap" ; |
76 | const QString TIME_TAG = "time" ; |
77 | const QString ROOT_TAG = "kronometer" ; |
78 | const QString PERSISTENCE_ATTR = "msec" ; |
79 | const QString TYPE_ATTR = "type" ; |
80 | const QString LAP_ID_ATTR = "id" ; |
81 | const QString REL_TYPE = "relative" ; |
82 | const QString ABS_TYPE = "absolute" ; |
83 | } |
84 | |
85 | |
86 | MainWindow::MainWindow(QWidget *parent, const QString& file) : KXmlGuiWindow(parent), unsavedTimes(false) |
87 | { |
88 | stopwatch = new Stopwatch(this); |
89 | stopwatchDisplay = new TimeDisplay(this); |
90 | connect(stopwatch, SIGNAL(time(QTime)), stopwatchDisplay, SLOT(onTime(QTime))); // bind stopwatch to its display |
91 | |
92 | setupCentralWidget(); |
93 | setupStatusBar(); |
94 | setupActions(); |
95 | loadSettings(); |
96 | |
97 | setWindowTitle(WINDOW_TITLE + QT_PLACE_HOLDER); |
98 | |
99 | if (not file.isEmpty()) { |
100 | openFile(file); |
101 | } |
102 | } |
103 | |
104 | bool MainWindow::queryClose() |
105 | { |
106 | if (stopwatch->isInactive() or not KronometerConfig::askOnExit()) { |
107 | return true; // exit without ask |
108 | } |
109 | |
110 | if (stopwatch->isRunning()) { |
111 | stopwatch->onPause(); |
112 | paused(); |
113 | } |
114 | |
115 | int buttonCode; |
116 | |
117 | if (fileName.isEmpty()) { |
118 | buttonCode = KMessageBox::warningYesNoCancel(this, i18n("Save times on a new file?" )); |
119 | |
120 | switch (buttonCode) { |
121 | case KMessageBox::Yes: |
122 | return saveFileAs(); |
123 | case KMessageBox::No: |
124 | return true; |
125 | default: // cancel |
126 | return false; |
127 | } |
128 | } |
129 | else if (unsavedTimes) { |
130 | QFileInfo fileInfo(fileName); |
131 | buttonCode = KMessageBox::warningYesNoCancel(this, i18n("Save times to file %1?" , fileInfo.fileName())); |
132 | |
133 | switch (buttonCode) { |
134 | case KMessageBox::Yes: |
135 | // save document here. If saving fails, return false; |
136 | return saveFile(); |
137 | case KMessageBox::No: |
138 | return true; |
139 | default: // cancel |
140 | return false; |
141 | } |
142 | } |
143 | |
144 | return true; // there is an open file, but times are already saved. |
145 | } |
146 | |
147 | void MainWindow::running() |
148 | { |
149 | statusLabel->setText(i18n("Running..." )); |
150 | |
151 | unsavedTimes = true; |
152 | setWindowModified(unsavedTimes); |
153 | |
154 | stateChanged(RUNNING_STATE); |
155 | } |
156 | |
157 | void MainWindow::paused() |
158 | { |
159 | startAction->setText(i18n("Re&sume" )); |
160 | statusLabel->setText(i18n("Paused" )); |
161 | |
162 | if (not fileName.isEmpty()) { |
163 | stateChanged(PAUSED_FILE_STATE); |
164 | } |
165 | else { |
166 | stateChanged(PAUSED_STATE); |
167 | } |
168 | |
169 | // the export action can be used only if there are laps (in both the paused states). |
170 | // so, it can't be enabled directly from kronometerui.rc |
171 | if (not lapModel->isEmpty()) { |
172 | exportAction->setEnabled(true); |
173 | } |
174 | } |
175 | |
176 | void MainWindow::inactive() |
177 | { |
178 | startAction->setText(i18n("&Start" )); |
179 | statusLabel->setText(i18n("Inactive" )); |
180 | |
181 | unsavedTimes = false; |
182 | setWindowModified(unsavedTimes); |
183 | |
184 | stateChanged(INACTIVE_STATE); |
185 | } |
186 | |
187 | void MainWindow::showSettings() |
188 | { |
189 | if (KConfigDialog::showDialog("settings" )) { |
190 | return; |
191 | } |
192 | |
193 | KConfigDialog* dialog = new KConfigDialog(this, "settings" , KronometerConfig::self()); |
194 | |
195 | dialog->showButtonSeparator(true); |
196 | |
197 | KPageWidgetItem *generalPage = dialog->addPage(new GeneralSettings(this), i18n("General settings" )); |
198 | generalPage->setIcon(KIcon(KApplication::windowIcon())); |
199 | |
200 | KPageWidgetItem *fontPage = dialog->addPage(new FontSettings(this), i18n("Font settings" )); |
201 | fontPage->setIcon(KIcon("preferences-desktop-font" )); |
202 | |
203 | KPageWidgetItem *colorPage = dialog->addPage(new ColorSettings(this), i18n("Color settings" )); |
204 | colorPage->setIcon(KIcon("fill-color" )); |
205 | |
206 | KPageWidgetItem *guiPage = dialog->addPage(new GuiSettings(this), i18n("Interface settings" )); |
207 | guiPage->setIcon(KIcon("preferences-desktop-theme" )); |
208 | |
209 | KPageWidgetItem *savePage = dialog->addPage(new SaveSettings(this), i18n("Save settings" )); |
210 | savePage->setIcon(KIcon("document-save" )); |
211 | |
212 | connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(writeSettings(QString))); |
213 | |
214 | dialog->show(); |
215 | } |
216 | |
217 | void MainWindow::writeSettings(const QString& dialogName) |
218 | { |
219 | Q_UNUSED(dialogName); |
220 | KronometerConfig::self()->writeConfig(); |
221 | |
222 | loadSettings(); |
223 | } |
224 | |
225 | void MainWindow::updateLapDock() |
226 | { |
227 | lapView->resizeColumnsToContents(); |
228 | lapView->horizontalHeader()->setStretchLastSection(true); |
229 | lapView->selectRow(lapModel->rowCount(QModelIndex()) - 1); // rows indexes start from 0 |
230 | } |
231 | |
232 | void MainWindow::newFile() |
233 | { |
234 | MainWindow *window = new MainWindow(); |
235 | window->show(); |
236 | } |
237 | |
238 | void MainWindow::openFile() |
239 | { |
240 | QPointer<KFileDialog> dialog = new KFileDialog(KUrl(), QString(), this); |
241 | dialog->setOperationMode(KFileDialog::Opening); |
242 | dialog->setWindowTitle(i18n("Choose a Kronometer save file" )); |
243 | |
244 | QStringList mimeTypes; |
245 | mimeTypes << XML_MIMETYPE; |
246 | dialog->setMimeFilter(mimeTypes); |
247 | |
248 | if (dialog->exec() == QDialog::Accepted) { |
249 | QString file = dialog->selectedFile(); |
250 | |
251 | if (not file.isEmpty()) { |
252 | MainWindow *window = new MainWindow(nullptr, file); |
253 | window->show(); |
254 | } |
255 | } |
256 | |
257 | delete dialog; |
258 | } |
259 | |
260 | bool MainWindow::saveFile() |
261 | { |
262 | return saveFileAs(fileName); |
263 | } |
264 | |
265 | bool MainWindow::saveFileAs() |
266 | { |
267 | QPointer<KFileDialog> dialog = new KFileDialog(KUrl(), QString(), this); |
268 | dialog->setOperationMode(KFileDialog::Saving); |
269 | dialog->setConfirmOverwrite(true); |
270 | dialog->setWindowTitle(i18n("Choose Kronometer save file destination" )); |
271 | |
272 | QStringList mimeTypes; |
273 | mimeTypes << XML_MIMETYPE; |
274 | dialog->setMimeFilter(mimeTypes); |
275 | |
276 | bool rc = false; |
277 | if (dialog->exec() == QDialog::Accepted) { |
278 | rc = saveFileAs(dialog->selectedFile()); |
279 | } |
280 | |
281 | delete dialog; |
282 | return rc; |
283 | } |
284 | |
285 | void MainWindow::exportLapsAs() |
286 | { |
287 | QPointer<KFileDialog> dialog = new KFileDialog(KUrl(), QString(), this); |
288 | dialog->setOperationMode(KFileDialog::Saving); |
289 | dialog->setConfirmOverwrite(true); |
290 | dialog->setWindowTitle(i18n("Choose export file destination" )); |
291 | |
292 | QStringList mimeTypes; |
293 | mimeTypes << XML_MIMETYPE << CSV_MIMETYPE; |
294 | dialog->setMimeFilter(mimeTypes, CSV_MIMETYPE); |
295 | |
296 | if (dialog->exec() == QDialog::Accepted) { |
297 | exportLapsAs(dialog->selectedFile(), dialog->currentMimeFilter()); |
298 | } |
299 | |
300 | delete dialog; |
301 | } |
302 | |
303 | void MainWindow::copyToClipboard() |
304 | { |
305 | KApplication::clipboard()->setText(stopwatchDisplay->currentTime()); |
306 | } |
307 | |
308 | void MainWindow::setupCentralWidget() |
309 | { |
310 | centralSplitter = new QSplitter(this); |
311 | |
312 | lapModel = new LapModel(this); |
313 | proxyModel = new QSortFilterProxyModel(this); |
314 | proxyModel->setSourceModel(lapModel); |
315 | |
316 | lapView = new QTableView(this); |
317 | lapView->setModel(proxyModel); |
318 | lapView->setSelectionBehavior(QAbstractItemView::SelectRows); |
319 | lapView->setGridStyle(Qt::DotLine); |
320 | lapView->verticalHeader()->hide(); |
321 | lapView->resizeColumnsToContents(); |
322 | lapView->horizontalHeader()->setStretchLastSection(true); |
323 | lapView->setSortingEnabled(true); |
324 | lapView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored); |
325 | |
326 | centralSplitter->setOrientation(Qt::Horizontal); |
327 | centralSplitter->setChildrenCollapsible(false); |
328 | centralSplitter->addWidget(stopwatchDisplay); |
329 | centralSplitter->addWidget(lapView); |
330 | |
331 | setCentralWidget(centralSplitter); |
332 | } |
333 | |
334 | void MainWindow::setupStatusBar() |
335 | { |
336 | statusLabel = new QLabel(this); |
337 | statusLabel->setToolTip(i18n("Current chronometer status" )); |
338 | |
339 | statusBar()->addWidget(statusLabel); |
340 | } |
341 | |
342 | void MainWindow::setupActions() |
343 | { |
344 | startAction = new KAction(this); |
345 | pauseAction = new KAction(this); |
346 | resetAction = new KAction(this); |
347 | lapAction = new KAction(this); |
348 | exportAction = new KAction(this); |
349 | |
350 | startAction->setIcon(KIcon("player-time" )); |
351 | startAction->setShortcut(Qt::Key_Space); |
352 | |
353 | pauseAction->setText(i18n("&Pause" )); // pauseAction/resetAction have fixed text (startAction don't) |
354 | pauseAction->setIcon(KIcon("media-playback-pause" )); |
355 | pauseAction->setShortcut(Qt::Key_Space); |
356 | |
357 | resetAction->setText(i18n("&Reset" )); |
358 | resetAction->setIcon(KIcon("edit-clear-history" )); |
359 | resetAction->setShortcut(Qt::Key_F5); |
360 | |
361 | lapAction->setText(i18n("&Lap" )); |
362 | lapAction->setIcon(KIcon("chronometer" )); |
363 | lapAction->setShortcut(Qt::Key_Return); |
364 | |
365 | exportAction->setText(i18n("&Export laps as..." )); |
366 | exportAction->setIcon(KIcon("document-export" )); |
367 | |
368 | actionCollection()->addAction(START_KEY, startAction); |
369 | actionCollection()->addAction(PAUSE_KEY, pauseAction); |
370 | actionCollection()->addAction(RESET_KEY, resetAction); |
371 | actionCollection()->addAction(LAP_KEY, lapAction); |
372 | actionCollection()->addAction(EXPORT_KEY, exportAction); |
373 | |
374 | // triggers for Stopwatch "behavioral" slots |
375 | connect(startAction, SIGNAL(triggered(bool)), stopwatch, SLOT(onStart())); |
376 | connect(pauseAction, SIGNAL(triggered(bool)), stopwatch, SLOT(onPause())); |
377 | connect(resetAction, SIGNAL(triggered(bool)), stopwatch, SLOT(onReset())); |
378 | connect(lapAction, SIGNAL(triggered(bool)), stopwatch, SLOT(onLap())); |
379 | |
380 | // triggers for LapModel slots |
381 | connect(resetAction, SIGNAL(triggered(bool)), lapModel, SLOT(onClear())); |
382 | connect(stopwatch, SIGNAL(lap(QTime)), lapModel, SLOT(onLap(QTime))); |
383 | |
384 | // triggers for MainWindow "gui" slots |
385 | connect(startAction, SIGNAL(triggered(bool)), this, SLOT(running())); |
386 | connect(pauseAction, SIGNAL(triggered(bool)), this, SLOT(paused())); |
387 | connect(resetAction, SIGNAL(triggered(bool)), this, SLOT(inactive())); |
388 | connect(lapAction, SIGNAL(triggered(bool)), this, SLOT(updateLapDock())); |
389 | |
390 | // File menu triggers |
391 | KStandardAction::quit(this, SLOT(close()), actionCollection()); |
392 | KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); |
393 | KStandardAction::openNew(this, SLOT(newFile()), actionCollection()); |
394 | KStandardAction::save(this, SLOT(saveFile()), actionCollection()); |
395 | KStandardAction::saveAs(this, SLOT(saveFileAs()), actionCollection()); |
396 | KStandardAction::open(this, SLOT(openFile()), actionCollection()); |
397 | KStandardAction::copy(this, SLOT(copyToClipboard()), actionCollection()); |
398 | connect(exportAction, SIGNAL(triggered(bool)), this, SLOT(exportLapsAs())); |
399 | |
400 | setupGUI(Default, "kronometerui.rc" ); |
401 | |
402 | inactive(); // inactive state is the default |
403 | } |
404 | |
405 | |
406 | void MainWindow::loadSettings() |
407 | { |
408 | TimeFormat timeFormat( |
409 | KronometerConfig::showHours(), |
410 | KronometerConfig::showMinutes(), |
411 | KronometerConfig::showSeconds(), |
412 | KronometerConfig::showTenths(), |
413 | KronometerConfig::showHundredths(), |
414 | KronometerConfig::showMilliseconds() |
415 | ); |
416 | |
417 | lapModel->setTimeFormat(timeFormat); |
418 | timeFormat.showDividers(false); |
419 | stopwatchDisplay->setTimeFormat(timeFormat); |
420 | stopwatchDisplay->setHourFont(KronometerConfig::hourFont()); |
421 | stopwatchDisplay->setMinFont(KronometerConfig::minFont()); |
422 | stopwatchDisplay->setSecFont(KronometerConfig::secFont()); |
423 | stopwatchDisplay->setFracFont(KronometerConfig::fracFont()); |
424 | stopwatchDisplay->setBackgroundColor(KronometerConfig::backgroundColor()); |
425 | stopwatchDisplay->setTextColor(KronometerConfig::textColor()); |
426 | stopwatchDisplay->showHeaders(KronometerConfig::showTimeHeaders()); |
427 | |
428 | setupGranularity(KronometerConfig::showTenths(), KronometerConfig::showHundredths(), KronometerConfig::showMilliseconds()); |
429 | } |
430 | |
431 | |
432 | void MainWindow::setupGranularity(bool tenths, bool hundredths, bool msec) |
433 | { |
434 | if (msec) { |
435 | stopwatch->setGranularity(Stopwatch::MILLISECONDS); |
436 | } |
437 | else if (hundredths) { |
438 | stopwatch->setGranularity(Stopwatch::HUNDREDTHS); |
439 | } |
440 | else if (tenths) { |
441 | stopwatch->setGranularity(Stopwatch::TENTHS); |
442 | } |
443 | else { |
444 | stopwatch->setGranularity(Stopwatch::SECONDS); |
445 | } |
446 | } |
447 | |
448 | bool MainWindow::saveFileAs(const QString& name) |
449 | { |
450 | if (name.isEmpty()) { |
451 | return false; |
452 | } |
453 | |
454 | QString saveName = name; |
455 | |
456 | if (not saveName.endsWith(XML_EXTENSION)) { |
457 | saveName += XML_EXTENSION; |
458 | } |
459 | |
460 | KSaveFile saveFile(saveName); |
461 | if (!saveFile.open()) { |
462 | KMessageBox::error(this, i18n("Failed to open file" )); |
463 | return false; |
464 | } |
465 | |
466 | // OLD: persistence using binary files |
467 | //QDataStream stream(&saveFile); |
468 | //stopwatch->serialize(stream); // save stopwatch time |
469 | //stream << *lapModel; // save laps |
470 | |
471 | // NEW: persistence using XML files |
472 | QTextStream stream(&saveFile); |
473 | createXmlSaveFile(stream); |
474 | |
475 | |
476 | bool isSaveSuccessfull = saveFile.finalize(); |
477 | saveFile.close(); |
478 | |
479 | if (isSaveSuccessfull) { |
480 | fileName = saveName; |
481 | |
482 | unsavedTimes = false; |
483 | setWindowModified(unsavedTimes); |
484 | return true; |
485 | } |
486 | else { |
487 | return false; |
488 | } |
489 | } |
490 | |
491 | void MainWindow::openFile(const QString& name) |
492 | { |
493 | QString buffer; |
494 | |
495 | if (KIO::NetAccess::download(name, buffer, this)) { |
496 | QFile file(buffer); |
497 | file.open(QIODevice::ReadOnly); |
498 | |
499 | // OLD: persistence using binary files |
500 | //QDataStream stream(&file); |
501 | //stopwatch->deserialize(stream); // load stopwatch time |
502 | //stream >> *lapModel; // load laps |
503 | |
504 | // NEW: persistence using XML files |
505 | QDomDocument doc; |
506 | QString errorMsg; |
507 | |
508 | if (doc.setContent(&file, &errorMsg)) { |
509 | if (parseXmlSaveFile(doc)) { |
510 | paused(); // enter in paused state |
511 | fileName = name; |
512 | |
513 | KIO::NetAccess::removeTempFile(buffer); |
514 | QFileInfo fileInfo(fileName); |
515 | setWindowTitle(WINDOW_TITLE + " - " + fileInfo.fileName() + QT_PLACE_HOLDER); |
516 | } |
517 | else { |
518 | KIO::NetAccess::removeTempFile(buffer); |
519 | close(); // files are opened in a new window, so if the open fails the new window has to be closed. |
520 | } |
521 | } |
522 | else { |
523 | KMessageBox::error(this, i18n("Cannot open file: %1" , errorMsg)); |
524 | KIO::NetAccess::removeTempFile(buffer); |
525 | close(); |
526 | } |
527 | } |
528 | else { |
529 | KMessageBox::error(this, KIO::NetAccess::lastErrorString()); |
530 | } |
531 | } |
532 | |
533 | void MainWindow::createXmlSaveFile(QTextStream& out) |
534 | { |
535 | QDomDocument doc; |
536 | QDomProcessingInstruction metaData = doc.createProcessingInstruction("xml" , "version='1.0' encoding='UTF-8'" ); |
537 | QDomComment = doc.createComment(timestampMessage()); |
538 | QDomElement rootElement = doc.createElement(ROOT_TAG); |
539 | |
540 | QDomElement stopwatchElement = doc.createElement(STOPWATCH_TAG); |
541 | stopwatch->serialize(stopwatchElement, PERSISTENCE_ATTR); |
542 | QDomElement stopwatchTime = doc.createElement(TIME_TAG); |
543 | stopwatchTime.setAttribute(TYPE_ATTR, ABS_TYPE); |
544 | stopwatchTime.appendChild(doc.createTextNode(stopwatchDisplay->currentTime())); |
545 | stopwatchElement.appendChild(stopwatchTime); |
546 | |
547 | QDomElement lapsElement = doc.createElement(LAPS_TAG); |
548 | |
549 | for (int i = 0; i < lapModel->rowCount(QModelIndex()); i++) { |
550 | QDomElement lap = doc.createElement(LAP_TAG); |
551 | lap.setAttribute(LAP_ID_ATTR, i); |
552 | lapModel->lapToXml(lap, PERSISTENCE_ATTR, i); |
553 | |
554 | QDomElement relTime = doc.createElement(TIME_TAG); |
555 | relTime.setAttribute(TYPE_ATTR, REL_TYPE); |
556 | relTime.appendChild(doc.createTextNode(lapModel->relativeLapTime(i))); |
557 | |
558 | QDomElement absTime = doc.createElement(TIME_TAG); |
559 | absTime.setAttribute(TYPE_ATTR, ABS_TYPE); |
560 | absTime.appendChild(doc.createTextNode(lapModel->absoluteLapTime(i))); |
561 | |
562 | lap.appendChild(relTime); |
563 | lap.appendChild(absTime); |
564 | lapsElement.appendChild(lap); |
565 | } |
566 | |
567 | rootElement.appendChild(stopwatchElement); |
568 | rootElement.appendChild(lapsElement); |
569 | doc.appendChild(metaData); |
570 | doc.appendChild(timestampComment); |
571 | doc.appendChild(rootElement); |
572 | doc.save(out, KronometerConfig::saveFileIndentSize()); |
573 | } |
574 | |
575 | bool MainWindow::parseXmlSaveFile(const QDomDocument& doc) |
576 | { |
577 | QDomElement rootElement = doc.namedItem(ROOT_TAG).toElement(); |
578 | |
579 | if (rootElement.isNull()) { |
580 | KMessageBox::error(this, i18n("Invalid XML file" )); |
581 | return false; |
582 | } |
583 | |
584 | QDomElement stopwatchElement = rootElement.namedItem(STOPWATCH_TAG).toElement(); |
585 | QDomElement lapsElement = rootElement.namedItem(LAPS_TAG).toElement(); |
586 | |
587 | if (stopwatchElement.isNull() or lapsElement.isNull()) { |
588 | KMessageBox::error(this, i18n("Incomplete Kronometer save file" )); |
589 | return false; |
590 | } |
591 | |
592 | stopwatch->deserialize(stopwatchElement, PERSISTENCE_ATTR); |
593 | |
594 | QDomElement lap = lapsElement.firstChildElement(LAP_TAG); |
595 | |
596 | while (not lap.isNull()) { |
597 | lapModel->lapFromXml(lap, PERSISTENCE_ATTR); |
598 | lap = lap.nextSiblingElement(LAP_TAG); |
599 | } |
600 | |
601 | return true; |
602 | } |
603 | |
604 | void MainWindow::exportLapsAs(const QString& name, const QString& mimetype) |
605 | { |
606 | if (name.isEmpty()) { |
607 | return; |
608 | } |
609 | |
610 | QString exportName = name; |
611 | |
612 | if (mimetype == XML_MIMETYPE) { |
613 | if (not exportName.endsWith(XML_EXTENSION)) { |
614 | exportName += XML_EXTENSION; |
615 | } |
616 | |
617 | KSaveFile exportFile(exportName); |
618 | exportFile.open(); |
619 | |
620 | QTextStream stream(&exportFile); |
621 | exportLapsAsXml(stream); |
622 | |
623 | exportFile.finalize(); |
624 | exportFile.close(); |
625 | } |
626 | |
627 | else if (mimetype == CSV_MIMETYPE) { |
628 | if (not exportName.endsWith(CSV_EXTENSION)) { |
629 | exportName += CSV_EXTENSION; |
630 | } |
631 | |
632 | KSaveFile exportFile(exportName); |
633 | exportFile.open(); |
634 | |
635 | QTextStream stream(&exportFile); |
636 | exportLapsAsCsv(stream); |
637 | |
638 | exportFile.finalize(); |
639 | exportFile.close(); |
640 | } |
641 | } |
642 | |
643 | void MainWindow::exportLapsAsXml(QTextStream& out) |
644 | { |
645 | QDomDocument doc; |
646 | QDomProcessingInstruction metaData = doc.createProcessingInstruction("xml" , "version='1.0' encoding='UTF-8'" ); |
647 | QDomComment = doc.createComment(timestampMessage()); |
648 | QDomElement rootElement = doc.createElement(ROOT_TAG); |
649 | |
650 | QDomElement lapsElement = doc.createElement(LAPS_TAG); |
651 | |
652 | for (int i = 0; i < lapModel->rowCount(QModelIndex()); i++) { |
653 | QDomElement lap = doc.createElement(LAP_TAG); |
654 | lap.setAttribute(LAP_ID_ATTR, i); |
655 | |
656 | QDomElement relTime = doc.createElement(TIME_TAG); |
657 | relTime.setAttribute(TYPE_ATTR, REL_TYPE); |
658 | relTime.appendChild(doc.createTextNode(lapModel->relativeLapTime(i))); |
659 | |
660 | QDomElement absTime = doc.createElement(TIME_TAG); |
661 | absTime.setAttribute(TYPE_ATTR, ABS_TYPE); |
662 | absTime.appendChild(doc.createTextNode(lapModel->absoluteLapTime(i))); |
663 | |
664 | lap.appendChild(relTime); |
665 | lap.appendChild(absTime); |
666 | lapsElement.appendChild(lap); |
667 | } |
668 | |
669 | rootElement.appendChild(lapsElement); |
670 | doc.appendChild(metaData); |
671 | doc.appendChild(timestampComment); |
672 | doc.appendChild(rootElement); |
673 | doc.save(out, KronometerConfig::exportedXmlIndentSize()); |
674 | } |
675 | |
676 | void MainWindow::exportLapsAsCsv(QTextStream& out) |
677 | { |
678 | out << '#' << timestampMessage() << '\r' << '\n'; |
679 | out << '#' << i18n("Lap number,Lap time,Global time" ) << '\r' << '\n'; |
680 | |
681 | for (int i = 0; i < lapModel->rowCount(QModelIndex()); i++) { |
682 | out << i << ',' << lapModel->relativeLapTime(i) << ',' << lapModel->absoluteLapTime(i) << '\r' << '\n'; |
683 | } |
684 | } |
685 | |
686 | QString MainWindow::timestampMessage() |
687 | { |
688 | QDateTime timestamp = QDateTime::currentDateTime(); |
689 | |
690 | return i18n("Created by Kronometer on %1" , timestamp.toString(Qt::DefaultLocaleLongDate)); |
691 | } |
692 | |
693 | |