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