Initial commit
This commit is contained in:
commit
4b310eb70e
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# CLion
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
|
||||
# Build artifacts
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.exe
|
||||
*_autogen/
|
||||
|
||||
# User-specific files
|
||||
*.user
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
109
CMakeLists.txt
Normal file
109
CMakeLists.txt
Normal file
@ -0,0 +1,109 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(PomodoroTimer VERSION 2.0.0 LANGUAGES CXX)
|
||||
|
||||
# Set C++ standard
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Find Qt6
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
# Define source files
|
||||
set(HEADERS
|
||||
PomodoroTimer.h
|
||||
CircularProgressBar.h
|
||||
SettingsDialog.h
|
||||
StatisticsDialog.h
|
||||
TimerState.h
|
||||
SystemTrayManager.h
|
||||
KeyboardShortcuts.h
|
||||
NotificationManager.h
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
PomodoroTimer.cpp
|
||||
CircularProgressBar.cpp
|
||||
SettingsDialog.cpp
|
||||
StatisticsDialog.cpp
|
||||
TimerState.cpp
|
||||
SystemTrayManager.cpp
|
||||
KeyboardShortcuts.cpp
|
||||
NotificationManager.cpp
|
||||
)
|
||||
|
||||
# Create executable
|
||||
qt6_add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
||||
|
||||
# Link Qt libraries
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# Set target properties
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
AUTOMOC ON
|
||||
AUTORCC ON
|
||||
AUTOUIC ON
|
||||
OUTPUT_NAME "PomodoroTimer"
|
||||
)
|
||||
|
||||
# Platform-specific configurations
|
||||
if(WIN32)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
elseif(APPLE)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Pomodoro Timer"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "com.pomodoroapp.timer"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
|
||||
MACOSX_BUNDLE_INFO_STRING "Pomodoro Focus Timer"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Compiler warnings and optimizations
|
||||
if(MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
/W4 /WX-
|
||||
$<$<CONFIG:Release>:/O2>
|
||||
)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
-Wall -Wextra -Wpedantic
|
||||
$<$<CONFIG:Release>:-O3>
|
||||
$<$<CONFIG:Debug>:-g -O0>
|
||||
)
|
||||
endif()
|
||||
|
||||
# --- Installation Rules ---
|
||||
|
||||
# Configure desktop file for Linux
|
||||
if(UNIX AND NOT APPLE)
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/pomodoro-timer.desktop.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pomodoro-timer.desktop"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_BINARY_DIR}/pomodoro-timer.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
# Install rules (optional)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Install prefix" FORCE)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
69
CircularProgressBar.cpp
Normal file
69
CircularProgressBar.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include "CircularProgressBar.h"
|
||||
#include <QPainter>
|
||||
#include <QResizeEvent>
|
||||
|
||||
CircularProgressBar::CircularProgressBar(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_value(0)
|
||||
, m_maximum(100)
|
||||
{
|
||||
setFixedSize(220, 220);
|
||||
}
|
||||
|
||||
void CircularProgressBar::setValue(int value)
|
||||
{
|
||||
int clampedValue = qBound(0, value, m_maximum);
|
||||
if (clampedValue != m_value) {
|
||||
m_value = clampedValue;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void CircularProgressBar::setMaximum(int maximum)
|
||||
{
|
||||
m_maximum = maximum;
|
||||
update();
|
||||
}
|
||||
|
||||
void CircularProgressBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int side = qMin(width(), height());
|
||||
QRect rect((width() - side) / 2 + 20, (height() - side) / 2 + 20,
|
||||
side - 40, side - 40);
|
||||
|
||||
// Background circle
|
||||
QPen backgroundPen(QColor(200, 200, 200), 12);
|
||||
backgroundPen.setCapStyle(Qt::RoundCap);
|
||||
painter.setPen(backgroundPen);
|
||||
painter.drawEllipse(rect);
|
||||
|
||||
// Progress arc
|
||||
if (m_value > 0) {
|
||||
QPen progressPen(QColor(70, 130, 180), 12);
|
||||
progressPen.setCapStyle(Qt::RoundCap);
|
||||
painter.setPen(progressPen);
|
||||
|
||||
int angle = (360 * m_value) / m_maximum;
|
||||
painter.drawArc(rect, 90 * 16, -angle * 16);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularProgressBar::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateChildPositions();
|
||||
}
|
||||
|
||||
void CircularProgressBar::updateChildPositions()
|
||||
{
|
||||
const auto children = findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
||||
for (QWidget *child : children) {
|
||||
child->resize(width(), 40);
|
||||
child->move(0, (height() - 40) / 2);
|
||||
}
|
||||
}
|
||||
29
CircularProgressBar.h
Normal file
29
CircularProgressBar.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef CIRCULARPROGRESSBAR_H
|
||||
#define CIRCULARPROGRESSBAR_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class CircularProgressBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CircularProgressBar(QWidget *parent = nullptr);
|
||||
|
||||
void setValue(int value);
|
||||
void setMaximum(int maximum);
|
||||
int value() const { return m_value; }
|
||||
int maximum() const { return m_maximum; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
int m_value;
|
||||
int m_maximum;
|
||||
|
||||
void updateChildPositions();
|
||||
};
|
||||
|
||||
#endif // CIRCULARPROGRESSBAR_H
|
||||
24
KeyboardShortcuts.cpp
Normal file
24
KeyboardShortcuts.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include "KeyboardShortcuts.h"
|
||||
#include <QWidget>
|
||||
|
||||
KeyboardShortcuts::KeyboardShortcuts(QWidget *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
setupShortcuts(parent);
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::setupShortcuts(QWidget *parent)
|
||||
{
|
||||
m_startShortcut = new QShortcut(QKeySequence(Qt::Key_Space), parent);
|
||||
m_pauseShortcut = new QShortcut(QKeySequence(Qt::Key_P), parent);
|
||||
m_resetShortcut = new QShortcut(QKeySequence(Qt::Key_R), parent);
|
||||
m_settingsShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Comma), parent);
|
||||
m_skipShortcut = new QShortcut(QKeySequence(Qt::Key_S), parent);
|
||||
|
||||
connect(m_startShortcut, &QShortcut::activated, this, &KeyboardShortcuts::startPauseRequested);
|
||||
connect(m_pauseShortcut, &QShortcut::activated, this, &KeyboardShortcuts::pauseRequested);
|
||||
connect(m_resetShortcut, &QShortcut::activated, this, &KeyboardShortcuts::resetRequested);
|
||||
connect(m_settingsShortcut, &QShortcut::activated, this, &KeyboardShortcuts::settingsRequested);
|
||||
connect(m_skipShortcut, &QShortcut::activated, this, &KeyboardShortcuts::skipRequested);
|
||||
}
|
||||
|
||||
33
KeyboardShortcuts.h
Normal file
33
KeyboardShortcuts.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef KEYBOARDSHORTCUTS_H
|
||||
#define KEYBOARDSHORTCUTS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QShortcut>
|
||||
|
||||
class KeyboardShortcuts : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KeyboardShortcuts(QWidget *parent = nullptr);
|
||||
~KeyboardShortcuts() = default;
|
||||
|
||||
signals:
|
||||
void startPauseRequested();
|
||||
void pauseRequested();
|
||||
void resetRequested();
|
||||
void settingsRequested();
|
||||
void skipRequested();
|
||||
|
||||
private:
|
||||
void setupShortcuts(QWidget *parent);
|
||||
|
||||
QShortcut *m_startShortcut;
|
||||
QShortcut *m_pauseShortcut;
|
||||
QShortcut *m_resetShortcut;
|
||||
QShortcut *m_settingsShortcut;
|
||||
QShortcut *m_skipShortcut;
|
||||
};
|
||||
|
||||
#endif // KEYBOARDSHORTCUTS_H
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
34
NotificationManager.cpp
Normal file
34
NotificationManager.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "NotificationManager.h"
|
||||
#include "SystemTrayManager.h"
|
||||
#include <QWidget>
|
||||
#include <QMessageBox>
|
||||
|
||||
NotificationManager::NotificationManager(QWidget *parent)
|
||||
: QObject(parent)
|
||||
, m_parent(parent)
|
||||
, m_trayManager(nullptr)
|
||||
, m_notificationsEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
void NotificationManager::setSystemTrayManager(SystemTrayManager *trayManager)
|
||||
{
|
||||
m_trayManager = trayManager;
|
||||
}
|
||||
|
||||
void NotificationManager::setNotificationsEnabled(bool enabled)
|
||||
{
|
||||
m_notificationsEnabled = enabled;
|
||||
}
|
||||
|
||||
void NotificationManager::showNotification(const QString &message)
|
||||
{
|
||||
if (!m_notificationsEnabled) return;
|
||||
|
||||
if (m_trayManager && m_trayManager->isVisible()) {
|
||||
m_trayManager->showMessage("Pomodoro Timer", message);
|
||||
} else if (m_parent) {
|
||||
QMessageBox::information(m_parent, "Pomodoro Timer", message);
|
||||
}
|
||||
}
|
||||
|
||||
29
NotificationManager.h
Normal file
29
NotificationManager.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef NOTIFICATIONMANAGER_H
|
||||
#define NOTIFICATIONMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
class SystemTrayManager;
|
||||
|
||||
class NotificationManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NotificationManager(QWidget *parent = nullptr);
|
||||
~NotificationManager() = default;
|
||||
|
||||
void setSystemTrayManager(SystemTrayManager *trayManager);
|
||||
void setNotificationsEnabled(bool enabled);
|
||||
void showNotification(const QString &message);
|
||||
|
||||
private:
|
||||
QWidget *m_parent;
|
||||
SystemTrayManager *m_trayManager;
|
||||
bool m_notificationsEnabled;
|
||||
};
|
||||
|
||||
#endif // NOTIFICATIONMANAGER_H
|
||||
|
||||
500
PomodoroTimer.cpp
Normal file
500
PomodoroTimer.cpp
Normal file
@ -0,0 +1,500 @@
|
||||
#include "PomodoroTimer.h"
|
||||
#include "CircularProgressBar.h"
|
||||
#include "SettingsDialog.h"
|
||||
#include "StatisticsDialog.h"
|
||||
#include "SystemTrayManager.h"
|
||||
#include "KeyboardShortcuts.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "TimerState.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QDateTime>
|
||||
#include <QFont>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
// UI Layout constants
|
||||
constexpr int WINDOW_WIDTH = 440;
|
||||
constexpr int WINDOW_HEIGHT = 550;
|
||||
constexpr int PROGRESS_BAR_SIZE = 220;
|
||||
constexpr int MAIN_BUTTON_WIDTH = 100;
|
||||
constexpr int MAIN_BUTTON_HEIGHT = 40;
|
||||
constexpr int SMALL_BUTTON_WIDTH = 90;
|
||||
constexpr int SMALL_BUTTON_HEIGHT = 35;
|
||||
constexpr int LAYOUT_SPACING = 10;
|
||||
constexpr int LAYOUT_MARGIN = 25;
|
||||
|
||||
// Font sizes
|
||||
constexpr int TIME_FONT_SIZE = 24;
|
||||
constexpr int STATUS_FONT_SIZE = 14;
|
||||
constexpr int SESSION_FONT_SIZE = 12;
|
||||
constexpr int BUTTON_FONT_SIZE = 11;
|
||||
|
||||
// Progress bar positioning
|
||||
constexpr int TIME_LABEL_Y_OFFSET = 90;
|
||||
}
|
||||
|
||||
PomodoroTimer::PomodoroTimer(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_timer(new QTimer(this))
|
||||
, m_trayManager(std::make_unique<SystemTrayManager>(this))
|
||||
, m_keyboardShortcuts(std::make_unique<KeyboardShortcuts>(this))
|
||||
, m_notificationManager(std::make_unique<NotificationManager>(this))
|
||||
{
|
||||
loadSettings();
|
||||
setupUI();
|
||||
setupConnections();
|
||||
resetTimerState();
|
||||
updateDisplay();
|
||||
|
||||
// Setup manager connections
|
||||
m_notificationManager->setSystemTrayManager(m_trayManager.get());
|
||||
m_notificationManager->setNotificationsEnabled(m_showNotifications);
|
||||
m_trayManager->show();
|
||||
}
|
||||
|
||||
PomodoroTimer::~PomodoroTimer()
|
||||
{
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
void PomodoroTimer::setupUI()
|
||||
{
|
||||
setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
m_mainFrame = new QFrame(this);
|
||||
m_circularProgress = new CircularProgressBar(m_mainFrame);
|
||||
m_circularProgress->setFixedSize(PROGRESS_BAR_SIZE, PROGRESS_BAR_SIZE);
|
||||
|
||||
createLabels();
|
||||
createButtons();
|
||||
createLayouts();
|
||||
applyStyles();
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void PomodoroTimer::createLabels()
|
||||
{
|
||||
// Time display label
|
||||
m_timeLabel = new QLabel("25:00", m_circularProgress);
|
||||
m_timeLabel->setAlignment(Qt::AlignCenter);
|
||||
m_timeLabel->resize(PROGRESS_BAR_SIZE, 40);
|
||||
m_timeLabel->move(0, TIME_LABEL_Y_OFFSET);
|
||||
|
||||
// Status indicator label
|
||||
m_statusLabel = new QLabel("Ready to Focus", m_mainFrame);
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
// Session progress label
|
||||
m_sessionLabel = new QLabel("Session 1 of 4", m_mainFrame);
|
||||
m_sessionLabel->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
void PomodoroTimer::createButtons()
|
||||
{
|
||||
m_startButton = new QPushButton("Start", m_mainFrame);
|
||||
m_pauseButton = new QPushButton("Pause", m_mainFrame);
|
||||
m_resetButton = new QPushButton("Reset", m_mainFrame);
|
||||
m_settingsButton = new QPushButton("Settings", m_mainFrame);
|
||||
m_skipButton = new QPushButton("Skip", m_mainFrame);
|
||||
m_statsButton = new QPushButton("Statistics", m_mainFrame);
|
||||
|
||||
// Set button sizes
|
||||
const QSize mainButtonSize(MAIN_BUTTON_WIDTH, MAIN_BUTTON_HEIGHT);
|
||||
const QSize smallButtonSize(SMALL_BUTTON_WIDTH, SMALL_BUTTON_HEIGHT);
|
||||
|
||||
m_startButton->setFixedSize(mainButtonSize);
|
||||
m_pauseButton->setFixedSize(mainButtonSize);
|
||||
m_resetButton->setFixedSize(mainButtonSize);
|
||||
m_settingsButton->setFixedSize(mainButtonSize);
|
||||
m_skipButton->setFixedSize(smallButtonSize);
|
||||
m_statsButton->setFixedSize(smallButtonSize);
|
||||
}
|
||||
|
||||
void PomodoroTimer::createLayouts()
|
||||
{
|
||||
// Progress bar layout
|
||||
auto progressLayout = new QVBoxLayout();
|
||||
progressLayout->addWidget(m_circularProgress, 0, Qt::AlignCenter);
|
||||
|
||||
// Main control buttons layout
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(LAYOUT_SPACING);
|
||||
buttonLayout->addWidget(m_startButton);
|
||||
buttonLayout->addWidget(m_pauseButton);
|
||||
buttonLayout->addWidget(m_resetButton);
|
||||
|
||||
// Header layout with settings button
|
||||
auto headerLayout = new QHBoxLayout();
|
||||
headerLayout->addStretch();
|
||||
headerLayout->addWidget(m_settingsButton);
|
||||
|
||||
// Extra buttons layout
|
||||
auto extraButtonLayout = new QHBoxLayout();
|
||||
extraButtonLayout->setSpacing(LAYOUT_SPACING);
|
||||
extraButtonLayout->addStretch();
|
||||
extraButtonLayout->addWidget(m_skipButton);
|
||||
extraButtonLayout->addWidget(m_statsButton);
|
||||
extraButtonLayout->addStretch();
|
||||
|
||||
// Main layout assembly
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(headerLayout);
|
||||
mainLayout->addSpacing(5);
|
||||
mainLayout->addWidget(m_statusLabel);
|
||||
mainLayout->addSpacing(15);
|
||||
mainLayout->addLayout(progressLayout);
|
||||
mainLayout->addSpacing(15);
|
||||
mainLayout->addWidget(m_sessionLabel);
|
||||
mainLayout->addSpacing(20);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
mainLayout->addSpacing(15);
|
||||
mainLayout->addLayout(extraButtonLayout);
|
||||
mainLayout->setContentsMargins(LAYOUT_MARGIN, LAYOUT_MARGIN, LAYOUT_MARGIN, LAYOUT_MARGIN);
|
||||
|
||||
m_mainFrame->setLayout(mainLayout);
|
||||
|
||||
// Frame layout
|
||||
auto frameLayout = new QVBoxLayout();
|
||||
frameLayout->addWidget(m_mainFrame);
|
||||
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(frameLayout);
|
||||
}
|
||||
|
||||
void PomodoroTimer::applyStyles()
|
||||
{
|
||||
QFont timeFont = m_timeLabel->font();
|
||||
timeFont.setPointSize(TIME_FONT_SIZE);
|
||||
timeFont.setBold(true);
|
||||
m_timeLabel->setFont(timeFont);
|
||||
|
||||
QFont statusFont = m_statusLabel->font();
|
||||
statusFont.setPointSize(STATUS_FONT_SIZE);
|
||||
m_statusLabel->setFont(statusFont);
|
||||
|
||||
QFont sessionFont = m_sessionLabel->font();
|
||||
sessionFont.setPointSize(SESSION_FONT_SIZE);
|
||||
m_sessionLabel->setFont(sessionFont);
|
||||
|
||||
QFont buttonFont;
|
||||
buttonFont.setPointSize(BUTTON_FONT_SIZE);
|
||||
|
||||
const auto buttons = {m_startButton, m_pauseButton, m_resetButton,
|
||||
m_settingsButton, m_skipButton, m_statsButton};
|
||||
for (auto* button : buttons) {
|
||||
if (button) button->setFont(buttonFont);
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::setupConnections()
|
||||
{
|
||||
// Timer connections
|
||||
connect(m_timer, &QTimer::timeout, this, &PomodoroTimer::onUpdateTimer);
|
||||
|
||||
// Button connections
|
||||
connect(m_startButton, &QPushButton::clicked, this, &PomodoroTimer::onStartTimer);
|
||||
connect(m_pauseButton, &QPushButton::clicked, this, &PomodoroTimer::onPauseTimer);
|
||||
connect(m_resetButton, &QPushButton::clicked, this, &PomodoroTimer::onResetTimer);
|
||||
connect(m_settingsButton, &QPushButton::clicked, this, &PomodoroTimer::onShowSettings);
|
||||
connect(m_skipButton, &QPushButton::clicked, this, &PomodoroTimer::onSkipSession);
|
||||
connect(m_statsButton, &QPushButton::clicked, this, &PomodoroTimer::onShowStatistics);
|
||||
|
||||
// System tray connections
|
||||
connect(m_trayManager.get(), &SystemTrayManager::showMainWindow, this, &PomodoroTimer::onToggleVisibility);
|
||||
connect(m_trayManager.get(), &SystemTrayManager::startPauseRequested, this, [this]() {
|
||||
m_isRunning ? onPauseTimer() : onStartTimer();
|
||||
});
|
||||
connect(m_trayManager.get(), &SystemTrayManager::resetRequested, this, &PomodoroTimer::onResetTimer);
|
||||
connect(m_trayManager.get(), &SystemTrayManager::skipRequested, this, &PomodoroTimer::onSkipSession);
|
||||
connect(m_trayManager.get(), &SystemTrayManager::settingsRequested, this, &PomodoroTimer::onShowSettings);
|
||||
connect(m_trayManager.get(), &SystemTrayManager::statisticsRequested, this, &PomodoroTimer::onShowStatistics);
|
||||
connect(m_trayManager.get(), &SystemTrayManager::quitRequested, qApp, &QCoreApplication::quit);
|
||||
|
||||
// Keyboard shortcuts connections
|
||||
connect(m_keyboardShortcuts.get(), &KeyboardShortcuts::startPauseRequested, this, [this]() {
|
||||
m_isRunning ? onPauseTimer() : onStartTimer();
|
||||
});
|
||||
connect(m_keyboardShortcuts.get(), &KeyboardShortcuts::pauseRequested, this, &PomodoroTimer::onPauseTimer);
|
||||
connect(m_keyboardShortcuts.get(), &KeyboardShortcuts::resetRequested, this, &PomodoroTimer::onResetTimer);
|
||||
connect(m_keyboardShortcuts.get(), &KeyboardShortcuts::settingsRequested, this, &PomodoroTimer::onShowSettings);
|
||||
connect(m_keyboardShortcuts.get(), &KeyboardShortcuts::skipRequested, this, &PomodoroTimer::onSkipSession);
|
||||
}
|
||||
|
||||
// Timer control methods
|
||||
void PomodoroTimer::onStartTimer()
|
||||
{
|
||||
if (m_isRunning) return;
|
||||
|
||||
m_isRunning = true;
|
||||
m_sessionStartTime = QDateTime::currentDateTime();
|
||||
m_timer->start(TIMER_INTERVAL_MS);
|
||||
|
||||
m_statusLabel->setText(TimerStateHelper::getStatusMessage(m_currentState, true));
|
||||
updateButtonStates();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void PomodoroTimer::onPauseTimer()
|
||||
{
|
||||
if (!m_isRunning) return;
|
||||
|
||||
m_timer->stop();
|
||||
m_isRunning = false;
|
||||
m_statusLabel->setText("⏸ Paused");
|
||||
updateButtonStates();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void PomodoroTimer::onResetTimer()
|
||||
{
|
||||
m_timer->stop();
|
||||
m_isRunning = false;
|
||||
resetTimerState();
|
||||
updateDisplay();
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void PomodoroTimer::onUpdateTimer()
|
||||
{
|
||||
--m_currentTime;
|
||||
updateDisplay();
|
||||
|
||||
if (m_currentTime <= 0) {
|
||||
onTimerFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::onTimerFinished()
|
||||
{
|
||||
m_timer->stop();
|
||||
m_isRunning = false;
|
||||
|
||||
// Update statistics
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
const int sessionDuration = static_cast<int>(m_sessionStartTime.secsTo(now));
|
||||
|
||||
if (m_currentState == TimerState::Work) {
|
||||
m_totalWorkTime += sessionDuration;
|
||||
m_totalSessions++;
|
||||
m_completedSessions++;
|
||||
} else {
|
||||
m_totalBreakTime += sessionDuration;
|
||||
}
|
||||
|
||||
// Determine next state and show notifications
|
||||
QString message, nextAction;
|
||||
if (m_currentState == TimerState::Work) {
|
||||
message = "🎉 Focus session completed!";
|
||||
|
||||
const bool isLongBreakTime = (m_completedSessions % SESSIONS_BEFORE_LONG_BREAK == 0);
|
||||
updateTimerState(isLongBreakTime ? TimerState::LongBreak : TimerState::ShortBreak);
|
||||
nextAction = isLongBreakTime ? "Time for a long break! 🧘♀️" : "Take a short break! ☕";
|
||||
} else {
|
||||
message = "Break time finished!";
|
||||
nextAction = "Ready to focus again? 🎯";
|
||||
updateTimerState(TimerState::Work);
|
||||
}
|
||||
|
||||
m_notificationManager->showNotification(message + " " + nextAction);
|
||||
onResetTimer();
|
||||
|
||||
// Auto-start if enabled
|
||||
const bool shouldAutoStart = (m_currentState != TimerState::Work && m_autoStartBreaks) ||
|
||||
(m_currentState == TimerState::Work && m_autoStartWork);
|
||||
if (shouldAutoStart) {
|
||||
QTimer::singleShot(AUTO_START_DELAY_MS, this, &PomodoroTimer::onStartTimer);
|
||||
}
|
||||
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
void PomodoroTimer::resetTimerState()
|
||||
{
|
||||
m_currentTime = TimerStateHelper::getDurationForState(m_currentState, m_workDuration, m_shortBreakDuration, m_longBreakDuration);
|
||||
m_totalDuration = m_currentTime;
|
||||
m_statusLabel->setText(TimerStateHelper::getStatusMessage(m_currentState, false));
|
||||
}
|
||||
|
||||
void PomodoroTimer::updateTimerState(TimerState newState)
|
||||
{
|
||||
m_currentState = newState;
|
||||
}
|
||||
|
||||
QString PomodoroTimer::formatTime(int seconds) const
|
||||
{
|
||||
const int minutes = seconds / 60;
|
||||
const int secs = seconds % 60;
|
||||
return QString("%1:%2").arg(minutes, 2, 10, QChar('0'))
|
||||
.arg(secs, 2, 10, QChar('0'));
|
||||
}
|
||||
|
||||
void PomodoroTimer::updateDisplay()
|
||||
{
|
||||
m_timeLabel->setText(formatTime(m_currentTime));
|
||||
|
||||
const int progress = m_totalDuration > 0 ?
|
||||
((m_totalDuration - m_currentTime) * 100) / m_totalDuration : 0;
|
||||
m_circularProgress->setValue(progress);
|
||||
|
||||
updateSessionCounter();
|
||||
updateWindowTitle();
|
||||
|
||||
// Update system tray
|
||||
const int currentSession = (m_completedSessions % SESSIONS_BEFORE_LONG_BREAK) + 1;
|
||||
m_trayManager->updateTooltip(m_currentState, m_isRunning, formatTime(m_currentTime),
|
||||
currentSession, SESSIONS_BEFORE_LONG_BREAK);
|
||||
}
|
||||
|
||||
void PomodoroTimer::updateSessionCounter()
|
||||
{
|
||||
const int currentSession = (m_completedSessions % SESSIONS_BEFORE_LONG_BREAK) + 1;
|
||||
m_sessionLabel->setText(QString("Session %1 of %2")
|
||||
.arg(currentSession)
|
||||
.arg(SESSIONS_BEFORE_LONG_BREAK));
|
||||
}
|
||||
|
||||
void PomodoroTimer::updateButtonStates()
|
||||
{
|
||||
m_startButton->setEnabled(!m_isRunning);
|
||||
m_pauseButton->setEnabled(m_isRunning);
|
||||
|
||||
const bool shouldShowReset = m_isRunning || m_currentTime < m_totalDuration;
|
||||
m_resetButton->setVisible(shouldShowReset);
|
||||
m_resetButton->setEnabled(true);
|
||||
|
||||
m_skipButton->setEnabled(m_isRunning);
|
||||
m_skipButton->setVisible(m_isRunning);
|
||||
|
||||
// Force layout update
|
||||
if (m_mainFrame && m_mainFrame->layout()) {
|
||||
m_mainFrame->layout()->update();
|
||||
m_mainFrame->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::updateWindowTitle()
|
||||
{
|
||||
const QString timeRemaining = formatTime(m_currentTime);
|
||||
const QString emoji = TimerStateHelper::getStateEmoji(m_currentState);
|
||||
|
||||
QString title;
|
||||
if (m_isRunning) {
|
||||
const QString stateText = (m_currentState == TimerState::Work) ? "Focus" : "Break";
|
||||
title = QString("%1 %2: %3").arg(emoji, stateText, timeRemaining);
|
||||
} else {
|
||||
title = "🍅 Pomodoro Timer";
|
||||
}
|
||||
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
void PomodoroTimer::onShowSettings()
|
||||
{
|
||||
SettingsDialog dialog(this);
|
||||
|
||||
dialog.setWorkDuration(m_workDuration / 60);
|
||||
dialog.setShortBreakDuration(m_shortBreakDuration / 60);
|
||||
dialog.setLongBreakDuration(m_longBreakDuration / 60);
|
||||
dialog.setAutoStartBreaks(m_autoStartBreaks);
|
||||
dialog.setAutoStartWork(m_autoStartWork);
|
||||
dialog.setMinimizeToTray(m_minimizeToTray);
|
||||
dialog.setShowNotifications(m_showNotifications);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
m_workDuration = dialog.workDuration() * 60;
|
||||
m_shortBreakDuration = dialog.shortBreakDuration() * 60;
|
||||
m_longBreakDuration = dialog.longBreakDuration() * 60;
|
||||
m_autoStartBreaks = dialog.autoStartBreaks();
|
||||
m_autoStartWork = dialog.autoStartWork();
|
||||
m_minimizeToTray = dialog.minimizeToTray();
|
||||
m_showNotifications = dialog.showNotifications();
|
||||
|
||||
saveSettings();
|
||||
onResetTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::onSkipSession()
|
||||
{
|
||||
if (m_isRunning) {
|
||||
onTimerFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::onShowStatistics()
|
||||
{
|
||||
StatisticsDialog dialog(this);
|
||||
dialog.setStatistics(m_totalSessions, m_totalWorkTime, m_totalBreakTime);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void PomodoroTimer::onToggleVisibility()
|
||||
{
|
||||
if (isVisible()) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void PomodoroTimer::loadSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
m_workDuration = settings.value("workDuration", DEFAULT_WORK_DURATION).toInt();
|
||||
m_shortBreakDuration = settings.value("shortBreakDuration", DEFAULT_SHORT_BREAK).toInt();
|
||||
m_longBreakDuration = settings.value("longBreakDuration", DEFAULT_LONG_BREAK).toInt();
|
||||
m_autoStartBreaks = settings.value("autoStartBreaks", false).toBool();
|
||||
m_autoStartWork = settings.value("autoStartWork", false).toBool();
|
||||
m_minimizeToTray = settings.value("minimizeToTray", true).toBool();
|
||||
m_showNotifications = settings.value("showNotifications", true).toBool();
|
||||
m_totalSessions = settings.value("totalSessions", 0).toInt();
|
||||
m_totalWorkTime = settings.value("totalWorkTime", 0).toInt();
|
||||
m_totalBreakTime = settings.value("totalBreakTime", 0).toInt();
|
||||
}
|
||||
|
||||
void PomodoroTimer::saveSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("workDuration", m_workDuration);
|
||||
settings.setValue("shortBreakDuration", m_shortBreakDuration);
|
||||
settings.setValue("longBreakDuration", m_longBreakDuration);
|
||||
settings.setValue("autoStartBreaks", m_autoStartBreaks);
|
||||
settings.setValue("autoStartWork", m_autoStartWork);
|
||||
settings.setValue("minimizeToTray", m_minimizeToTray);
|
||||
settings.setValue("showNotifications", m_showNotifications);
|
||||
settings.setValue("totalSessions", m_totalSessions);
|
||||
settings.setValue("totalWorkTime", m_totalWorkTime);
|
||||
settings.setValue("totalBreakTime", m_totalBreakTime);
|
||||
}
|
||||
|
||||
void PomodoroTimer::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
hide();
|
||||
}
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void PomodoroTimer::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (m_trayManager && m_trayManager->isVisible()) {
|
||||
hide();
|
||||
event->ignore();
|
||||
|
||||
static bool firstHide = true;
|
||||
if (firstHide) {
|
||||
m_notificationManager->showNotification("Application was minimized to tray. Click the tray icon to show again.");
|
||||
firstHide = false;
|
||||
}
|
||||
} else {
|
||||
saveSettings();
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
136
PomodoroTimer.h
Normal file
136
PomodoroTimer.h
Normal file
@ -0,0 +1,136 @@
|
||||
#ifndef POMODOROTIMER_H
|
||||
#define POMODOROTIMER_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QFrame>
|
||||
#include <QDateTime>
|
||||
#include <memory>
|
||||
#include "TimerState.h"
|
||||
|
||||
// Forward declarations
|
||||
class CircularProgressBar;
|
||||
class SettingsDialog;
|
||||
class StatisticsDialog;
|
||||
class SystemTrayManager;
|
||||
class KeyboardShortcuts;
|
||||
class NotificationManager;
|
||||
|
||||
class PomodoroTimer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PomodoroTimer(QWidget *parent = nullptr);
|
||||
~PomodoroTimer() override;
|
||||
|
||||
// Timer configuration constants
|
||||
static constexpr int DEFAULT_WORK_DURATION = 1500; // 25 minutes in seconds
|
||||
static constexpr int DEFAULT_SHORT_BREAK = 300; // 5 minutes in seconds
|
||||
static constexpr int DEFAULT_LONG_BREAK = 900; // 15 minutes in seconds
|
||||
static constexpr int SESSIONS_BEFORE_LONG_BREAK = 4;
|
||||
static constexpr int TIMER_INTERVAL_MS = 1000;
|
||||
static constexpr int AUTO_START_DELAY_MS = 3000;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
// Timer control slots
|
||||
void onStartTimer();
|
||||
void onPauseTimer();
|
||||
void onResetTimer();
|
||||
void onUpdateTimer();
|
||||
void onTimerFinished();
|
||||
|
||||
// UI interaction slots
|
||||
void onShowSettings();
|
||||
void onSkipSession();
|
||||
void onShowStatistics();
|
||||
void onToggleVisibility();
|
||||
|
||||
private:
|
||||
// Setup methods
|
||||
void setupUI();
|
||||
void setupConnections();
|
||||
|
||||
// UI creation helpers
|
||||
void createLabels();
|
||||
void createButtons();
|
||||
void createLayouts();
|
||||
void applyStyles();
|
||||
|
||||
// Settings management
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
// Timer state management
|
||||
void resetTimerState();
|
||||
void updateTimerState(TimerState newState);
|
||||
|
||||
// Display update methods - optimized to avoid unnecessary updates
|
||||
void updateDisplay();
|
||||
void updateSessionCounter();
|
||||
void updateButtonStates();
|
||||
void updateWindowTitle();
|
||||
void updateTrayTooltip();
|
||||
|
||||
// Utility methods
|
||||
QString formatTime(int seconds) const;
|
||||
bool needsDisplayUpdate() const;
|
||||
|
||||
// UI elements - use raw pointers for Qt objects with parent ownership
|
||||
QTimer *m_timer;
|
||||
QLabel *m_timeLabel{nullptr};
|
||||
QLabel *m_statusLabel{nullptr};
|
||||
QLabel *m_sessionLabel{nullptr};
|
||||
QPushButton *m_startButton{nullptr};
|
||||
QPushButton *m_pauseButton{nullptr};
|
||||
QPushButton *m_resetButton{nullptr};
|
||||
QPushButton *m_settingsButton{nullptr};
|
||||
QPushButton *m_skipButton{nullptr};
|
||||
QPushButton *m_statsButton{nullptr};
|
||||
CircularProgressBar *m_circularProgress{nullptr};
|
||||
QFrame *m_mainFrame{nullptr};
|
||||
|
||||
// Managers - keep as unique_ptr for complex objects
|
||||
std::unique_ptr<SystemTrayManager> m_trayManager;
|
||||
std::unique_ptr<KeyboardShortcuts> m_keyboardShortcuts;
|
||||
std::unique_ptr<NotificationManager> m_notificationManager;
|
||||
|
||||
// Timer state
|
||||
int m_currentTime{0};
|
||||
int m_totalDuration{0};
|
||||
TimerState m_currentState{TimerState::Work};
|
||||
int m_completedSessions{0};
|
||||
bool m_isRunning{false};
|
||||
|
||||
// Cache for reducing string operations
|
||||
mutable QString m_cachedTimeString;
|
||||
mutable int m_lastFormattedTime{-1};
|
||||
|
||||
// Settings
|
||||
int m_workDuration{DEFAULT_WORK_DURATION};
|
||||
int m_shortBreakDuration{DEFAULT_SHORT_BREAK};
|
||||
int m_longBreakDuration{DEFAULT_LONG_BREAK};
|
||||
bool m_autoStartBreaks{false};
|
||||
bool m_autoStartWork{false};
|
||||
bool m_minimizeToTray{true};
|
||||
bool m_showNotifications{true};
|
||||
|
||||
// Statistics
|
||||
int m_totalSessions{0};
|
||||
int m_totalWorkTime{0};
|
||||
int m_totalBreakTime{0};
|
||||
QDateTime m_sessionStartTime;
|
||||
|
||||
// Update tracking to prevent unnecessary redraws
|
||||
bool m_needsButtonUpdate{true};
|
||||
bool m_needsSessionUpdate{true};
|
||||
QString m_lastWindowTitle;
|
||||
};
|
||||
|
||||
#endif // POMODOROTIMER_H
|
||||
44
README.md
Normal file
44
README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Pomodoro Timer
|
||||
|
||||
A simple Pomodoro Timer application built with C++ and the Qt framework.
|
||||
|
||||
## Features
|
||||
|
||||
* Work, Short Break, and Long Break timers.
|
||||
* Customizable timer durations.
|
||||
* System tray integration with status updates.
|
||||
* Desktop notifications for state changes.
|
||||
* Session tracking.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* A C++17 compatible compiler
|
||||
* Qt 6
|
||||
* CMake
|
||||
|
||||
### Steps
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
```
|
||||
2. Create a build directory:
|
||||
```bash
|
||||
cd PomodoroTimer
|
||||
mkdir build && cd build
|
||||
```
|
||||
3. Run CMake and build the project:
|
||||
```bash
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
4. Run the application:
|
||||
```bash
|
||||
./PomodoroTimer
|
||||
```
|
||||
102
SettingsDialog.cpp
Normal file
102
SettingsDialog.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "SettingsDialog.h"
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Pomodoro Settings");
|
||||
setFixedSize(350, 300); // Reduced from 400 since we removed sound settings
|
||||
// Remove all custom styling
|
||||
setStyleSheet("");
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void SettingsDialog::setupUI()
|
||||
{
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
// Timer Settings Group
|
||||
QGroupBox *timerGroup = new QGroupBox("Timer Settings");
|
||||
QFormLayout *timerLayout = new QFormLayout(timerGroup);
|
||||
|
||||
m_workDurationSpin = new QSpinBox();
|
||||
m_workDurationSpin->setRange(1, 60);
|
||||
m_workDurationSpin->setSuffix(" minutes");
|
||||
timerLayout->addRow("Work Duration:", m_workDurationSpin);
|
||||
|
||||
m_shortBreakSpin = new QSpinBox();
|
||||
m_shortBreakSpin->setRange(1, 30);
|
||||
m_shortBreakSpin->setSuffix(" minutes");
|
||||
timerLayout->addRow("Short Break:", m_shortBreakSpin);
|
||||
|
||||
m_longBreakSpin = new QSpinBox();
|
||||
m_longBreakSpin->setRange(5, 60);
|
||||
m_longBreakSpin->setSuffix(" minutes");
|
||||
timerLayout->addRow("Long Break:", m_longBreakSpin);
|
||||
|
||||
// Behavior Settings Group
|
||||
QGroupBox *behaviorGroup = new QGroupBox("Behavior");
|
||||
QVBoxLayout *behaviorLayout = new QVBoxLayout(behaviorGroup);
|
||||
|
||||
m_autoStartBreaksCheck = new QCheckBox("Auto-start breaks");
|
||||
m_autoStartWorkCheck = new QCheckBox("Auto-start work sessions");
|
||||
m_minimizeToTrayCheck = new QCheckBox("Minimize to system tray");
|
||||
m_showNotificationsCheck = new QCheckBox("Show notifications");
|
||||
|
||||
behaviorLayout->addWidget(m_autoStartBreaksCheck);
|
||||
behaviorLayout->addWidget(m_autoStartWorkCheck);
|
||||
behaviorLayout->addWidget(m_minimizeToTrayCheck);
|
||||
behaviorLayout->addWidget(m_showNotificationsCheck);
|
||||
|
||||
// Dialog Buttons
|
||||
m_buttonBox = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
|
||||
|
||||
// Layout
|
||||
mainLayout->addWidget(timerGroup);
|
||||
mainLayout->addWidget(behaviorGroup);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->addWidget(m_buttonBox);
|
||||
|
||||
// Connections
|
||||
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_buttonBox->button(QDialogButtonBox::RestoreDefaults),
|
||||
&QPushButton::clicked, this, &SettingsDialog::resetToDefaults);
|
||||
}
|
||||
|
||||
// Getters
|
||||
int SettingsDialog::workDuration() const { return m_workDurationSpin->value(); }
|
||||
int SettingsDialog::shortBreakDuration() const { return m_shortBreakSpin->value(); }
|
||||
int SettingsDialog::longBreakDuration() const { return m_longBreakSpin->value(); }
|
||||
bool SettingsDialog::autoStartBreaks() const { return m_autoStartBreaksCheck->isChecked(); }
|
||||
bool SettingsDialog::autoStartWork() const { return m_autoStartWorkCheck->isChecked(); }
|
||||
bool SettingsDialog::minimizeToTray() const { return m_minimizeToTrayCheck->isChecked(); }
|
||||
bool SettingsDialog::showNotifications() const { return m_showNotificationsCheck->isChecked(); }
|
||||
|
||||
// Setters
|
||||
void SettingsDialog::setWorkDuration(int minutes) { m_workDurationSpin->setValue(minutes); }
|
||||
void SettingsDialog::setShortBreakDuration(int minutes) { m_shortBreakSpin->setValue(minutes); }
|
||||
void SettingsDialog::setLongBreakDuration(int minutes) { m_longBreakSpin->setValue(minutes); }
|
||||
void SettingsDialog::setAutoStartBreaks(bool enabled) { m_autoStartBreaksCheck->setChecked(enabled); }
|
||||
void SettingsDialog::setAutoStartWork(bool enabled) { m_autoStartWorkCheck->setChecked(enabled); }
|
||||
void SettingsDialog::setMinimizeToTray(bool enabled) { m_minimizeToTrayCheck->setChecked(enabled); }
|
||||
void SettingsDialog::setShowNotifications(bool enabled) { m_showNotificationsCheck->setChecked(enabled); }
|
||||
|
||||
void SettingsDialog::resetToDefaults()
|
||||
{
|
||||
setWorkDuration(25);
|
||||
setShortBreakDuration(5);
|
||||
setLongBreakDuration(15);
|
||||
setAutoStartBreaks(false);
|
||||
setAutoStartWork(false);
|
||||
setMinimizeToTray(true);
|
||||
setShowNotifications(true);
|
||||
}
|
||||
57
SettingsDialog.h
Normal file
57
SettingsDialog.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef SETTINGSDIALOG_H
|
||||
#define SETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
// Forward declarations
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||
|
||||
// Getters
|
||||
int workDuration() const;
|
||||
int shortBreakDuration() const;
|
||||
int longBreakDuration() const;
|
||||
bool autoStartBreaks() const;
|
||||
bool autoStartWork() const;
|
||||
bool minimizeToTray() const;
|
||||
bool showNotifications() const;
|
||||
|
||||
// Setters
|
||||
void setWorkDuration(int minutes);
|
||||
void setShortBreakDuration(int minutes);
|
||||
void setLongBreakDuration(int minutes);
|
||||
void setAutoStartBreaks(bool enabled);
|
||||
void setAutoStartWork(bool enabled);
|
||||
void setMinimizeToTray(bool enabled);
|
||||
void setShowNotifications(bool enabled);
|
||||
|
||||
signals:
|
||||
void settingsChanged();
|
||||
|
||||
private slots:
|
||||
void resetToDefaults();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
// UI elements
|
||||
QSpinBox *m_workDurationSpin;
|
||||
QSpinBox *m_shortBreakSpin;
|
||||
QSpinBox *m_longBreakSpin;
|
||||
QCheckBox *m_autoStartBreaksCheck;
|
||||
QCheckBox *m_autoStartWorkCheck;
|
||||
QCheckBox *m_minimizeToTrayCheck;
|
||||
QCheckBox *m_showNotificationsCheck;
|
||||
QDialogButtonBox *m_buttonBox;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
402
StatisticsDialog.cpp
Normal file
402
StatisticsDialog.cpp
Normal file
@ -0,0 +1,402 @@
|
||||
#include "StatisticsDialog.h"
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QScrollArea>
|
||||
#include <QPainter>
|
||||
#include <QSettings>
|
||||
#include <QDate>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QTextStream>
|
||||
#include <QApplication>
|
||||
|
||||
// StatisticsChart implementation
|
||||
StatisticsChart::StatisticsChart(QWidget *parent)
|
||||
: QWidget(parent), m_maxValue(0)
|
||||
{
|
||||
setMinimumSize(400, 200);
|
||||
setStyleSheet("background-color: white; border: 1px solid #ccc;");
|
||||
}
|
||||
|
||||
void StatisticsChart::setData(const QMap<QDate, int> &workTimeData)
|
||||
{
|
||||
m_workTimeData = workTimeData;
|
||||
m_maxValue = 0;
|
||||
|
||||
for (auto it = workTimeData.begin(); it != workTimeData.end(); ++it) {
|
||||
if (it.value() > m_maxValue) {
|
||||
m_maxValue = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void StatisticsChart::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
if (m_workTimeData.isEmpty()) {
|
||||
painter.drawText(rect(), Qt::AlignCenter, "No data available");
|
||||
return;
|
||||
}
|
||||
|
||||
const int margin = 40;
|
||||
const int chartWidth = width() - 2 * margin;
|
||||
const int chartHeight = height() - 2 * margin;
|
||||
|
||||
// Draw axes
|
||||
painter.setPen(QPen(Qt::black, 2));
|
||||
painter.drawLine(margin, height() - margin, width() - margin, height() - margin); // X-axis
|
||||
painter.drawLine(margin, margin, margin, height() - margin); // Y-axis
|
||||
|
||||
if (m_maxValue == 0) return;
|
||||
|
||||
// Draw bars
|
||||
const int barWidth = chartWidth / qMax(1, m_workTimeData.size());
|
||||
int x = margin;
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(70, 130, 180));
|
||||
|
||||
auto sortedData = m_workTimeData;
|
||||
|
||||
for (auto it = sortedData.begin(); it != sortedData.end(); ++it) {
|
||||
const int barHeight = (it.value() * chartHeight) / m_maxValue;
|
||||
const int barY = height() - margin - barHeight;
|
||||
|
||||
QRect barRect(x + 2, barY, barWidth - 4, barHeight);
|
||||
painter.drawRect(barRect);
|
||||
|
||||
// Draw date labels (every 3rd day to avoid crowding)
|
||||
if ((x - margin) % (barWidth * 3) == 0) {
|
||||
painter.setPen(Qt::black);
|
||||
QString dateStr = it.key().toString("dd/MM");
|
||||
painter.drawText(x, height() - 10, dateStr);
|
||||
painter.setPen(Qt::NoPen);
|
||||
}
|
||||
|
||||
x += barWidth;
|
||||
}
|
||||
|
||||
// Draw Y-axis labels
|
||||
painter.setPen(Qt::black);
|
||||
for (int i = 0; i <= 5; ++i) {
|
||||
int y = height() - margin - (i * chartHeight / 5);
|
||||
int minutes = (i * m_maxValue) / 5;
|
||||
painter.drawText(5, y + 5, QString::number(minutes) + "m");
|
||||
}
|
||||
}
|
||||
|
||||
// StatisticsDialog implementation
|
||||
StatisticsDialog::StatisticsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_totalSessions(0)
|
||||
, m_totalWorkTime(0)
|
||||
, m_totalBreakTime(0)
|
||||
, m_chartPeriod("week")
|
||||
{
|
||||
setWindowTitle("📊 Pomodoro Statistics");
|
||||
setMinimumSize(600, 500);
|
||||
setupUI();
|
||||
loadDailyStatistics();
|
||||
}
|
||||
|
||||
void StatisticsDialog::setupUI()
|
||||
{
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
// Create tab widget
|
||||
m_tabWidget = new QTabWidget();
|
||||
|
||||
setupOverviewTab();
|
||||
setupChartTab();
|
||||
setupDetailsTab();
|
||||
|
||||
m_tabWidget->addTab(m_overviewTab, "📈 Overview");
|
||||
m_tabWidget->addTab(m_chartTab, "📊 Chart");
|
||||
m_tabWidget->addTab(m_detailsTab, "📋 Details");
|
||||
|
||||
// No additional buttons - only native window close button
|
||||
mainLayout->addWidget(m_tabWidget);
|
||||
|
||||
// Connect chart period buttons
|
||||
connect(m_weekButton, &QPushButton::clicked, this, [this]() {
|
||||
m_chartPeriod = "week";
|
||||
updateChart();
|
||||
});
|
||||
connect(m_monthButton, &QPushButton::clicked, this, [this]() {
|
||||
m_chartPeriod = "month";
|
||||
updateChart();
|
||||
});
|
||||
connect(m_yearButton, &QPushButton::clicked, this, [this]() {
|
||||
m_chartPeriod = "year";
|
||||
updateChart();
|
||||
});
|
||||
}
|
||||
|
||||
void StatisticsDialog::setupOverviewTab()
|
||||
{
|
||||
m_overviewTab = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(m_overviewTab);
|
||||
|
||||
// Title
|
||||
QLabel *titleLabel = new QLabel("📊 Statistics Overview");
|
||||
QFont titleFont = titleLabel->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
titleLabel->setFont(titleFont);
|
||||
titleLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
// Main statistics grid
|
||||
QGridLayout *statsGrid = new QGridLayout();
|
||||
|
||||
// Create labels
|
||||
m_totalSessionsLabel = new QLabel();
|
||||
m_totalTimeLabel = new QLabel();
|
||||
m_workTimeLabel = new QLabel();
|
||||
m_breakTimeLabel = new QLabel();
|
||||
m_avgSessionLabel = new QLabel();
|
||||
m_efficiencyLabel = new QLabel();
|
||||
|
||||
// Period statistics
|
||||
m_todayLabel = new QLabel();
|
||||
m_weekLabel = new QLabel();
|
||||
m_monthLabel = new QLabel();
|
||||
|
||||
// Add to grid
|
||||
statsGrid->addWidget(new QLabel("🍅 Total Sessions:"), 0, 0);
|
||||
statsGrid->addWidget(m_totalSessionsLabel, 0, 1);
|
||||
|
||||
statsGrid->addWidget(new QLabel("⏱️ Total Time:"), 1, 0);
|
||||
statsGrid->addWidget(m_totalTimeLabel, 1, 1);
|
||||
|
||||
statsGrid->addWidget(new QLabel("🎯 Work Time:"), 2, 0);
|
||||
statsGrid->addWidget(m_workTimeLabel, 2, 1);
|
||||
|
||||
statsGrid->addWidget(new QLabel("☕ Break Time:"), 3, 0);
|
||||
statsGrid->addWidget(m_breakTimeLabel, 3, 1);
|
||||
|
||||
statsGrid->addWidget(new QLabel("📊 Avg Session:"), 4, 0);
|
||||
statsGrid->addWidget(m_avgSessionLabel, 4, 1);
|
||||
|
||||
statsGrid->addWidget(new QLabel("⚡ Efficiency:"), 5, 0);
|
||||
statsGrid->addWidget(m_efficiencyLabel, 5, 1);
|
||||
|
||||
// Period statistics section
|
||||
QLabel *periodTitle = new QLabel("📅 Period Statistics");
|
||||
QFont periodFont = periodTitle->font();
|
||||
periodFont.setPointSize(14);
|
||||
periodFont.setBold(true);
|
||||
periodTitle->setFont(periodFont);
|
||||
|
||||
QGridLayout *periodGrid = new QGridLayout();
|
||||
periodGrid->addWidget(new QLabel("📅 Today:"), 0, 0);
|
||||
periodGrid->addWidget(m_todayLabel, 0, 1);
|
||||
|
||||
periodGrid->addWidget(new QLabel("📅 This Week:"), 1, 0);
|
||||
periodGrid->addWidget(m_weekLabel, 1, 1);
|
||||
|
||||
periodGrid->addWidget(new QLabel("📅 This Month:"), 2, 0);
|
||||
periodGrid->addWidget(m_monthLabel, 2, 1);
|
||||
|
||||
layout->addWidget(titleLabel);
|
||||
layout->addLayout(statsGrid);
|
||||
layout->addSpacing(20);
|
||||
layout->addWidget(periodTitle);
|
||||
layout->addLayout(periodGrid);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
void StatisticsDialog::setupChartTab()
|
||||
{
|
||||
m_chartTab = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(m_chartTab);
|
||||
|
||||
// Chart period buttons
|
||||
QHBoxLayout *periodLayout = new QHBoxLayout();
|
||||
m_chartPeriodLabel = new QLabel("📊 Work Time Chart");
|
||||
QFont chartFont = m_chartPeriodLabel->font();
|
||||
chartFont.setPointSize(14);
|
||||
chartFont.setBold(true);
|
||||
m_chartPeriodLabel->setFont(chartFont);
|
||||
|
||||
m_weekButton = new QPushButton("Last 7 Days");
|
||||
m_monthButton = new QPushButton("Last 30 Days");
|
||||
m_yearButton = new QPushButton("Last Year");
|
||||
|
||||
m_weekButton->setCheckable(true);
|
||||
m_monthButton->setCheckable(true);
|
||||
m_yearButton->setCheckable(true);
|
||||
m_weekButton->setChecked(true);
|
||||
|
||||
periodLayout->addWidget(m_chartPeriodLabel);
|
||||
periodLayout->addStretch();
|
||||
periodLayout->addWidget(m_weekButton);
|
||||
periodLayout->addWidget(m_monthButton);
|
||||
periodLayout->addWidget(m_yearButton);
|
||||
|
||||
// Chart widget
|
||||
m_chart = new StatisticsChart();
|
||||
|
||||
layout->addLayout(periodLayout);
|
||||
layout->addWidget(m_chart);
|
||||
}
|
||||
|
||||
void StatisticsDialog::setupDetailsTab()
|
||||
{
|
||||
m_detailsTab = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(m_detailsTab);
|
||||
|
||||
QLabel *detailsTitle = new QLabel("📋 Detailed Statistics");
|
||||
QFont detailsFont = detailsTitle->font();
|
||||
detailsFont.setPointSize(14);
|
||||
detailsFont.setBold(true);
|
||||
detailsTitle->setFont(detailsFont);
|
||||
|
||||
m_detailsText = new QTextEdit();
|
||||
m_detailsText->setReadOnly(true);
|
||||
m_detailsText->setFont(QFont("Courier", 10));
|
||||
|
||||
layout->addWidget(detailsTitle);
|
||||
layout->addWidget(m_detailsText);
|
||||
}
|
||||
|
||||
void StatisticsDialog::setStatistics(int totalSessions, int totalWorkTime, int totalBreakTime)
|
||||
{
|
||||
m_totalSessions = totalSessions;
|
||||
m_totalWorkTime = totalWorkTime;
|
||||
m_totalBreakTime = totalBreakTime;
|
||||
|
||||
updateOverview();
|
||||
updateChart();
|
||||
|
||||
// Update details tab
|
||||
QString details = QString(
|
||||
"DETAILED POMODORO STATISTICS\n"
|
||||
"=============================\n\n"
|
||||
"Overall Performance:\n"
|
||||
"- Total Sessions Completed: %1\n"
|
||||
"- Total Time Spent: %2 hours %3 minutes\n"
|
||||
"- Work Time: %4 hours %5 minutes\n"
|
||||
"- Break Time: %6 hours %7 minutes\n\n"
|
||||
"Productivity Metrics:\n"
|
||||
"- Average Session Length: %8 minutes\n"
|
||||
"- Work/Break Ratio: %9\n"
|
||||
"- Sessions per Day (avg): %10\n\n"
|
||||
"Recent Activity:\n"
|
||||
"- Today: %11 minutes\n"
|
||||
"- Yesterday: %12 minutes\n"
|
||||
"- This Week: %13 minutes\n"
|
||||
"- Last Week: %14 minutes\n"
|
||||
).arg(m_totalSessions)
|
||||
.arg(m_totalWorkTime / 3600).arg((m_totalWorkTime % 3600) / 60)
|
||||
.arg(m_totalWorkTime / 3600).arg((m_totalWorkTime % 3600) / 60)
|
||||
.arg(m_totalBreakTime / 3600).arg((m_totalBreakTime % 3600) / 60)
|
||||
.arg(m_totalSessions > 0 ? m_totalWorkTime / (m_totalSessions * 60) : 0)
|
||||
.arg(m_totalBreakTime > 0 ? QString::number(double(m_totalWorkTime) / m_totalBreakTime, 'f', 2) : "N/A")
|
||||
.arg(m_totalSessions > 0 ? QString::number(double(m_totalSessions) / 7, 'f', 1) : "0")
|
||||
.arg(m_dailyWorkTime.value(QDate::currentDate(), 0))
|
||||
.arg(m_dailyWorkTime.value(QDate::currentDate().addDays(-1), 0))
|
||||
.arg(m_dailyWorkTime.value(QDate::currentDate(), 0)) // This week calculation can be improved
|
||||
.arg(0); // Last week calculation can be improved
|
||||
|
||||
m_detailsText->setPlainText(details);
|
||||
}
|
||||
|
||||
void StatisticsDialog::loadDailyStatistics()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
// Load daily work time data for the last 30 days
|
||||
QDate today = QDate::currentDate();
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
QDate date = today.addDays(-i);
|
||||
QString key = QString("dailyStats/workTime_%1").arg(date.toString("yyyy-MM-dd"));
|
||||
int workTime = settings.value(key, 0).toInt();
|
||||
if (workTime > 0) {
|
||||
m_dailyWorkTime[date] = workTime / 60; // Convert to minutes
|
||||
}
|
||||
|
||||
QString sessionKey = QString("dailyStats/sessions_%1").arg(date.toString("yyyy-MM-dd"));
|
||||
int sessions = settings.value(sessionKey, 0).toInt();
|
||||
if (sessions > 0) {
|
||||
m_dailySessions[date] = sessions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsDialog::updateOverview()
|
||||
{
|
||||
m_totalSessionsLabel->setText(QString::number(m_totalSessions));
|
||||
|
||||
int totalMinutes = (m_totalWorkTime + m_totalBreakTime) / 60;
|
||||
m_totalTimeLabel->setText(QString("%1h %2m")
|
||||
.arg(totalMinutes / 60)
|
||||
.arg(totalMinutes % 60));
|
||||
|
||||
int workMinutes = m_totalWorkTime / 60;
|
||||
m_workTimeLabel->setText(QString("%1h %2m")
|
||||
.arg(workMinutes / 60)
|
||||
.arg(workMinutes % 60));
|
||||
|
||||
int breakMinutes = m_totalBreakTime / 60;
|
||||
m_breakTimeLabel->setText(QString("%1h %2m")
|
||||
.arg(breakMinutes / 60)
|
||||
.arg(breakMinutes % 60));
|
||||
|
||||
int avgSession = m_totalSessions > 0 ? workMinutes / m_totalSessions : 0;
|
||||
m_avgSessionLabel->setText(QString("%1 minutes").arg(avgSession));
|
||||
|
||||
int efficiency = totalMinutes > 0 ? (workMinutes * 100 / totalMinutes) : 0;
|
||||
m_efficiencyLabel->setText(QString("%1%").arg(efficiency));
|
||||
|
||||
// Period statistics
|
||||
QDate today = QDate::currentDate();
|
||||
int todayMinutes = m_dailyWorkTime.value(today, 0);
|
||||
m_todayLabel->setText(QString("%1 minutes").arg(todayMinutes));
|
||||
|
||||
// Calculate week total
|
||||
int weekTotal = 0;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
weekTotal += m_dailyWorkTime.value(today.addDays(-i), 0);
|
||||
}
|
||||
m_weekLabel->setText(QString("%1 minutes").arg(weekTotal));
|
||||
|
||||
// Calculate month total
|
||||
int monthTotal = 0;
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
monthTotal += m_dailyWorkTime.value(today.addDays(-i), 0);
|
||||
}
|
||||
m_monthLabel->setText(QString("%1 minutes").arg(monthTotal));
|
||||
}
|
||||
|
||||
void StatisticsDialog::updateChart()
|
||||
{
|
||||
QMap<QDate, int> chartData;
|
||||
QDate today = QDate::currentDate();
|
||||
|
||||
int days = 7;
|
||||
if (m_chartPeriod == "month") days = 30;
|
||||
else if (m_chartPeriod == "year") days = 365;
|
||||
|
||||
for (int i = days - 1; i >= 0; --i) {
|
||||
QDate date = today.addDays(-i);
|
||||
chartData[date] = m_dailyWorkTime.value(date, 0);
|
||||
}
|
||||
|
||||
m_chart->setData(chartData);
|
||||
|
||||
// Update button states
|
||||
m_weekButton->setChecked(m_chartPeriod == "week");
|
||||
m_monthButton->setChecked(m_chartPeriod == "month");
|
||||
m_yearButton->setChecked(m_chartPeriod == "year");
|
||||
}
|
||||
85
StatisticsDialog.h
Normal file
85
StatisticsDialog.h
Normal file
@ -0,0 +1,85 @@
|
||||
#ifndef STATISTICSDIALOG_H
|
||||
#define STATISTICSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QMap>
|
||||
#include <QDate>
|
||||
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class QPushButton;
|
||||
class QTabWidget;
|
||||
class QTextEdit;
|
||||
class QScrollArea;
|
||||
|
||||
class StatisticsChart : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StatisticsChart(QWidget *parent = nullptr);
|
||||
void setData(const QMap<QDate, int> &workTimeData);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QMap<QDate, int> m_workTimeData;
|
||||
int m_maxValue;
|
||||
};
|
||||
|
||||
class StatisticsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StatisticsDialog(QWidget *parent = nullptr);
|
||||
void setStatistics(int totalSessions, int totalWorkTime, int totalBreakTime);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void setupOverviewTab();
|
||||
void setupChartTab();
|
||||
void setupDetailsTab();
|
||||
void loadDailyStatistics();
|
||||
void updateOverview();
|
||||
void updateChart();
|
||||
|
||||
// UI elements
|
||||
QTabWidget *m_tabWidget;
|
||||
|
||||
// Overview tab
|
||||
QWidget *m_overviewTab;
|
||||
QLabel *m_totalSessionsLabel;
|
||||
QLabel *m_totalTimeLabel;
|
||||
QLabel *m_workTimeLabel;
|
||||
QLabel *m_breakTimeLabel;
|
||||
QLabel *m_avgSessionLabel;
|
||||
QLabel *m_efficiencyLabel;
|
||||
QLabel *m_todayLabel;
|
||||
QLabel *m_weekLabel;
|
||||
QLabel *m_monthLabel;
|
||||
|
||||
// Chart tab
|
||||
QWidget *m_chartTab;
|
||||
StatisticsChart *m_chart;
|
||||
QLabel *m_chartPeriodLabel;
|
||||
QPushButton *m_weekButton;
|
||||
QPushButton *m_monthButton;
|
||||
QPushButton *m_yearButton;
|
||||
|
||||
// Details tab
|
||||
QWidget *m_detailsTab;
|
||||
QTextEdit *m_detailsText;
|
||||
|
||||
// Data
|
||||
int m_totalSessions;
|
||||
int m_totalWorkTime;
|
||||
int m_totalBreakTime;
|
||||
QMap<QDate, int> m_dailyWorkTime;
|
||||
QMap<QDate, int> m_dailySessions;
|
||||
QString m_chartPeriod;
|
||||
};
|
||||
|
||||
#endif // STATISTICSDIALOG_H
|
||||
107
SystemTrayManager.cpp
Normal file
107
SystemTrayManager.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "SystemTrayManager.h"
|
||||
#include <QApplication>
|
||||
#include <QAction>
|
||||
|
||||
SystemTrayManager::SystemTrayManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_trayIcon(nullptr)
|
||||
, m_trayMenu(nullptr)
|
||||
{
|
||||
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||
qWarning("System tray is not available on this system.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
m_trayIcon->setIcon(qApp->windowIcon());
|
||||
|
||||
setupTrayMenu();
|
||||
|
||||
connect(m_trayIcon, QOverload<QSystemTrayIcon::ActivationReason>::of(&QSystemTrayIcon::activated),
|
||||
this, &SystemTrayManager::onTrayIconActivated);
|
||||
}
|
||||
|
||||
void SystemTrayManager::setupTrayMenu()
|
||||
{
|
||||
m_trayMenu = new QMenu();
|
||||
|
||||
// Use QStringLiteral for string literals to avoid runtime allocations
|
||||
m_trayMenu->addAction(QStringLiteral("Show Timer"), this, &SystemTrayManager::showMainWindow);
|
||||
m_trayMenu->addSeparator();
|
||||
m_trayMenu->addAction(QStringLiteral("Start/Pause"), this, &SystemTrayManager::startPauseRequested);
|
||||
m_trayMenu->addAction(QStringLiteral("Reset"), this, &SystemTrayManager::resetRequested);
|
||||
m_trayMenu->addAction(QStringLiteral("Skip Session"), this, &SystemTrayManager::skipRequested);
|
||||
m_trayMenu->addSeparator();
|
||||
m_trayMenu->addAction(QStringLiteral("Settings"), this, &SystemTrayManager::settingsRequested);
|
||||
m_trayMenu->addAction(QStringLiteral("Statistics"), this, &SystemTrayManager::statisticsRequested);
|
||||
m_trayMenu->addSeparator();
|
||||
m_trayMenu->addAction(QStringLiteral("Quit"), this, &SystemTrayManager::quitRequested);
|
||||
|
||||
m_trayIcon->setContextMenu(m_trayMenu);
|
||||
}
|
||||
|
||||
void SystemTrayManager::show()
|
||||
{
|
||||
if (m_trayIcon) {
|
||||
m_trayIcon->show();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayManager::hide()
|
||||
{
|
||||
if (m_trayIcon) {
|
||||
m_trayIcon->hide();
|
||||
}
|
||||
}
|
||||
|
||||
bool SystemTrayManager::isVisible() const
|
||||
{
|
||||
return m_trayIcon && m_trayIcon->isVisible();
|
||||
}
|
||||
|
||||
void SystemTrayManager::updateTooltip(TimerState state, bool isRunning, const QString &timeRemaining, int currentSession, int totalSessions)
|
||||
{
|
||||
if (!m_trayIcon) return;
|
||||
|
||||
// Build tooltip more efficiently
|
||||
QString tooltip;
|
||||
tooltip.reserve(100); // Reserve space to avoid reallocations
|
||||
|
||||
QString emoji = TimerStateHelper::getStateEmoji(state);
|
||||
QString stateText = TimerStateHelper::getStateText(state);
|
||||
|
||||
if (isRunning) {
|
||||
tooltip = QStringLiteral("%1 %2 - %3 remaining").arg(
|
||||
emoji,
|
||||
stateText,
|
||||
timeRemaining);
|
||||
} else {
|
||||
stateText += " (Paused)";
|
||||
tooltip = QStringLiteral("%1 %2 Ready (%3)").arg(
|
||||
emoji,
|
||||
stateText,
|
||||
timeRemaining);
|
||||
}
|
||||
|
||||
tooltip += QStringLiteral("\nSession %1 of %2").arg(currentSession).arg(totalSessions);
|
||||
|
||||
// Only update if tooltip changed
|
||||
if (tooltip != m_lastTooltip) {
|
||||
m_trayIcon->setToolTip(tooltip);
|
||||
m_lastTooltip = std::move(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayManager::showMessage(const QString &title, const QString &message)
|
||||
{
|
||||
if (m_trayIcon && m_trayIcon->isVisible()) {
|
||||
m_trayIcon->showMessage(title, message, QSystemTrayIcon::Information, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
if (reason == QSystemTrayIcon::Trigger) {
|
||||
emit showMainWindow();
|
||||
}
|
||||
}
|
||||
45
SystemTrayManager.h
Normal file
45
SystemTrayManager.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef SYSTEMTRAYMANAGER_H
|
||||
#define SYSTEMTRAYMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMenu>
|
||||
#include "TimerState.h"
|
||||
|
||||
class SystemTrayManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SystemTrayManager(QObject *parent = nullptr);
|
||||
~SystemTrayManager() = default;
|
||||
|
||||
void show();
|
||||
void hide();
|
||||
bool isVisible() const;
|
||||
void updateTooltip(TimerState state, bool isRunning, const QString &timeRemaining, int currentSession, int totalSessions);
|
||||
void showMessage(const QString &title, const QString &message);
|
||||
|
||||
signals:
|
||||
void showMainWindow();
|
||||
void startPauseRequested();
|
||||
void resetRequested();
|
||||
void skipRequested();
|
||||
void settingsRequested();
|
||||
void statisticsRequested();
|
||||
void quitRequested();
|
||||
|
||||
private slots:
|
||||
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
|
||||
private:
|
||||
void setupTrayMenu();
|
||||
|
||||
QSystemTrayIcon *m_trayIcon;
|
||||
QMenu *m_trayMenu;
|
||||
|
||||
// Cache to avoid unnecessary tooltip updates
|
||||
QString m_lastTooltip;
|
||||
};
|
||||
|
||||
#endif // SYSTEMTRAYMANAGER_H
|
||||
1
TimerState.cpp
Normal file
1
TimerState.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "TimerState.h"
|
||||
65
TimerState.h
Normal file
65
TimerState.h
Normal file
@ -0,0 +1,65 @@
|
||||
#ifndef TIMERSTATE_H
|
||||
#define TIMERSTATE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
enum class TimerState : quint8 { // Use smaller enum type
|
||||
Work,
|
||||
ShortBreak,
|
||||
LongBreak
|
||||
};
|
||||
|
||||
class TimerStateHelper
|
||||
{
|
||||
public:
|
||||
// Use constexpr and string literals for better performance
|
||||
static QString getStateText(TimerState state) noexcept {
|
||||
switch (state) {
|
||||
case TimerState::Work:
|
||||
return "Work";
|
||||
case TimerState::ShortBreak:
|
||||
return "Short Break";
|
||||
case TimerState::LongBreak:
|
||||
return "Long Break";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static QString getStateEmoji(TimerState state) noexcept {
|
||||
switch (state) {
|
||||
case TimerState::Work:
|
||||
return "👨💻";
|
||||
case TimerState::ShortBreak:
|
||||
return "☕️";
|
||||
case TimerState::LongBreak:
|
||||
return "😴";
|
||||
}
|
||||
return "❓";
|
||||
}
|
||||
|
||||
static QString getStatusMessage(TimerState state, bool isRunning) noexcept {
|
||||
QString text = getStateText(state);
|
||||
if (!isRunning) {
|
||||
text += " (Paused)";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
static QString getStateTextString(TimerState state) {
|
||||
return getStateText(state);
|
||||
}
|
||||
|
||||
static int getDurationForState(TimerState state, int workDuration, int shortBreak, int longBreak) noexcept {
|
||||
switch (state) {
|
||||
case TimerState::Work:
|
||||
return workDuration;
|
||||
case TimerState::ShortBreak:
|
||||
return shortBreak;
|
||||
case TimerState::LongBreak:
|
||||
return longBreak;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TIMERSTATE_H
|
||||
131
main.cpp
Normal file
131
main.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QRadialGradient>
|
||||
#include <QPen>
|
||||
#include <QBrush>
|
||||
#include "PomodoroTimer.h"
|
||||
|
||||
namespace {
|
||||
// Application constants - use QStringLiteral for compile-time optimization
|
||||
const QString APP_NAME = QStringLiteral("Pomodoro Timer");
|
||||
const QString APP_VERSION = QStringLiteral("0.1.0");
|
||||
const QString ORGANIZATION = QStringLiteral("PomodoroApp");
|
||||
|
||||
// Icon generation constants
|
||||
constexpr double TOMATO_SIZE_RATIO = 0.8;
|
||||
constexpr double STEM_SIZE_RATIO = 0.15;
|
||||
constexpr double TOMATO_FLATTEN_RATIO = 0.9;
|
||||
constexpr int PEN_WIDTH = 1;
|
||||
constexpr int HIGHLIGHT_ALPHA = 100;
|
||||
|
||||
// Cache for generated icons to avoid regeneration
|
||||
QIcon g_cachedAppIcon;
|
||||
|
||||
QIcon createTomatoIcon(int size = 32)
|
||||
{
|
||||
QPixmap pixmap(size, size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Calculate proportions
|
||||
const int tomatoSize = static_cast<int>(size * TOMATO_SIZE_RATIO);
|
||||
const int stemSize = static_cast<int>(size * STEM_SIZE_RATIO);
|
||||
const int margin = (size - tomatoSize) / 2;
|
||||
|
||||
// Draw tomato body with gradient
|
||||
QRadialGradient tomatoGradient(size/2, size/2 + stemSize, tomatoSize/2);
|
||||
tomatoGradient.setColorAt(0.0, QColor(255, 100, 100));
|
||||
tomatoGradient.setColorAt(0.7, QColor(220, 50, 50));
|
||||
tomatoGradient.setColorAt(1.0, QColor(180, 30, 30));
|
||||
|
||||
painter.setBrush(QBrush(tomatoGradient));
|
||||
painter.setPen(QPen(QColor(150, 20, 20), PEN_WIDTH));
|
||||
|
||||
const QRect tomatoRect(margin, margin + stemSize, tomatoSize,
|
||||
static_cast<int>(tomatoSize * TOMATO_FLATTEN_RATIO));
|
||||
painter.drawEllipse(tomatoRect);
|
||||
|
||||
// Draw stem and leaves
|
||||
painter.setBrush(QBrush(QColor(34, 139, 34)));
|
||||
painter.setPen(QPen(QColor(20, 100, 20), PEN_WIDTH));
|
||||
|
||||
// Main stem
|
||||
const QRect stemRect(size/2 - stemSize/3, margin, stemSize/1.5, stemSize);
|
||||
painter.drawRect(stemRect);
|
||||
|
||||
// Draw leaves using polygons - optimize polygon creation
|
||||
QPolygon leftLeaf, rightLeaf;
|
||||
leftLeaf.reserve(3);
|
||||
rightLeaf.reserve(3);
|
||||
|
||||
leftLeaf << QPoint(size/2 - stemSize/2, margin + stemSize/3)
|
||||
<< QPoint(size/2 - stemSize, margin)
|
||||
<< QPoint(size/2 - stemSize/3, margin + stemSize/2);
|
||||
|
||||
rightLeaf << QPoint(size/2 + stemSize/2, margin + stemSize/3)
|
||||
<< QPoint(size/2 + stemSize, margin)
|
||||
<< QPoint(size/2 + stemSize/3, margin + stemSize/2);
|
||||
|
||||
painter.drawPolygon(leftLeaf);
|
||||
painter.drawPolygon(rightLeaf);
|
||||
|
||||
// Add 3D highlight effect
|
||||
painter.setBrush(QBrush(QColor(255, 150, 150, HIGHLIGHT_ALPHA)));
|
||||
painter.setPen(Qt::NoPen);
|
||||
const QRect highlightRect(margin + tomatoSize/4, margin + stemSize + tomatoSize/4,
|
||||
tomatoSize/3, tomatoSize/4);
|
||||
painter.drawEllipse(highlightRect);
|
||||
|
||||
return QIcon(pixmap);
|
||||
}
|
||||
|
||||
const QIcon& createApplicationIcon()
|
||||
{
|
||||
if (g_cachedAppIcon.isNull()) {
|
||||
constexpr int iconSizes[] = {16, 24, 32, 48, 64};
|
||||
|
||||
for (int size : iconSizes) {
|
||||
g_cachedAppIcon.addPixmap(createTomatoIcon(size).pixmap(size, size));
|
||||
}
|
||||
}
|
||||
return g_cachedAppIcon;
|
||||
}
|
||||
|
||||
void centerWindow(QWidget* window)
|
||||
{
|
||||
const QScreen* screen = QApplication::primaryScreen();
|
||||
if (!screen) return;
|
||||
|
||||
const QRect screenGeometry = screen->availableGeometry();
|
||||
const int x = (screenGeometry.width() - window->width()) / 2;
|
||||
const int y = (screenGeometry.height() - window->height()) / 2;
|
||||
window->move(x, y);
|
||||
}
|
||||
|
||||
void setupApplicationProperties(QApplication& app)
|
||||
{
|
||||
app.setApplicationName(APP_NAME);
|
||||
app.setApplicationVersion(APP_VERSION);
|
||||
app.setOrganizationName(ORGANIZATION);
|
||||
app.setWindowIcon(createApplicationIcon());
|
||||
app.setQuitOnLastWindowClosed(false);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
setupApplicationProperties(app);
|
||||
|
||||
PomodoroTimer timer;
|
||||
timer.show();
|
||||
centerWindow(&timer);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
9
pomodoro-timer.desktop.in
Normal file
9
pomodoro-timer.desktop.in
Normal file
@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=Pomodoro Timer
|
||||
GenericName=Time Management
|
||||
Comment=A simple Pomodoro timer to help you focus.
|
||||
Exec=@CMAKE_INSTALL_PREFIX@/bin/PomodoroTimer
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Office;
|
||||
Loading…
Reference in New Issue
Block a user