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