From 0a3b315652f4f677eed45643cc6b8ad86ca71767 Mon Sep 17 00:00:00 2001 From: talksik Date: Sun, 1 Feb 2026 12:40:53 -0800 Subject: [PATCH] checkpoint: manage parsing, persistence, mainwindow structure --- CMakeLists.txt | 10 +- CMakeLists.txt.user | 460 ++++++++++++++++++++++++++++++++++++++++++ TODO.md | 45 +++++ main.cpp | 1 + mainwindow.cpp | 29 ++- mainwindow.h | 15 ++ mainwindow.ui | 96 +++++++++ namegenerator.cpp | 9 + namegenerator.h | 8 + service.cpp | 270 +++++++++++++++++++++++++ service.h | 82 ++++++++ sessionslistmodel.cpp | 34 ++++ sessionslistmodel.h | 22 ++ 13 files changed, 1075 insertions(+), 6 deletions(-) create mode 100644 CMakeLists.txt.user create mode 100644 TODO.md create mode 100644 mainwindow.ui create mode 100644 namegenerator.cpp create mode 100644 namegenerator.h create mode 100644 service.cpp create mode 100644 service.h create mode 100644 sessionslistmodel.cpp create mode 100644 sessionslistmodel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8150df8..7883e91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,15 @@ find_package(Qt6 REQUIRED COMPONENTS qt_standard_project_setup() -qt_add_executable(adhd main.cpp mainwindow.cpp) +qt_add_executable(adhd + main.cpp + mainwindow.cpp + mainwindow.h + service.cpp + service.h + mainwindow.ui + sessionslistmodel.h sessionslistmodel.cpp +) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..2b8465e --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,460 @@ + + + + + + EnvironmentId + {1354daad-1138-41d2-985c-27357b7ee48a} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 5 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Qt 6.8.6 for macOS + Qt 6.8.6 for macOS + qt.qt6.686.clang_64_kit + 0 + 0 + 0 + + Debug + 2 + false + + -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + 0 + /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Debug + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + Release + 2 + false + + -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Release + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-RelWithDebInfo + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + 0 + /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-Profile + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + CMakeProjectManager.CMakeBuildConfiguration + + + MinSizeRel + 2 + false + + -DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} + /Users/talksik/Documents/code/adhd/build/Qt_6_8_6_for_macOS-MinSizeRel + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + + ProjectExplorer.CustomExecutableRunConfiguration + + false + true + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a442b66 --- /dev/null +++ b/TODO.md @@ -0,0 +1,45 @@ +## Todo +- [ ] tab widget: one for settings, one for sessions, one for active session editing +- [ ] active session view would show timer, pause, end, and edit session details +- [ ] create new session button +- [ ] click on a session to set as active + +- [ ] create separate overlay that the main app can listen to and notify of active session changes + +## Ideas +- Show me all sessions and the current session +- Sum up how many sessions I won/lost +- A session can simply be described by simple paragraph of what you'll do, maybe markdown +- Less rigidity in the sense of typing out thoughts and session goals...less due dates. Capture first, do later. +- Later: allow notes within a session, jot things down, all live in the same folder + - It's like a todo.md +- Every session is based on week # - year - session x +- Every session is an individual document that lives on disk +- See which week of the year that I'm in +- See progress bar of how much of the year is done or percentage +- Show stats on how many sessions total I've won/lost +- Display a separate overlay window that shows me my active working item in center bottom in a pill +- Can commit to github for "sync" and even include an input url to auto-upload the data directory to github + +- overlay can show the first line of the session doc (or separate input) +- review of session can be: `Win`, `Neutral`, or `Loss` + +## Flow +- select location to save all your session data in markdown files +- data in the app parses the markdown file and uses it as the database, including the frontmatter + +1. At the start of the day/session, set an intention by creating a session +2. Enter the top things to do this session in plain english (list or paragraph in markdown) +3. Click start session, and clock runs. Can pause, or stop. +4. When done for the session, select "Close session"...it will ask you to grade the session +5. View previous sessions in a list + +## marketing +- local-only +- access files and metadata +- simple video demo OR flowy research-site page that has simple visuals + +## Launch +- [ ] install via brew +- [ ] open source github +- [ ] hackernews "for developers with adhd"...purist site diff --git a/main.cpp b/main.cpp index 1e87fcd..df55bee 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ #include #include #include "mainwindow.h" +#include "service.h" int main(int argc, char *argv[]) { std::cout << "Starting adhd program" << std::endl; diff --git a/mainwindow.cpp b/mainwindow.cpp index bdf4f15..b694bda 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,13 +1,32 @@ #include "mainwindow.h" +#include "service.h" +#include "sessionslistmodel.h" +#include #include +#include +#include "ui_mainwindow.h" -MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), m_service(new Service(this)) { - QWidget *centralWidget = new QWidget(this); - QLabel *label = new QLabel(centralWidget); - label->setText("Let's get some stuff done!"); + ui = new Ui::MainWindow; + ui->setupUi(this); - setCentralWidget(centralWidget); + m_listModel = new SessionsListModel(m_service, this); + ui->listView->setModel(m_listModel); } MainWindow::~MainWindow() {} + +void MainWindow::on_selectVaultButton_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, + "Select Directory", + QDir::homePath(), + QFileDialog::ShowDirsOnly); + if (!dir.isEmpty()) { + m_service->setVaultDirectory(dir); + ui->tabWidget->setCurrentIndex(1); + ui->vaultLabel->setText(m_service->vaultDirectory()); + } +} diff --git a/mainwindow.h b/mainwindow.h index 2869038..e62dba7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -4,15 +4,30 @@ #include #include +class Service; +class SessionsListModel; + QT_BEGIN_NAMESPACE QT_END_NAMESPACE +namespace Ui { +class MainWindow; +} + class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); + + private slots: + void on_selectVaultButton_clicked(); + + private: + Ui::MainWindow *ui; + Service *m_service; + SessionsListModel *m_listModel; }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..0fd782c --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,96 @@ + + + MainWindow + + + + 0 + 0 + 400 + 600 + + + + MainWindow + + + + + + + 0 + + + + Settings + + + + + 140 + 210 + 88 + 16 + + + + Select a folder + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + 120 + 240 + 121 + 32 + + + + Select vault + + + + + + true + + + + + + Sessions + + + + + + + + + + Active + + + + + + + + + + 0 + 0 + 400 + 30 + + + + + + + + diff --git a/namegenerator.cpp b/namegenerator.cpp new file mode 100644 index 0000000..8e307ad --- /dev/null +++ b/namegenerator.cpp @@ -0,0 +1,9 @@ +#include "namegenerator.cpp" + +namespace { + constexpr char data[] +} + +QString generate() { + return; +} diff --git a/namegenerator.h b/namegenerator.h new file mode 100644 index 0000000..4aaa210 --- /dev/null +++ b/namegenerator.h @@ -0,0 +1,8 @@ +#ifndef NAMEGENERATOR_H +#define NAMEGENERATOR_H + +#include + +QString generate(); + +#endif //NAMEGENERATOR_H diff --git a/service.cpp b/service.cpp new file mode 100644 index 0000000..e3fd559 --- /dev/null +++ b/service.cpp @@ -0,0 +1,270 @@ +#include "service.h" +#include +#include +#include +#include + +bool Session::isComplete() const { + return result != UNSPECIFIED; +} + +Service::Service(QObject *parent) : QObject(parent) { +} + +Service::~Service() {} + +QString Service::vaultDirectory() const { + return m_vaultDirectory; +} + +int Service::sessionCount() const { + return m_sessions.size(); +} + +int Service::completedSessionCount() const { + int count = 0; + for (const Session &session : m_sessions) { + if (session.isComplete()) { + count++; + } + } + return count; +} + +int Service::totalWins() const { + int count = 0; + for (const Session &session : m_sessions) { + if (session.result == Session::WIN) { + count++; + } + } + return count; +} + +int Service::totalLosses() const { + int count = 0; + for (const Session &session : m_sessions) { + if (session.result == Session::LOSS) { + count++; + } + } + return count; +} + +int Service::totalNeutrals() const { + int count = 0; + for (const Session &session : m_sessions) { + if (session.result == Session::NEUTRAL) { + count++; + } + } + return count; +} + +QList Service::sessions() const { + return m_sessions; +} + +void Service::setVaultDirectory(const QString &path) { + m_sessions.clear(); + + qDebug() << "Setting vault directory to: " << path; + + QDir dir = QDir(path); + if (!dir.exists()) { + qWarning() << "Vault directory does not exist"; + return; + } + + m_vaultDirectory = path; + + dir.setFilter(QDir::Files); + dir.setSorting(QDir::Time); + + QFileInfoList list = dir.entryInfoList(); + for (int i = 0; i < list.size(); ++i) { + QFileInfo fileInfo = list.at(i); + + if (fileInfo.completeSuffix() != QStringLiteral("md")) { + qWarning() << "Skipping invalid file format: " << fileInfo.completeSuffix(); + continue; + } + + qDebug() << "Parsing file: " << fileInfo.absoluteFilePath(); + std::optional session = parseFile(fileInfo); + if (session) { + m_sessions.append(*session); + } else { + qWarning() << "Failed to parse session file: " << fileInfo.absoluteFilePath(); + } + } + + emit sessionsChanged(); +} + +Session Service::createSession(const QString &oneliner, const QString &content) { + assert(!m_vaultDirectory.isEmpty() && "Vault directory must be set before creating sessions"); + + QString uuid = QUuid().toString(QUuid::WithoutBraces); + + Session session = {}; + session.id = uuid; + session.oneliner = oneliner; + session.durationMs = 0; + session.content = content; + session.result = Session::UNSPECIFIED; + session.createdAt = QDateTime::currentDateTime(); + session.lastModifiedAt = QDateTime::currentDateTime(); + + overwriteSessionFile(session); + + m_sessions.prepend(session); + emit sessionsChanged(); + + return session; +} + +void Service::updateSession(const Session &session) { + overwriteSessionFile(session); + for (int i = 0; i < m_sessions.size(); ++i) { + if (m_sessions[i].id == session.id) { + m_sessions[i] = session; + break; + } + } + emit sessionsChanged(); +} + +void Service::deleteSession(const QString &sessionId) { + QDir dir = QDir(m_vaultDirectory); + QString filePath = dir.filePath(sessionId + ".md"); + QFile file = QFile(filePath); + if (!file.remove()) { + qWarning() << "Failed to delete session file: " << filePath; + return; + } + + for (int i = 0; i < m_sessions.size(); ++i) { + if (m_sessions[i].id == sessionId) { + m_sessions.removeAt(i); + break; + } + } + emit sessionsChanged(); +} + +std::optional Service::parseFile(const QFileInfo &fileInfo) { + QString path = fileInfo.absoluteFilePath(); + QFile file = QFile(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return std::nullopt; + } + + Session session = {}; + session.id = file.fileName(); + session.content = ""; + session.oneliner = ""; + session.durationMs = 0; + session.result = Session::UNSPECIFIED; + session.createdAt = fileInfo.birthTime(); + session.lastModifiedAt = fileInfo.lastModified(); + + QTextStream in(&file); + int separatorCounts = 0; + while (!in.atEnd()) { + QString line = in.readLine(); + // qDebug() << "Reading line: " << line; + + if (separatorCounts >= 2) { + session.content += line + '\n'; + continue; + } + + switch (line[0].toLatin1()) { + case '-': + separatorCounts++; + continue; + break; + default: + break; + } + // parse each key value pair + QStringList parts = line.split(':'); + if (parts.size() != 2) { + continue; + } + QString key = parts[0].trimmed(); + QString value = parts[1].trimmed(); + if (key == "Oneliner") { + session.oneliner = value; + continue; + } else if (key == "DurationMs") { + session.durationMs = value.toInt(); + continue; + } else if (key == "Result") { + session.result = parseResult(value); + continue; + } + } + + qDebug() << "Parsed oneliner: " << session.oneliner; + qDebug() << "Parsed durationMs: " << session.durationMs; + qDebug() << "Parsed result: " << static_cast(session.result); + qDebug() << "Parsed content: " << session.content; + qDebug() << "Created at: " << session.createdAt; + qDebug() << "Last modified at: " << session.lastModifiedAt; + + file.close(); + + return session; +} + +Session::Result Service::parseResult(const QString &resultStr) { + if (resultStr == "Win") { + return Session::WIN; + } else if (resultStr == "Loss") { + return Session::LOSS; + } else if (resultStr == "Neutral") { + return Session::NEUTRAL; + } else { + return Session::UNSPECIFIED; + } +} + +void Service::overwriteSessionFile(const Session &session) +{ + // rewrites the entire file + QDir dir = QDir(m_vaultDirectory); + QString filePath = dir.filePath(session.id + ".md"); + QFile file = QFile(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << "Failed to update session file: " << filePath; + return; + } + + QTextStream out(&file); + out << "---\n"; + out << "Oneliner: " << session.oneliner << "\n"; + out << "DurationMs: " << session.durationMs << "\n"; + 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 = ""; + break; + } + if (!resultStr.isEmpty()) { + out << "Result: " << resultStr << "\n"; + } + out << "---\n"; + out << session.content << "\n"; + file.close(); +} diff --git a/service.h b/service.h new file mode 100644 index 0000000..011bbad --- /dev/null +++ b/service.h @@ -0,0 +1,82 @@ +#ifndef SERVICE_H +#define SERVICE_H + +#include +#include +#include +#include +#include +#include + +struct Session +{ + enum Result { + UNSPECIFIED = 0, + WIN, + LOSS, + NEUTRAL + }; + // Becomes the file name + QString id; + // Stored in the frontmatter under 'Oneliner' key + QString oneliner; + // Stored in the frontmatter + int durationMs; + // After the --- onwards, serves as general scratchpad + QString content; + // Stored in the frontmatter under 'Result' key + Result result; + + QDateTime lastModifiedAt; + QDateTime createdAt; + + bool isComplete() const; +}; + +class Service : public QObject { + Q_OBJECT + +public: + explicit Service(QObject *parent = nullptr); + ~Service(); + + QList sessions() const; + QString vaultDirectory() const; + + int sessionCount() const; + int completedSessionCount() const; + int totalWins() const; + int totalLosses() const; + int totalNeutrals() const; + +public slots: + /// @brief sets the directory from which to read/write sessions + /// Fails silently if not a valid path + /// Loads sessions from this path + void setVaultDirectory(const QString &path); + + /// @brief creates the session and stores in sessions list + /// @returns the newly created session + Session createSession(const QString &oneliner, const QString &content); + + /// @brief updates the session + /// Updates session in the sessions list, and the file associated + void updateSession(const Session &session); + + void deleteSession(const QString &sessionId); + +signals: + void sessionsChanged(); + +private: + QString m_vaultDirectory; + QList m_sessions; + + /// @returns null Session if not a valid session markdown document + std::optional parseFile(const QFileInfo &fileInfo); + + Session::Result parseResult(const QString &resultStr); + void overwriteSessionFile(const Session &session); +}; + +#endif //SERVICE_H diff --git a/sessionslistmodel.cpp b/sessionslistmodel.cpp new file mode 100644 index 0000000..77239b8 --- /dev/null +++ b/sessionslistmodel.cpp @@ -0,0 +1,34 @@ +#include "sessionslistmodel.h" + +SessionsListModel::SessionsListModel(Service *service, QObject *parent) + : QAbstractListModel{parent}, m_service(service) +{ + connect(m_service, &Service::sessionsChanged, this, [this]() { + beginResetModel(); + endResetModel(); + }); +} + +int SessionsListModel::rowCount(const QModelIndex &parent) const { + return m_service->sessions().size(); +} + +int SessionsListModel::columnCount(const QModelIndex &parent) const { + return 1; +} + +QVariant SessionsListModel::data(const QModelIndex &index, int role) const { + if (index.row() < 0 || index.row() >= rowCount()) { + return QVariant(); + } else if (!index.isValid()) { + return QVariant(); + } + + const Session session = m_service->sessions().at(index.row()); + switch (role) { + case Qt::DisplayRole: + return session.oneliner; + default: + return QVariant(); + } +} diff --git a/sessionslistmodel.h b/sessionslistmodel.h new file mode 100644 index 0000000..1419bf4 --- /dev/null +++ b/sessionslistmodel.h @@ -0,0 +1,22 @@ +#ifndef SESSIONSLISTMODEL_H +#define SESSIONSLISTMODEL_H + +#include +#include +#include "service.h" + +class SessionsListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit SessionsListModel(Service *service, QObject *parent = nullptr); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const; + +private: + Service *m_service; +}; + +#endif // SESSIONSLISTMODEL_H