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"