Warning, file /utilities/kronometer/src/gui/mainwindow.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     Copyright (C) 2014 by Elvis Angelaccio <elvis.angelaccio@kde.org>
0003 
0004     This file is part of Kronometer.
0005 
0006     Kronometer is free software: you can redistribute it and/or modify
0007     it under the terms of the GNU General Public License as published by
0008     the Free Software Foundation, either version 2 of the License, or
0009     (at your option) any later version.
0010 
0011     Kronometer is distributed in the hope that it will be useful,
0012     but WITHOUT ANY WARRANTY; without even the implied warranty of
0013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014     GNU General Public License for more details.
0015 
0016     You should have received a copy of the GNU General Public License
0017     along with Kronometer.  If not, see <http://www.gnu.org/licenses/>.
0018 */
0019 
0020 #include "mainwindow.h"
0021 #include "colorsettings.h"
0022 #include "fontsettings.h"
0023 #include "generalsettings.h"
0024 #include "lapmodel.h"
0025 #include "sessiondialog.h"
0026 #include "sessionmodel.h"
0027 #include "settings.h"
0028 #include "stopwatch.h"
0029 #include "timedisplay.h"
0030 
0031 #include <KActionCollection>
0032 #include <KConfigDialog>
0033 #include <KHelpMenu>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 #include <KToggleAction>
0037 #include <KToolBar>
0038 
0039 #include <QAction>
0040 #include <QApplication>
0041 #include <QClipboard>
0042 #include <QDBusConnection>
0043 #include <QDBusInterface>
0044 #include <QDBusPendingCallWatcher>
0045 #include <QDBusPendingReply>
0046 #include <QFileDialog>
0047 #include <QInputDialog>
0048 #include <QJsonArray>
0049 #include <QJsonDocument>
0050 #include <QJsonObject>
0051 #include <QMenuBar>
0052 #include <QPointer>
0053 #include <QSaveFile>
0054 #include <QSortFilterProxyModel>
0055 #include <QSplitter>
0056 #include <QStatusBar>
0057 #include <QTableView>
0058 #include <QToolButton>
0059 
0060 MainWindow::MainWindow(QWidget *parent, const Session& session) : KXmlGuiWindow(parent),
0061     m_session {session}
0062 {
0063     Q_INIT_RESOURCE(kronometerui);
0064 
0065     m_stopwatch = new Stopwatch {this};
0066     m_stopwatchDisplay = new TimeDisplay {this};
0067     connect(m_stopwatch, &Stopwatch::time, m_stopwatchDisplay, &TimeDisplay::setTime);  // bind stopwatch to its display
0068     connect(m_stopwatch, &Stopwatch::running, this, &MainWindow::slotRunning);
0069     connect(m_stopwatch, &Stopwatch::paused, this, &MainWindow::slotPaused);
0070     connect(m_stopwatch, &Stopwatch::inactive, this, &MainWindow::slotInactive);
0071 
0072     m_sessionModel = new SessionModel {this};
0073 
0074     setupCentralWidget();
0075     setupActions();
0076     setupGUI(ToolBar | Keys | Save | Create, QStringLiteral("kronometerui.rc"));
0077 
0078     // #351746: prevent ugly 640x480 default size (unless there is a previous size to be restored, see #361494).
0079     if (not isWindowSizeSaved()) {
0080         resize(minimumSizeHint());
0081     }
0082 
0083     loadSettings();
0084     statusBar()->hide();
0085 
0086     if (m_session.isEmpty()) {
0087         slotInactive();
0088     }
0089     else {
0090         loadSession();
0091     }
0092 
0093     // TODO: replace this with solid-power API, once it's released.
0094     QDBusConnection::systemBus().connect(
0095                 QStringLiteral("org.freedesktop.login1"),
0096                 QStringLiteral("/org/freedesktop/login1"),
0097                 QStringLiteral("org.freedesktop.login1.Manager"),
0098                 QStringLiteral("PrepareForSleep"),
0099                 this, SLOT(slotPrepareForSleep(bool)));
0100 
0101     m_screensaverInterface = new QDBusInterface(QStringLiteral("org.freedesktop.ScreenSaver"),
0102                                                 QStringLiteral("/ScreenSaver"),
0103                                                 QStringLiteral("org.freedesktop.ScreenSaver"));
0104 }
0105 
0106 MainWindow::~MainWindow()
0107 {
0108     if (m_controlMenuButton) {
0109         m_controlMenuButton->disconnect();
0110     }
0111 }
0112 
0113 void MainWindow::setWindowTitle(const QString& title)
0114 {
0115     if (title.endsWith(QLatin1String("[*]"))) {
0116         KXmlGuiWindow::setWindowTitle(title);
0117     }
0118     else {
0119         KXmlGuiWindow::setWindowTitle(title + QLatin1String("[*]"));
0120     }
0121 }
0122 
0123 bool MainWindow::queryClose()
0124 {
0125     if (m_stopwatch->isInactive()) {
0126         return true;  // exit without ask
0127     }
0128 
0129     if (m_stopwatch->isRunning()) {
0130         m_stopwatch->pause();
0131     }
0132 
0133     if (m_session.isEmpty()) {
0134         auto buttonCode = KMessageBox::warningContinueCancel(
0135                     this,
0136                     i18n("Do you want to quit and lose your unsaved times?"),
0137                     i18nc("@title:window", "Confirm Quit"),
0138                     KStandardGuiItem::quit(),
0139                     KStandardGuiItem::cancel(),
0140                     QStringLiteral("quit-and-lose-times"));
0141 
0142         if (buttonCode != KMessageBox::Cancel) {
0143             return true;
0144         }
0145         return false;
0146     }
0147     else if (m_session.isOutdated()) {
0148         auto buttonCode = KMessageBox::warningYesNoCancel(this, i18n("Save times to session %1?", m_session.name()));
0149 
0150         switch (buttonCode) {
0151         case KMessageBox::Yes:
0152             slotSaveSession();
0153             return true;
0154         case KMessageBox::No:
0155             return true;
0156         default: // cancel
0157             return false;
0158         }
0159     }
0160 
0161     return true;  // there is an open session, but times are already saved.
0162 }
0163 
0164 void MainWindow::slotRunning()
0165 {
0166     m_session.setIsOutdated(true);
0167     setWindowModified(true);
0168     activateScreenInhibition();
0169 
0170     stateChanged(QStringLiteral("running"));
0171 }
0172 
0173 void MainWindow::slotPaused()
0174 {
0175     m_startAction->setText(i18nc("@action", "Re&sume"));
0176     disactivateScreenInhibition();
0177 
0178     if (m_session.isEmpty()) {
0179         stateChanged(QStringLiteral("paused"));
0180     }
0181     else {
0182         stateChanged(QStringLiteral("pausedSession"));
0183     }
0184 
0185     // the export action can be used only if there are laps (in both the paused states).
0186     // so, it can't be enabled directly from kronometerui.rc
0187     if (not m_lapModel->isEmpty()) {
0188         m_exportAction->setEnabled(true);
0189     }
0190 }
0191 
0192 void MainWindow::slotInactive()
0193 {
0194     m_startAction->setText(i18nc("@action", "&Start"));
0195 
0196     m_session = Session {};
0197 
0198     setWindowTitle(i18nc("untitled window", "Untitled"));
0199     setWindowModified(false);
0200     disactivateScreenInhibition();
0201 
0202     stateChanged(QStringLiteral("inactive"));
0203 }
0204 
0205 void MainWindow::slotPrepareForSleep(bool beforeSleep)
0206 {
0207     if (not beforeSleep)
0208         return;
0209 
0210     qDebug() << "System is going to sleep, pausing the stopwatch.";
0211     m_stopwatch->pause();
0212 }
0213 
0214 void MainWindow::slotShowSettings()
0215 {
0216     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
0217         return;
0218     }
0219 
0220     auto dialog = new KConfigDialog {this, QStringLiteral("settings"), KronometerConfig::self()};
0221     dialog->setModal(true);
0222 
0223     auto generalPage = dialog->addPage(new GeneralSettings {this}, i18nc("@title:tab", "General Settings"));
0224     generalPage->setIcon(QIcon::fromTheme(QApplication::windowIcon().name()));
0225 
0226     auto fontPage = dialog->addPage(new FontSettings {this}, i18nc("@title:tab", "Font Settings"));
0227     fontPage->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
0228 
0229     auto colorPage = dialog->addPage(new ColorSettings {this}, i18nc("@title:tab", "Color Settings"));
0230     colorPage->setIcon(QIcon::fromTheme(QStringLiteral("fill-color")));
0231 
0232     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::slotWriteSettings);
0233 
0234     dialog->show();
0235 }
0236 
0237 void MainWindow::slotWriteSettings()
0238 {
0239     KronometerConfig::self()->save();
0240 
0241     foreach (auto widget, QApplication::topLevelWidgets()) {
0242         auto window = qobject_cast<MainWindow*>(widget);
0243 
0244         if (window) {
0245             window->loadSettings();
0246         }
0247     }
0248 }
0249 
0250 void MainWindow::slotUpdateLapDock()
0251 {
0252     m_lapView->selectRow(m_lapModel->rowCount() - 1);  // rows indexes start from 0
0253 }
0254 
0255 void MainWindow::slotNewSession()
0256 {
0257     auto window = new MainWindow {};
0258     window->show();
0259 }
0260 
0261 void MainWindow::slotOpenSession()
0262 {
0263     auto dialog = new SessionDialog {this};
0264 
0265     connect(dialog, &QDialog::finished, this, [dialog](int result) {
0266         if (result == QDialog::Accepted) {
0267             auto window = new MainWindow {nullptr, dialog->selectedSession()};
0268             window->show();
0269         }
0270         dialog->deleteLater();
0271     });
0272 
0273     dialog->open();
0274 }
0275 
0276 void MainWindow::slotSaveSession()
0277 {
0278     if (not m_session.isOutdated())
0279         return;
0280 
0281     m_session.clear();    // required for laps consistency
0282     m_session.setTime(m_stopwatch->raw());
0283 
0284     for (int i = 0; i < m_lapModel->rowCount(); i++) {
0285         m_session.addLap(m_lapModel->data(m_lapModel->index(i, 0), static_cast<int>(LapModel::Roles::LapRole)).value<Lap>());
0286     }
0287 
0288     m_session.setIsOutdated(false);
0289     m_sessionModel->update(m_session);
0290 
0291     setWindowModified(false);
0292 }
0293 
0294 void MainWindow::slotSaveSessionAs()
0295 {
0296     auto accepted = false;
0297     auto name = QInputDialog::getText(this,
0298                                       i18nc("@title:window", "Save Session"),
0299                                       i18n("You can choose a name for this session:"),
0300                                       QLineEdit::Normal,
0301                                       {},
0302                                       &accepted);
0303 
0304     if (not accepted)
0305         return;
0306 
0307     if (name.isEmpty())
0308         name = i18nc("untitled session", "Untitled session");
0309 
0310     saveSessionAs(name);
0311 }
0312 
0313 void MainWindow::slotExportLapsAs()
0314 {
0315     auto dialog = new QFileDialog {this};
0316     dialog->setAcceptMode(QFileDialog::AcceptSave);
0317     dialog->setConfirmOverwrite(true);
0318     dialog->setWindowTitle(i18nc("@title:window", "Export Laps"));
0319     dialog->setMimeTypeFilters({ QStringLiteral("text/csv"), QStringLiteral("application/json") });
0320 
0321     connect(dialog, &QDialog::finished, this, [this, dialog](int result) {
0322         if (result == QDialog::Accepted) {
0323             exportLapsAs(dialog->selectedFiles().at(0), dialog->selectedMimeTypeFilter());
0324         }
0325         dialog->deleteLater();
0326     });
0327 
0328     dialog->open();
0329 }
0330 
0331 void MainWindow::slotCopyToClipboard()
0332 {
0333     QApplication::clipboard()->setText(m_stopwatchDisplay->currentTime());
0334 }
0335 
0336 void MainWindow::slotToggleMenuBar()
0337 {
0338     // Menubar shown for the first time.
0339     if (KronometerConfig::menuBarNeverShown()) {
0340         KronometerConfig::setMenuBarNeverShown(false);
0341         slotWriteSettings();
0342     }
0343 
0344     menuBar()->setVisible(!menuBar()->isVisible());
0345     m_toggleMenuAction->setChecked(menuBar()->isVisible());
0346 
0347     menuBar()->isVisible() ? deleteControlMenuButton() : createControlMenuButton();
0348 }
0349 
0350 void MainWindow::slotUpdateControlMenu()
0351 {
0352     auto menu = qobject_cast<QMenu*>(sender());
0353     if (not menu)
0354         return;
0355 
0356     // All actions get cleared by QMenu::clear().
0357     // This includes the sub-menus because 'menu' is their parent.
0358     menu->clear();
0359 
0360     auto ac = actionCollection();
0361 
0362     // Add "File" actions
0363     auto added = addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::New))), menu) |
0364                  addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Open))), menu);
0365 
0366     if (added)
0367         menu->addSeparator();
0368 
0369     added = addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save))), menu) |
0370             addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs))), menu);
0371 
0372     if (added)
0373         menu->addSeparator();
0374 
0375     added = addActionToMenu(m_exportAction, menu);
0376 
0377     if (added)
0378         menu->addSeparator();
0379 
0380     // Add "Edit actions
0381     added = addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Copy))), menu);
0382 
0383     if (added)
0384         menu->addSeparator();
0385 
0386     // Add "Settings" menu entries
0387     addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::KeyBindings))), menu);
0388     addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::ConfigureToolbars))), menu);
0389     addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Preferences))), menu);
0390 
0391     // Add "Help" menu
0392     auto helpMenu = new KHelpMenu {menu};
0393     menu->addMenu(helpMenu->menu());
0394 
0395     menu->addSeparator();
0396     addActionToMenu(ac->action(QString::fromLatin1(KStandardAction::name(KStandardAction::ShowMenubar))), menu);
0397 }
0398 
0399 void MainWindow::slotToolBarUpdated()
0400 {
0401     if (menuBar()->isVisible())
0402         return;
0403 
0404     createControlMenuButton();
0405 }
0406 
0407 void MainWindow::setupCentralWidget()
0408 {
0409     auto splitter = new QSplitter {this};
0410 
0411     m_lapModel = new LapModel {this};
0412     auto proxyModel = new QSortFilterProxyModel {this};
0413     proxyModel->setSourceModel(m_lapModel);
0414 
0415     m_lapView = new QTableView {this};
0416     m_lapView->setModel(proxyModel);
0417     m_lapView->setSelectionBehavior(QAbstractItemView::SelectRows);
0418     m_lapView->setGridStyle(Qt::DotLine);
0419     m_lapView->verticalHeader()->hide();
0420     m_lapView->horizontalHeader()->setStretchLastSection(true);
0421     m_lapView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0422     // Enable sorting and resize the columns to take the sorting arrow into account.
0423     m_lapView->setSortingEnabled(true);
0424     m_lapView->resizeColumnsToContents();
0425 
0426     splitter->setOrientation(Qt::Horizontal);
0427     splitter->setChildrenCollapsible(false);
0428     splitter->addWidget(m_stopwatchDisplay);
0429     splitter->addWidget(m_lapView);
0430 
0431     setCentralWidget(splitter);
0432 }
0433 
0434 void MainWindow::setupActions()
0435 {
0436     m_startAction = new QAction {this};
0437     m_pauseAction = new QAction {this};
0438     m_resetAction = new QAction {this};
0439     m_lapAction = new QAction {this};
0440     m_exportAction = new QAction {this};
0441 
0442     m_startAction->setIcon(QIcon::fromTheme(QStringLiteral("chronometer-start")));
0443 
0444     m_pauseAction->setText(i18nc("@action", "&Pause"));  // pauseAction/resetAction have fixed text (startAction doesn't)
0445     m_pauseAction->setIcon(QIcon::fromTheme(QStringLiteral("chronometer-pause")));
0446 
0447     m_resetAction->setText(i18nc("@action", "&Reset"));
0448     m_resetAction->setIcon(QIcon::fromTheme(QStringLiteral("chronometer-reset")));
0449 
0450     m_lapAction->setText(i18nc("@action", "&Lap"));
0451     m_lapAction->setIcon(QIcon::fromTheme(QStringLiteral("chronometer-lap")));
0452 
0453     m_exportAction->setText(i18nc("@action:inmenu", "&Export Laps as..."));
0454     m_exportAction->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
0455 
0456     actionCollection()->addAction(QStringLiteral("start"), m_startAction);
0457     actionCollection()->addAction(QStringLiteral("pause"), m_pauseAction);
0458     actionCollection()->addAction(QStringLiteral("reset"), m_resetAction);
0459     actionCollection()->addAction(QStringLiteral("lap"), m_lapAction);
0460     actionCollection()->addAction(QStringLiteral("export_laps"), m_exportAction);
0461     actionCollection()->setDefaultShortcut(m_startAction, Qt::Key_Space);
0462     actionCollection()->setDefaultShortcut(m_pauseAction, Qt::Key_Space);
0463     actionCollection()->setDefaultShortcut(m_resetAction, Qt::Key_F5);
0464     actionCollection()->setDefaultShortcut(m_lapAction, Qt::Key_Return);
0465 
0466     // Pause is initially disabled.
0467     m_pauseAction->setEnabled(false);
0468 
0469     // triggers for Stopwatch "behavioral" slots
0470     connect(m_startAction, &QAction::triggered, m_stopwatch, &Stopwatch::start);
0471     connect(m_pauseAction, &QAction::triggered, m_stopwatch, &Stopwatch::pause);
0472     connect(m_resetAction, &QAction::triggered, m_stopwatch, &Stopwatch::reset);
0473     connect(m_lapAction, &QAction::triggered, m_stopwatch, &Stopwatch::storeLap);
0474 
0475     // triggers for LapModel slots
0476     connect(m_resetAction, &QAction::triggered, m_lapModel,&LapModel::clear);
0477     connect(m_stopwatch, &Stopwatch::lap, m_lapModel, &LapModel::addLap);
0478 
0479     // triggers for MainWindow "gui" slots
0480     connect(m_startAction, &QAction::triggered, this, &MainWindow::slotRunning);
0481     connect(m_pauseAction, &QAction::triggered, this, &MainWindow::slotPaused);
0482     connect(m_resetAction, &QAction::triggered, this, &MainWindow::slotInactive);
0483     connect(m_lapAction, &QAction::triggered, this, &MainWindow::slotUpdateLapDock);
0484 
0485     // triggers for TimeDisplay slots
0486     connect(m_resetAction, &QAction::triggered, m_stopwatchDisplay, &TimeDisplay::reset);
0487 
0488     // File menu triggers
0489     KStandardAction::quit(this, &QWidget::close, actionCollection());
0490     KStandardAction::preferences(this, &MainWindow::slotShowSettings, actionCollection());
0491     KStandardAction::openNew(this, &MainWindow::slotNewSession, actionCollection());
0492     KStandardAction::save(this, &MainWindow::slotSaveSession, actionCollection());
0493     KStandardAction::saveAs(this, &MainWindow::slotSaveSessionAs, actionCollection());
0494     KStandardAction::open(this, &MainWindow::slotOpenSession, actionCollection());
0495     KStandardAction::copy(this, &MainWindow::slotCopyToClipboard, actionCollection());
0496     connect(m_exportAction, &QAction::triggered, this, &MainWindow::slotExportLapsAs);
0497 
0498     m_toggleMenuAction = KStandardAction::showMenubar(nullptr, nullptr, actionCollection());
0499     // QueuedConnection prevents crashes when toggling the visibility
0500     connect(m_toggleMenuAction, &KToggleAction::triggered, this, &MainWindow::slotToggleMenuBar, Qt::QueuedConnection);
0501 }
0502 
0503 void MainWindow::loadSettings()
0504 {
0505     auto timeFrac = KronometerConfig::showSecondFractions() ? KronometerConfig::fractionsType() : TimeFormat::NoFractions;
0506     auto lapFrac = KronometerConfig::showSecondFractions() ? KronometerConfig::lapFractionsType() : TimeFormat::NoFractions;
0507 
0508     auto timeFormat = TimeFormat {KronometerConfig::showHours(), KronometerConfig::showMinutes(), timeFrac};
0509     auto lapTimeFormat = TimeFormat {KronometerConfig::showLapHours(), KronometerConfig::showLapMinutes(), lapFrac};
0510 
0511     m_lapAction->setVisible(KronometerConfig::isLapsRecordingEnabled());
0512     m_exportAction->setVisible(KronometerConfig::isLapsRecordingEnabled());
0513     m_lapView->setVisible(KronometerConfig::isLapsRecordingEnabled());
0514     m_lapView->setColumnHidden(m_lapModel->columnForRole(LapModel::Roles::AbsoluteTimeRole), not KronometerConfig::showLapAbsoluteTimes());
0515     m_lapView->setColumnHidden(m_lapModel->columnForRole(LapModel::Roles::NoteRole), not KronometerConfig::showLapNotes());
0516     m_lapModel->setTimeFormat(lapTimeFormat);
0517     timeFormat.showDividers(false);
0518     m_stopwatchDisplay->setTimeFormat(timeFormat);
0519     m_stopwatchDisplay->setHoursFont(KronometerConfig::hourFont());
0520     m_stopwatchDisplay->setMinutesFont(KronometerConfig::minFont());
0521     m_stopwatchDisplay->setSecondsFont(KronometerConfig::secFont());
0522     m_stopwatchDisplay->setFractionsFont(KronometerConfig::fracFont());
0523     m_stopwatchDisplay->setBackgroundColor(KronometerConfig::backgroundColor());
0524     m_stopwatchDisplay->setTextColor(KronometerConfig::textColor());
0525 
0526     setupGranularity();
0527 
0528     // Always hide the menubar until the user shows it for the first time.
0529     if (KronometerConfig::menuBarNeverShown()) {
0530         menuBar()->hide();
0531     }
0532 
0533     if (menuBar()->isHidden()) {
0534         m_toggleMenuAction->setChecked(false);
0535         createControlMenuButton();
0536     }
0537 }
0538 
0539 void MainWindow::setupGranularity()
0540 {
0541     if (not KronometerConfig::showSecondFractions()) {
0542         m_stopwatch->setGranularity(Stopwatch::Granularity::Seconds);
0543         return;
0544     }
0545 
0546     switch (KronometerConfig::fractionsType()) {
0547     case TimeFormat::UpToTenths:
0548         m_stopwatch->setGranularity(Stopwatch::Granularity::Tenths);
0549         break;
0550     case TimeFormat::UpToHundredths:
0551         m_stopwatch->setGranularity(Stopwatch::Granularity::Hundredths);
0552         break;
0553     case TimeFormat::UpToMilliseconds:
0554         m_stopwatch->setGranularity(Stopwatch::Granularity::Milliseconds);
0555         break;
0556     default:
0557         break;
0558     }
0559 }
0560 
0561 void MainWindow::saveSessionAs(const QString& name)
0562 {
0563     auto newSession = Session {m_stopwatch->raw()};
0564     newSession.setName(name);
0565 
0566     for (int i = 0; i < m_lapModel->rowCount(); i++) {
0567         newSession.addLap(m_lapModel->data(m_lapModel->index(i, 0), static_cast<int>(LapModel::Roles::LapRole)).value<Lap>());
0568     }
0569 
0570     m_sessionModel->append(newSession);
0571 
0572     m_session = newSession;
0573 
0574     setWindowTitle(m_session.name());
0575     setWindowModified(false);
0576 }
0577 
0578 void MainWindow::loadSession()
0579 {
0580     m_stopwatch->initialize(m_session.time());
0581 
0582     const auto laps = m_session.laps();
0583     for (const auto& lap : laps) {
0584         m_lapModel->append(lap);
0585     }
0586 
0587     slotPaused();   // enter in paused state
0588     setWindowTitle(m_session.name());
0589 }
0590 
0591 void MainWindow::exportLapsAs(const QString& name, const QString& mimeType)
0592 {
0593     if (name.isEmpty()) {
0594         return;
0595     }
0596 
0597     auto exportName = name;
0598 
0599     if (mimeType == QLatin1String("application/json")) {
0600         if (not exportName.endsWith(QLatin1String(".json"))) {
0601             exportName += QLatin1String(".json");
0602         }
0603 
0604         QSaveFile exportFile {exportName};
0605         exportFile.open(QIODevice::WriteOnly);
0606 
0607         QJsonObject json;
0608         exportLapsAsJson(json);
0609 
0610         auto exportDoc = QJsonDocument {json};
0611         exportFile.write(exportDoc.toJson());
0612         exportFile.commit();
0613     }
0614     else if (mimeType == QLatin1String("text/csv")) {
0615         if (not exportName.endsWith(QLatin1String(".csv"))) {
0616             exportName += QLatin1String(".csv");
0617         }
0618 
0619         QSaveFile exportFile {exportName};
0620         exportFile.open(QIODevice::WriteOnly);
0621 
0622         QTextStream stream {&exportFile};
0623         exportLapsAsCsv(stream);
0624 
0625         exportFile.commit();
0626     }
0627 }
0628 
0629 void MainWindow::exportLapsAsJson(QJsonObject& json)
0630 {
0631     auto laps = QJsonArray {};
0632     for (auto i = 0; i < m_lapModel->rowCount(); i++) {
0633         auto object = QJsonObject {};
0634         const auto lap = m_lapModel->data(m_lapModel->index(i, 0), static_cast<int>(LapModel::Roles::LapRole)).value<Lap>();
0635         lap.write(object);
0636         laps.append(object);
0637     }
0638 
0639     json[QStringLiteral("laps")] = laps;
0640 }
0641 
0642 void MainWindow::exportLapsAsCsv(QTextStream& out)
0643 {
0644     out << '#' << timestampMessage() << '\r' << '\n';
0645     out << '#' << i18nc("@info:shell", "Lap number,Lap time,Global time,Note") << '\r' << '\n';
0646 
0647     for (auto i = 0; i < m_lapModel->rowCount(); i++) {
0648         const auto index = m_lapModel->index(i, 0);
0649         out << i;
0650         out << ',' << m_lapModel->data(index, static_cast<int>(LapModel::Roles::RelativeTimeRole)).toString();
0651         out << ',' << m_lapModel->data(index, static_cast<int>(LapModel::Roles::AbsoluteTimeRole)).toString();
0652         out << ',' << m_lapModel->data(index, static_cast<int>(LapModel::Roles::NoteRole)).toString();
0653         out << '\r' << '\n';
0654     }
0655 }
0656 
0657 bool MainWindow::isWindowSizeSaved() const
0658 {
0659     KConfigGroup group {KSharedConfig::openConfig(), "MainWindow"};
0660 
0661     const auto keys = group.keyList();
0662     for (const auto& key : keys) {
0663         // Size keys contain the screen size, e.g. 'Width 1920' and 'Height 1080'.
0664         if (key.startsWith(QLatin1String("Height")) or key.startsWith(QLatin1String("Width"))) {
0665             return true;
0666         }
0667     }
0668 
0669     return false;
0670 }
0671 
0672 QString MainWindow::timestampMessage()
0673 {
0674     const auto timestamp = QDateTime::currentDateTime();
0675 
0676     return i18nc("@info:shell", "Created by Kronometer on %1", timestamp.toString(Qt::DefaultLocaleLongDate));
0677 }
0678 
0679 void MainWindow::createControlMenuButton()
0680 {
0681     if (not m_controlMenuButton.isNull()) {
0682         return;
0683     }
0684 
0685     m_controlMenuButton = new QToolButton {this};
0686     m_controlMenuButton->setIcon(QIcon::fromTheme(QStringLiteral("application-menu")));
0687     m_controlMenuButton->setPopupMode(QToolButton::InstantPopup);
0688     m_controlMenuButton->setToolButtonStyle(toolBar()->toolButtonStyle());
0689 
0690     auto controlMenu = new QMenu {m_controlMenuButton};
0691     connect(controlMenu, &QMenu::aboutToShow, this, &MainWindow::slotUpdateControlMenu);
0692 
0693     m_controlMenuButton->setMenu(controlMenu);
0694 
0695     toolBar()->addWidget(m_controlMenuButton);
0696     connect(toolBar(), &KToolBar::iconSizeChanged,
0697             m_controlMenuButton, &QToolButton::setIconSize);
0698     connect(toolBar(), &KToolBar::toolButtonStyleChanged,
0699             m_controlMenuButton, &QToolButton::setToolButtonStyle);
0700 
0701     // The control button may get deleted when e.g. the toolbar gets edited.
0702     // In this case we must add it again. The adding is done asynchronously using a QTimer.
0703     m_controlMenuTimer.reset(new QTimer {});
0704     m_controlMenuTimer->setInterval(500);
0705     m_controlMenuTimer->setSingleShot(true);
0706     connect(m_controlMenuButton, &QObject::destroyed, m_controlMenuTimer.get(), qOverload<>(&QTimer::start));
0707     connect(m_controlMenuTimer.get(), &QTimer::timeout, this, &MainWindow::slotToolBarUpdated);
0708 }
0709 
0710 void MainWindow::deleteControlMenuButton()
0711 {
0712     delete m_controlMenuButton;
0713     m_controlMenuTimer.reset();
0714 }
0715 
0716 bool MainWindow::addActionToMenu(QAction *action, QMenu *menu)
0717 {
0718     if (not action or not menu)
0719         return false;
0720 
0721     const auto toolBarWidget = toolBar();
0722     const auto widgets = action->associatedWidgets();
0723     for (const auto widget : widgets) {
0724         if (widget == toolBarWidget) {
0725             return false;
0726         }
0727     }
0728 
0729     menu->addAction(action);
0730     return true;
0731 }
0732 
0733 void MainWindow::activateScreenInhibition()
0734 {
0735     if (m_screenInhibitCookie) {
0736         qWarning() << "Screen inhibition is already active.";
0737         return;
0738     }
0739 
0740     auto pendingCall = m_screensaverInterface->asyncCall(QStringLiteral("Inhibit"),
0741                                                          QCoreApplication::applicationName(),
0742                                                          i18n("A stopwatch is running."));
0743 
0744     auto pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall, this);
0745     connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *callWatcher) {
0746         QDBusPendingReply<quint32> reply = *callWatcher;
0747         if (reply.isValid()) {
0748             m_screenInhibitCookie = reply.value();
0749             qDebug() << "Screen inhibition activated, got cookie:" << m_screenInhibitCookie;
0750         } else {
0751             qWarning() << "Could not inhibit screen locker:" << reply.error();
0752         }
0753     });
0754 }
0755 
0756 void MainWindow::disactivateScreenInhibition()
0757 {
0758     if (!m_screenInhibitCookie) {
0759         return;
0760     }
0761 
0762     auto pendingCall = m_screensaverInterface->asyncCall(QStringLiteral("UnInhibit"), m_screenInhibitCookie);
0763 
0764     auto pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall, this);
0765     connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *callWatcher) {
0766         QDBusPendingReply<void> reply = *callWatcher;
0767         if (reply.isValid()) {
0768             qDebug() << "Screen inhibition disabled.";
0769             m_screenInhibitCookie = 0;
0770         } else {
0771             qWarning() << "Could not disable screen inhibition:" << reply.error();
0772         }
0773     });
0774 }
0775