File indexing completed on 2024-05-12 05:54:14

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