generate ui core flows

This commit is contained in:
talksik
2026-02-01 13:21:21 -08:00
parent 0a3b315652
commit 9e5f0a5d2b
20 changed files with 1433 additions and 14 deletions
+15 -1
View File
@@ -20,7 +20,21 @@ qt_add_executable(adhd
service.cpp
service.h
mainwindow.ui
sessionslistmodel.h sessionslistmodel.cpp
sessionslistmodel.h
sessionslistmodel.cpp
sessionitemdelegate.h
sessionitemdelegate.cpp
createsessiondialog.h
createsessiondialog.cpp
createsessiondialog.ui
focusoverlay.h
focusoverlay.cpp
focusoverlay.ui
endsessiondialog.h
endsessiondialog.cpp
endsessiondialog.ui
namegenerator.h
namegenerator.cpp
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
+40
View File
@@ -0,0 +1,40 @@
#include "createsessiondialog.h"
#include "ui_createsessiondialog.h"
#include <QPushButton>
CreateSessionDialog::CreateSessionDialog(QWidget *parent)
: QDialog(parent),
ui(new Ui::CreateSessionDialog)
{
ui->setupUi(this);
setWindowTitle("Create New Session");
// Disable OK button initially
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
// Connect oneliner field to validation
connect(ui->onelinerEdit, &QLineEdit::textChanged,
this, &CreateSessionDialog::onOnelinerChanged);
}
CreateSessionDialog::~CreateSessionDialog()
{
delete ui;
}
QString CreateSessionDialog::getOneliner() const
{
return ui->onelinerEdit->text().trimmed();
}
QString CreateSessionDialog::getContent() const
{
return ui->contentEdit->toPlainText().trimmed();
}
void CreateSessionDialog::onOnelinerChanged(const QString &text)
{
bool isValid = !text.trimmed().isEmpty();
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid);
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef CREATESESSIONDIALOG_H
#define CREATESESSIONDIALOG_H
#include <QDialog>
namespace Ui {
class CreateSessionDialog;
}
class CreateSessionDialog : public QDialog
{
Q_OBJECT
public:
explicit CreateSessionDialog(QWidget *parent = nullptr);
~CreateSessionDialog();
QString getOneliner() const;
QString getContent() const;
private slots:
void onOnelinerChanged(const QString &text);
private:
Ui::CreateSessionDialog *ui;
};
#endif // CREATESESSIONDIALOG_H
+96
View File
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CreateSessionDialog</class>
<widget class="QDialog" name="CreateSessionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Session</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="onelinerLabel">
<property name="text">
<string>Oneliner:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="onelinerEdit">
<property name="placeholderText">
<string>What will you focus on?</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="contentLabel">
<property name="text">
<string>Initial Content:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextEdit" name="contentEdit">
<property name="placeholderText">
<string>Optional: Add initial notes or context...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CreateSessionDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CreateSessionDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+68
View File
@@ -0,0 +1,68 @@
#include "endsessiondialog.h"
#include "ui_endsessiondialog.h"
EndSessionDialog::EndSessionDialog(const QString &oneliner, int elapsedMs, QWidget *parent)
: QDialog(parent),
ui(new Ui::EndSessionDialog),
m_selectedResult(Session::UNSPECIFIED)
{
ui->setupUi(this);
setWindowTitle("End Session");
// Set oneliner and elapsed time
ui->onelinerLabel->setText(oneliner);
int seconds = elapsedMs / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
QString timeStr = QString("%1:%2:%3")
.arg(hours, 2, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
ui->elapsedLabel->setText("Duration: " + timeStr);
// Connect buttons
connect(ui->winButton, &QPushButton::clicked,
this, &EndSessionDialog::onWinClicked);
connect(ui->lossButton, &QPushButton::clicked,
this, &EndSessionDialog::onLossClicked);
connect(ui->neutralButton, &QPushButton::clicked,
this, &EndSessionDialog::onNeutralClicked);
}
EndSessionDialog::~EndSessionDialog()
{
delete ui;
}
Session::Result EndSessionDialog::getResult() const
{
return m_selectedResult;
}
QString EndSessionDialog::getAdditionalNotes() const
{
return ui->notesEdit->toPlainText().trimmed();
}
void EndSessionDialog::onWinClicked()
{
m_selectedResult = Session::WIN;
accept();
}
void EndSessionDialog::onLossClicked()
{
m_selectedResult = Session::LOSS;
accept();
}
void EndSessionDialog::onNeutralClicked()
{
m_selectedResult = Session::NEUTRAL;
accept();
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef ENDSESSIONDIALOG_H
#define ENDSESSIONDIALOG_H
#include <QDialog>
#include "service.h"
namespace Ui {
class EndSessionDialog;
}
class EndSessionDialog : public QDialog
{
Q_OBJECT
public:
explicit EndSessionDialog(const QString &oneliner, int elapsedMs, QWidget *parent = nullptr);
~EndSessionDialog();
Session::Result getResult() const;
QString getAdditionalNotes() const;
private slots:
void onWinClicked();
void onLossClicked();
void onNeutralClicked();
private:
Ui::EndSessionDialog *ui;
Session::Result m_selectedResult;
};
#endif // ENDSESSIONDIALOG_H
+221
View File
@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EndSessionDialog</class>
<widget class="QDialog" name="EndSessionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>End Session</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>How did your session go?</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="onelinerLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Session Oneliner</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="elapsedLabel">
<property name="text">
<string>Duration: 00:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="winButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
background-color: #2ecc71;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #27ae60;
}
QPushButton:pressed {
background-color: #229954;
}</string>
</property>
<property name="text">
<string>WIN</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lossButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
background-color: #e74c3c;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #c0392b;
}
QPushButton:pressed {
background-color: #a93226;
}</string>
</property>
<property name="text">
<string>LOSS</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="neutralButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
background-color: #95a5a6;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #7f8c8d;
}
QPushButton:pressed {
background-color: #707b7c;
}</string>
</property>
<property name="text">
<string>NEUTRAL</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Optional: Add final notes</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="notesEdit">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="placeholderText">
<string>Any final thoughts or notes about this session...</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+71
View File
@@ -0,0 +1,71 @@
#include "focusoverlay.h"
#include "ui_focusoverlay.h"
#include <QScreen>
#include <QGuiApplication>
FocusOverlay::FocusOverlay(QWidget *parent)
: QWidget(parent),
ui(new Ui::FocusOverlay)
{
ui->setupUi(this);
// Set window flags for always-on-top frameless window
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
setAttribute(Qt::WA_TranslucentBackground);
// Connect buttons
connect(ui->resetButton, &QPushButton::clicked,
this, &FocusOverlay::onResetButtonClicked);
connect(ui->endButton, &QPushButton::clicked,
this, &FocusOverlay::onEndButtonClicked);
// Position window
positionAtBottomCenter();
}
FocusOverlay::~FocusOverlay()
{
delete ui;
}
void FocusOverlay::setOneliner(const QString &oneliner)
{
ui->onelinerLabel->setText(oneliner);
}
void FocusOverlay::setElapsedTime(int elapsedMs)
{
int seconds = elapsedMs / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
QString timeStr = QString("%1:%2:%3")
.arg(hours, 2, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
ui->elapsedLabel->setText("Elapsed: " + timeStr);
}
void FocusOverlay::onResetButtonClicked()
{
emit resetClicked();
}
void FocusOverlay::onEndButtonClicked()
{
emit endSessionClicked();
}
void FocusOverlay::positionAtBottomCenter()
{
QScreen *screen = QGuiApplication::primaryScreen();
if (!screen) return;
QRect screenGeometry = screen->geometry();
int x = (screenGeometry.width() - width()) / 2;
int y = screenGeometry.height() - height() - 300; // 300px from bottom
move(x, y);
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef FOCUSOVERLAY_H
#define FOCUSOVERLAY_H
#include <QWidget>
namespace Ui {
class FocusOverlay;
}
class FocusOverlay : public QWidget
{
Q_OBJECT
public:
explicit FocusOverlay(QWidget *parent = nullptr);
~FocusOverlay();
void setOneliner(const QString &oneliner);
void setElapsedTime(int elapsedMs);
signals:
void resetClicked();
void endSessionClicked();
private slots:
void onResetButtonClicked();
void onEndButtonClicked();
private:
Ui::FocusOverlay *ui;
void positionAtBottomCenter();
};
#endif // FOCUSOVERLAY_H
+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FocusOverlay</class>
<widget class="QWidget" name="FocusOverlay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="windowTitle">
<string>Focus Session</string>
</property>
<property name="styleSheet">
<string notr="true">QWidget#FocusOverlay {
background-color: rgba(50, 50, 50, 200);
border-radius: 10px;
border: 2px solid rgba(100, 100, 100, 255);
}
QLabel {
color: white;
}
QPushButton {
background-color: rgba(80, 80, 80, 255);
color: white;
border: 1px solid rgba(120, 120, 120, 255);
border-radius: 5px;
padding: 5px 10px;
min-width: 60px;
}
QPushButton:hover {
background-color: rgba(100, 100, 100, 255);
}
QPushButton:pressed {
background-color: rgba(60, 60, 60, 255);
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="topMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="onelinerLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Session Oneliner</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="elapsedLabel">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Elapsed: 00:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="endButton">
<property name="text">
<string>End Session</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+218 -2
View File
@@ -1,19 +1,51 @@
#include "mainwindow.h"
#include "service.h"
#include "sessionslistmodel.h"
#include "sessionitemdelegate.h"
#include "createsessiondialog.h"
#include "focusoverlay.h"
#include "endsessiondialog.h"
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <QTimer>
#include <QMessageBox>
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), m_service(new Service(this))
: QMainWindow(parent),
m_service(new Service(this)),
m_focusOverlay(nullptr),
m_updatingActiveFields(false)
{
ui = new Ui::MainWindow;
ui->setupUi(this);
m_listModel = new SessionsListModel(m_service, this);
ui->listView->setModel(m_listModel);
ui->listView->setItemDelegate(new SessionItemDelegate(this));
// Connect Service signals
connect(m_service, &Service::activeSessionChanged,
this, &MainWindow::onActiveSessionChanged);
connect(m_service, &Service::sessionTimerTicked,
this, &MainWindow::onSessionTimerTicked);
// Connect Active tab field changes
connect(ui->oneLineEdit, &QLineEdit::editingFinished,
this, &MainWindow::onActiveOnelinerChanged);
// Debounce content text changes
QTimer *contentDebounce = new QTimer(this);
contentDebounce->setSingleShot(true);
contentDebounce->setInterval(500);
connect(ui->contentTextEdit, &QTextEdit::textChanged,
[contentDebounce]() { contentDebounce->start(); });
connect(contentDebounce, &QTimer::timeout,
this, &MainWindow::onActiveContentChanged);
// Set initial state: no active session
ui->activeStackedWidget->setCurrentIndex(0);
}
MainWindow::~MainWindow() {}
@@ -27,6 +59,190 @@ void MainWindow::on_selectVaultButton_clicked()
if (!dir.isEmpty()) {
m_service->setVaultDirectory(dir);
ui->tabWidget->setCurrentIndex(1);
ui->vaultLabel->setText(m_service->vaultDirectory());
ui->vaultLabel->setText(dir);
}
}
void MainWindow::on_newSessionButton_clicked()
{
CreateSessionDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
QString oneliner = dialog.getOneliner();
QString content = dialog.getContent();
Session session = m_service->createSession(oneliner, content);
m_service->startSession(session);
}
}
void MainWindow::on_startSessionButton_clicked()
{
QModelIndex index = ui->listView->currentIndex();
if (!index.isValid()) {
QMessageBox::warning(this, "No Selection", "Please select a session to start.");
return;
}
Session session = m_listModel->getSessionByIndex(index);
m_service->startSession(session);
}
void MainWindow::on_endSessionButton_clicked()
{
auto activeSession = m_service->activeSession();
if (!activeSession) {
return;
}
EndSessionDialog dialog(activeSession->oneliner, m_service->getElapsedMs(), this);
if (dialog.exec() == QDialog::Accepted) {
Session::Result result = dialog.getResult();
QString notes = dialog.getAdditionalNotes();
// Append notes to content if provided
if (!notes.isEmpty()) {
Session updated = *activeSession;
if (!updated.content.isEmpty()) {
updated.content += "\n\n";
}
updated.content += notes;
m_service->updateSession(updated);
}
m_service->endSession(result);
}
}
void MainWindow::onActiveSessionChanged()
{
auto activeSession = m_service->activeSession();
if (activeSession) {
// Session started
ui->activeStackedWidget->setCurrentIndex(1);
updateActiveTab();
// Create and show overlay
if (!m_focusOverlay) {
m_focusOverlay = new FocusOverlay();
connect(m_focusOverlay, &FocusOverlay::resetClicked,
this, &MainWindow::onOverlayResetClicked);
connect(m_focusOverlay, &FocusOverlay::endSessionClicked,
this, &MainWindow::onOverlayEndSessionClicked);
}
m_focusOverlay->setOneliner(activeSession->oneliner);
m_focusOverlay->setElapsedTime(0);
m_focusOverlay->show();
// Switch to Active tab
ui->tabWidget->setCurrentIndex(2);
} else {
// Session ended
ui->activeStackedWidget->setCurrentIndex(0);
// Hide and delete overlay
if (m_focusOverlay) {
m_focusOverlay->hide();
m_focusOverlay->deleteLater();
m_focusOverlay = nullptr;
}
}
}
void MainWindow::onSessionTimerTicked(int elapsedMs)
{
ui->elapsedLabel->setText(formatDuration(elapsedMs));
if (m_focusOverlay) {
m_focusOverlay->setElapsedTime(elapsedMs);
}
}
void MainWindow::onActiveOnelinerChanged()
{
if (m_updatingActiveFields) return;
auto activeSession = m_service->activeSession();
if (!activeSession) return;
Session updated = *activeSession;
updated.oneliner = ui->oneLineEdit->text();
updated.lastModifiedAt = QDateTime::currentDateTime();
m_updatingActiveFields = true;
m_service->updateSession(updated);
m_service->updateActiveSession(updated);
// Update overlay
if (m_focusOverlay) {
m_focusOverlay->setOneliner(updated.oneliner);
}
m_updatingActiveFields = false;
}
void MainWindow::onActiveContentChanged()
{
if (m_updatingActiveFields) return;
auto activeSession = m_service->activeSession();
if (!activeSession) return;
Session updated = *activeSession;
updated.content = ui->contentTextEdit->toPlainText();
updated.lastModifiedAt = QDateTime::currentDateTime();
m_updatingActiveFields = true;
m_service->updateSession(updated);
m_service->updateActiveSession(updated);
m_updatingActiveFields = false;
}
void MainWindow::onOverlayResetClicked()
{
QMessageBox::StandardButton reply = QMessageBox::question(
this,
"Reset Timer",
"Are you sure you want to reset the timer to 00:00:00?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
m_service->resetSessionTimer();
}
}
void MainWindow::onOverlayEndSessionClicked()
{
on_endSessionButton_clicked();
}
void MainWindow::updateActiveTab()
{
auto activeSession = m_service->activeSession();
if (!activeSession) return;
m_updatingActiveFields = true;
ui->oneLineEdit->setText(activeSession->oneliner);
ui->elapsedLabel->setText(formatDuration(m_service->getElapsedMs()));
ui->createdLabel->setText(activeSession->createdAt.toString("yyyy-MM-dd hh:mm:ss"));
ui->modifiedLabel->setText(activeSession->lastModifiedAt.toString("yyyy-MM-dd hh:mm:ss"));
ui->contentTextEdit->setPlainText(activeSession->content);
m_updatingActiveFields = false;
}
QString MainWindow::formatDuration(int ms) const
{
int seconds = ms / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
return QString("%1:%2:%3")
.arg(hours, 2, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
}
+23
View File
@@ -6,6 +6,8 @@
class Service;
class SessionsListModel;
class FocusOverlay;
class QTimer;
QT_BEGIN_NAMESPACE
QT_END_NAMESPACE
@@ -23,11 +25,32 @@ public:
private slots:
void on_selectVaultButton_clicked();
void on_newSessionButton_clicked();
void on_startSessionButton_clicked();
void on_endSessionButton_clicked();
// Service signal handlers
void onActiveSessionChanged();
void onSessionTimerTicked(int elapsedMs);
// Active tab field editing
void onActiveOnelinerChanged();
void onActiveContentChanged();
// Overlay handlers
void onOverlayResetClicked();
void onOverlayEndSessionClicked();
private:
Ui::MainWindow *ui;
Service *m_service;
SessionsListModel *m_listModel;
FocusOverlay *m_focusOverlay;
bool m_updatingActiveFields;
void updateActiveTab();
QString formatDuration(int ms) const;
};
#endif // MAINWINDOW_H
+193
View File
@@ -65,6 +65,37 @@
<string>Sessions</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="newSessionButton">
<property name="text">
<string>New Session</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="startSessionButton">
<property name="text">
<string>Start Selected</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="listView"/>
</item>
@@ -74,6 +105,168 @@
<attribute name="title">
<string>Active</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QStackedWidget" name="activeStackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="noSessionPage">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="noSessionLabel">
<property name="text">
<string>No session running. Start a session to begin.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="activeSessionPage">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>457</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Oneliner:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="oneLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Elapsed Time:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="elapsedLabel">
<property name="text">
<string>00:00:00</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Created:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="createdLabel">
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Modified:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="modifiedLabel">
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Content:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QTextEdit" name="contentTextEdit">
<property name="placeholderText">
<string>Add notes, context, or anything related to this session...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="endSessionButton">
<property name="text">
<string>End Session</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
+2 -6
View File
@@ -1,9 +1,5 @@
#include "namegenerator.cpp"
namespace {
constexpr char data[]
}
#include "namegenerator.h"
QString generate() {
return;
return QString("generated-name-placeholder");
}
+81 -2
View File
@@ -8,7 +8,12 @@ bool Session::isComplete() const {
return result != UNSPECIFIED;
}
Service::Service(QObject *parent) : QObject(parent) {
Service::Service(QObject *parent)
: QObject(parent),
m_tickTimer(new QTimer(this))
{
m_tickTimer->setInterval(1000); // Fire every second
connect(m_tickTimer, &QTimer::timeout, this, &Service::onTickTimerFired);
}
Service::~Service() {}
@@ -161,7 +166,7 @@ std::optional<Session> Service::parseFile(const QFileInfo &fileInfo) {
}
Session session = {};
session.id = file.fileName();
session.id = fileInfo.baseName(); // Get filename without extension
session.content = "";
session.oneliner = "";
session.durationMs = 0;
@@ -268,3 +273,77 @@ void Service::overwriteSessionFile(const Session &session)
out << session.content << "\n";
file.close();
}
// Session lifecycle methods
void Service::startSession(const Session &session) {
if (m_activeSession.has_value()) {
qWarning() << "A session is already active. End it before starting a new one.";
return;
}
m_activeSession = session;
m_sessionTimer.start();
m_tickTimer->start();
emit activeSessionChanged();
emit sessionTimerTicked(0);
}
void Service::updateActiveSession(const Session &session) {
if (!m_activeSession.has_value()) {
qWarning() << "No active session to update.";
return;
}
m_activeSession = session;
// Don't emit activeSessionChanged - just update the session data
// Timer keeps running
}
int Service::getElapsedMs() const {
if (!m_activeSession) {
return 0;
}
return m_sessionTimer.elapsed();
}
void Service::resetSessionTimer() {
if (!m_activeSession) {
return;
}
m_sessionTimer.restart();
emit sessionTimerTicked(0);
}
void Service::endSession(Session::Result result) {
if (!m_activeSession) {
return;
}
// Update session with final values
Session updated = *m_activeSession;
updated.durationMs = getElapsedMs();
updated.result = result;
updated.lastModifiedAt = QDateTime::currentDateTime();
// Save to file and update in list
updateSession(updated);
// Clear active state
m_activeSession.reset();
m_tickTimer->stop();
emit activeSessionChanged();
}
std::optional<Session> Service::activeSession() const {
return m_activeSession;
}
void Service::onTickTimerFired() {
if (m_activeSession) {
emit sessionTimerTicked(getElapsedMs());
}
}
+22
View File
@@ -6,6 +6,8 @@
#include <QDateTime>
#include <QString>
#include <QFileInfo>
#include <QElapsedTimer>
#include <QTimer>
#include <optional>
struct Session
@@ -65,18 +67,38 @@ public slots:
void deleteSession(const QString &sessionId);
// Session lifecycle methods
void startSession(const Session &session);
void updateActiveSession(const Session &session); // Update without restarting timer
int getElapsedMs() const;
void resetSessionTimer();
void endSession(Session::Result result);
std::optional<Session> activeSession() const;
signals:
void sessionsChanged();
void activeSessionChanged();
void sessionTimerTicked(int elapsedMs);
private:
QString m_vaultDirectory;
QList<Session> m_sessions;
// Active session tracking
std::optional<Session> m_activeSession;
QElapsedTimer m_sessionTimer;
QTimer *m_tickTimer;
/// @returns null Session if not a valid session markdown document
std::optional<Session> parseFile(const QFileInfo &fileInfo);
Session::Result parseResult(const QString &resultStr);
void overwriteSessionFile(const Session &session);
private slots:
void onTickTimerFired();
};
Q_DECLARE_METATYPE(Session)
#endif //SERVICE_H
+139
View File
@@ -0,0 +1,139 @@
#include "sessionitemdelegate.h"
#include "service.h"
#include <QPainter>
#include <QDateTime>
SessionItemDelegate::SessionItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
void SessionItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (!index.isValid()) {
QStyledItemDelegate::paint(painter, option, index);
return;
}
painter->save();
// Get session data
Session session = index.data(Qt::UserRole).value<Session>();
QString oneliner = session.oneliner;
// Format duration
int seconds = session.durationMs / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
QString durationStr = QString("%1:%2:%3")
.arg(hours, 2, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
// Format result
QString resultStr;
switch (session.result) {
case Session::WIN:
resultStr = "WIN";
break;
case Session::LOSS:
resultStr = "LOSS";
break;
case Session::NEUTRAL:
resultStr = "NEUTRAL";
break;
default:
resultStr = "UNSPECIFIED";
break;
}
// Format relative time
QDateTime now = QDateTime::currentDateTime();
qint64 secsSinceModified = session.lastModifiedAt.secsTo(now);
QString relativeTime;
if (secsSinceModified < 60) {
relativeTime = "just now";
} else if (secsSinceModified < 3600) {
relativeTime = QString("%1 min ago").arg(secsSinceModified / 60);
} else if (secsSinceModified < 86400) {
relativeTime = QString("%1 hr ago").arg(secsSinceModified / 3600);
} else {
relativeTime = QString("%1 days ago").arg(secsSinceModified / 86400);
}
QString metadata = QString("Duration: %1 | Result: %2 | Modified: %3")
.arg(durationStr)
.arg(resultStr)
.arg(relativeTime);
// Draw background
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
} else {
painter->fillRect(option.rect, option.palette.base());
}
// Draw result indicator bar on the left
QColor indicatorColor;
switch (session.result) {
case Session::WIN:
indicatorColor = QColor(46, 204, 113); // Green
break;
case Session::LOSS:
indicatorColor = QColor(231, 76, 60); // Red
break;
case Session::NEUTRAL:
indicatorColor = QColor(149, 165, 166); // Gray
break;
default:
indicatorColor = QColor(0, 0, 0, 0); // Transparent
break;
}
if (session.result != Session::UNSPECIFIED) {
QRect indicatorRect(option.rect.left(), option.rect.top(), 4, option.rect.height());
painter->fillRect(indicatorRect, indicatorColor);
}
// Set up text drawing
QRect textRect = option.rect.adjusted(10, 5, -5, -5);
QColor textColor = (option.state & QStyle::State_Selected)
? option.palette.highlightedText().color()
: option.palette.text().color();
// Draw oneliner (bold, larger font)
painter->setPen(textColor);
QFont onelineFont = option.font;
onelineFont.setPointSize(14);
onelineFont.setBold(true);
painter->setFont(onelineFont);
QRect onelineRect = textRect;
onelineRect.setHeight(24);
QString elidedOneliner = painter->fontMetrics().elidedText(oneliner, Qt::ElideRight, onelineRect.width());
painter->drawText(onelineRect, Qt::AlignLeft | Qt::AlignVCenter, elidedOneliner);
// Draw metadata (smaller font, lighter color)
QFont metadataFont = option.font;
metadataFont.setPointSize(11);
painter->setFont(metadataFont);
QColor metadataColor = textColor;
metadataColor.setAlpha(180); // Make it slightly transparent
painter->setPen(metadataColor);
QRect metadataRect = textRect;
metadataRect.setTop(onelineRect.bottom() + 2);
QString elidedMetadata = painter->fontMetrics().elidedText(metadata, Qt::ElideRight, metadataRect.width());
painter->drawText(metadataRect, Qt::AlignLeft | Qt::AlignVCenter, elidedMetadata);
painter->restore();
}
QSize SessionItemDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
return QSize(option.rect.width(), 65);
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef SESSIONITEMDELEGATE_H
#define SESSIONITEMDELEGATE_H
#include <QStyledItemDelegate>
class SessionItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit SessionItemDelegate(QObject *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
};
#endif // SESSIONITEMDELEGATE_H
+11
View File
@@ -27,8 +27,19 @@ QVariant SessionsListModel::data(const QModelIndex &index, int role) const {
const Session session = m_service->sessions().at(index.row());
switch (role) {
case Qt::DisplayRole:
// Simple display for fallback - delegate will handle rich rendering
return session.oneliner;
case Qt::UserRole:
// Store full session object for delegate access
return QVariant::fromValue(session);
default:
return QVariant();
}
}
Session SessionsListModel::getSessionByIndex(const QModelIndex &index) const {
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
return Session(); // Return default-constructed session
}
return m_service->sessions().at(index.row());
}
+3 -1
View File
@@ -13,7 +13,9 @@ public:
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const;
QVariant data(const QModelIndex &index, int role) const override;
Session getSessionByIndex(const QModelIndex &index) const;
private:
Service *m_service;