From 8b9d388b1d28f5eb57c14ef1193e4517c0c5fa28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20DELHAIE?= Date: Sun, 22 Mar 2026 17:54:37 +0100 Subject: [PATCH] rearch, add note, add sqlite --- ToDo.pro | 32 ++- dialog.cpp | 36 --- dialog.h | 29 -- listservice.cpp | 50 ---- mainwindow.cpp | 54 ---- src/core/listservice.cpp | 191 ++++++++++++++ listservice.h => src/core/listservice.h | 17 +- src/core/noteservice.cpp | 248 ++++++++++++++++++ src/core/noteservice.h | 37 +++ src/gui/dialog/input/inputdialog.cpp | 52 ++++ src/gui/dialog/input/inputdialog.h | 33 +++ .../gui/dialog/input/inputdialog.ui | 14 +- src/gui/mainwindow.cpp | 183 +++++++++++++ mainwindow.h => src/gui/mainwindow.h | 13 +- mainwindow.ui => src/gui/mainwindow.ui | 135 +++++++--- main.cpp => src/main.cpp | 2 +- list.cpp => src/obj/list.cpp | 0 list.h => src/obj/list.h | 0 src/obj/note.cpp | 52 ++++ src/obj/note.h | 30 +++ 20 files changed, 975 insertions(+), 233 deletions(-) delete mode 100644 dialog.cpp delete mode 100644 dialog.h delete mode 100644 listservice.cpp delete mode 100644 mainwindow.cpp create mode 100644 src/core/listservice.cpp rename listservice.h => src/core/listservice.h (75%) create mode 100644 src/core/noteservice.cpp create mode 100644 src/core/noteservice.h create mode 100644 src/gui/dialog/input/inputdialog.cpp create mode 100644 src/gui/dialog/input/inputdialog.h rename dialog.ui => src/gui/dialog/input/inputdialog.ui (92%) create mode 100644 src/gui/mainwindow.cpp rename mainwindow.h => src/gui/mainwindow.h (57%) rename mainwindow.ui => src/gui/mainwindow.ui (69%) rename main.cpp => src/main.cpp (94%) rename list.cpp => src/obj/list.cpp (100%) rename list.h => src/obj/list.h (100%) create mode 100644 src/obj/note.cpp create mode 100644 src/obj/note.h diff --git a/ToDo.pro b/ToDo.pro index a5fb6bc..fafadb1 100644 --- a/ToDo.pro +++ b/ToDo.pro @@ -1,12 +1,12 @@ -QT += widgets +QT += widgets sql CONFIG += c++17 QMAKE_CXXFLAGS += -Wall -Wextra -Wpedantic QMAKE_CXXFLAGS += -Werror -win32:VERSION = 3.0.0.0 # major.minor.patch.build -else:VERSION = 3.0.0 # major.minor.patch +win32:VERSION = 0.0.1.0 # major.minor.patch.build +else:VERSION = 0.0.1 # major.minor.patch DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\" DEFINES += APP_NAME=\"\\\"ToDo\\\"\" @@ -70,21 +70,25 @@ linux-* { #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - dialog.cpp \ - list.cpp \ - listservice.cpp \ - main.cpp \ - mainwindow.cpp + src/core/noteservice.cpp \ + src/gui/dialog/input/inputdialog.cpp \ + src/obj/list.cpp \ + src/core/listservice.cpp \ + src/main.cpp \ + src/gui/mainwindow.cpp \ + src/obj/note.cpp HEADERS += \ - dialog.h \ - list.h \ - listservice.h \ - mainwindow.h + src/core/noteservice.h \ + src/gui/dialog/input/inputdialog.h \ + src/obj/list.h \ + src/core/listservice.h \ + src/gui/mainwindow.h \ + src/obj/note.h FORMS += \ - dialog.ui \ - mainwindow.ui + src/gui/dialog/input/inputdialog.ui \ + src/gui/mainwindow.ui TRANSLATIONS += \ ToDo_en_150.ts diff --git a/dialog.cpp b/dialog.cpp deleted file mode 100644 index 9691ab1..0000000 --- a/dialog.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "dialog.h" -#include "ui_dialog.h" - -Dialog::Dialog(QWidget *parent, QString title, QString headline, QString message) : QDialog(parent), ui(new Ui::Dialog) -{ - ui->setupUi(this); - this->setWindowTitle(title); - ui->headline->setText(headline); - ui->message->setText(message); -} - -Dialog::~Dialog() -{ - delete ui; -} - -QString Dialog::getInput() -{ - if (this->result() != Accepted) { - return ""; - } - return this->ui->lineEdit->text(); -} - - -void Dialog::on_buttonBox_accepted() -{ - accept(); -} - - -void Dialog::on_buttonBox_rejected() -{ - reject(); -} - diff --git a/dialog.h b/dialog.h deleted file mode 100644 index 9a7b9ab..0000000 --- a/dialog.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DIALOG_H -#define DIALOG_H - -#include - -namespace Ui { -class Dialog; -} - -class Dialog : public QDialog -{ - Q_OBJECT - -public: - Dialog(QWidget *parent, QString title, QString headline, QString message); - ~Dialog(); - - QString getInput(); - -private slots: - void on_buttonBox_accepted(); - - void on_buttonBox_rejected(); - -private: - Ui::Dialog *ui; -}; - -#endif // DIALOG_H diff --git a/listservice.cpp b/listservice.cpp deleted file mode 100644 index 23b0a3b..0000000 --- a/listservice.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "listservice.h" - -ListService* ListService::_instance = nullptr; - -ListService::ListService() {} - -ListService *ListService::getInstance() -{ - if (_instance == nullptr) { - _instance = new ListService(); - } - - return _instance; -} - -List ListService::create(QString name) -{ - // create the object, the uuid is already assigned by itself - List l = List(name); - - // store - // TODO: implement the real datastore (SQLite or something like that) - this->store[l.getUUID().toUInt128()] = l; - - // return the actual value - onListCreated(l); - return l; -} - -List ListService::update(QUuid uuid, QString newName) -{ - if (!this->store.contains(uuid.toUInt128())) { - throw new std::exception(); - } - - List l = this->store.value(uuid.toUInt128(), List("!!!")); - l.setName(newName); - this->store[uuid.toUInt128()] = l; - - onListUpdated(l); - return l; -} - -void ListService::remove(QUuid uuid) -{ - this->store.remove(uuid.toUInt128()); - onListDeleted(uuid); -} - - diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index 3b56b2e..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include "dialog.h" -#include "listservice.h" - -#include -#include - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - /* - * Events - */ - - // ui - connect(ui->addListButton, &QPushButton::clicked, this, &MainWindow::openCreateListDialog); - - // services - connect(ListService::getInstance(), &ListService::onListCreated, this, &MainWindow::onListCreated); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::openCreateListDialog(bool _) -{ - // create the input dialog - Dialog d = Dialog(this, "Create a list", "New List", "Give a name to this list"); - auto res = d.exec(); - - // execute, ignore if not saved - if (res != QDialog::Accepted) { - return; - } - - QString newListName = d.getInput(); - ListService::getInstance()->create(newListName); -} - -void MainWindow::onListCreated(List value) -{ - QListWidgetItem* item = new QListWidgetItem(); - item->setText(value.getName()); - item->setData(Qt::UserRole, QVariant(value.getUUID())); - - this->ui->lists->addItem(item); -} diff --git a/src/core/listservice.cpp b/src/core/listservice.cpp new file mode 100644 index 0000000..bf7f78a --- /dev/null +++ b/src/core/listservice.cpp @@ -0,0 +1,191 @@ +#include "src/core/listservice.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace { +const QString kConnectionName = "todo_connection"; +const QString kDatabaseFileName = "todo.sqlite3"; + +QString toDbUuid(const QUuid &uuid) +{ + return uuid.toString(QUuid::WithoutBraces); +} + +std::runtime_error makeSqlException(const QString &context, const QString &error) +{ + return std::runtime_error(QString("%1: %2").arg(context, error).toStdString()); +} + +QSqlDatabase getOpenDatabase() +{ + QSqlDatabase db; + if (QSqlDatabase::contains(kConnectionName)) { + db = QSqlDatabase::database(kConnectionName); + } else { + const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + if (appDataPath.isEmpty()) { + throw std::runtime_error("Unable to resolve AppDataLocation for SQLite database"); + } + + QDir dir(appDataPath); + if (!dir.exists() && !dir.mkpath(".")) { + throw std::runtime_error("Unable to create application data directory for SQLite database"); + } + + db = QSqlDatabase::addDatabase("QSQLITE", kConnectionName); + db.setDatabaseName(dir.filePath(kDatabaseFileName)); + } + + if (!db.isOpen() && !db.open()) { + throw makeSqlException("Failed to open SQLite database", db.lastError().text()); + } + + QSqlQuery pragma(db); + if (!pragma.exec("PRAGMA foreign_keys = ON;")) { + throw makeSqlException("Failed to enable SQLite foreign keys", pragma.lastError().text()); + } + + return db; +} + +void ensureSchema(QSqlDatabase &db) +{ + QSqlQuery query(db); + + if (!query.exec("CREATE TABLE IF NOT EXISTS lists (" + "id TEXT PRIMARY KEY," + "name TEXT NOT NULL" + ");")) { + throw makeSqlException("Failed to create lists table", query.lastError().text()); + } + + if (!query.exec("CREATE TABLE IF NOT EXISTS notes (" + "id TEXT PRIMARY KEY," + "list_id TEXT NOT NULL," + "content TEXT NOT NULL," + "finished BOOL NOT NULL," + "FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE" + ");")) { + throw makeSqlException("Failed to create notes table", query.lastError().text()); + } + + if (!query.exec("CREATE INDEX IF NOT EXISTS idx_notes_list_id ON notes(list_id);")) { + throw makeSqlException("Failed to create notes index", query.lastError().text()); + } +} +} // namespace + +ListService* ListService::_instance = nullptr; + +ListService::ListService() +{ + QSqlDatabase db = getOpenDatabase(); + ensureSchema(db); +} + +ListService *ListService::getInstance() +{ + if (_instance == nullptr) { + _instance = new ListService(); + } + return _instance; +} + +List ListService::create(QString name) +{ + List list(name); + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("INSERT INTO lists(id, name) VALUES(:id, :name);"); + query.bindValue(":id", toDbUuid(list.getUUID())); + query.bindValue(":name", list.getName()); + + if (!query.exec()) { + throw makeSqlException("Failed to insert list", query.lastError().text()); + } + + emit onListCreated(list); + return list; +} + +List ListService::update(QUuid uuid, QString newName) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("UPDATE lists SET name = :name WHERE id = :id;"); + query.bindValue(":id", toDbUuid(uuid)); + query.bindValue(":name", newName); + + if (!query.exec()) { + throw makeSqlException("Failed to update list", query.lastError().text()); + } + if (query.numRowsAffected() <= 0) { + throw std::runtime_error("List not found"); + } + + List updated(uuid, newName); + emit onListUpdated(updated); + return updated; +} + +void ListService::remove(QUuid uuid) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("DELETE FROM lists WHERE id = :id;"); + query.bindValue(":id", toDbUuid(uuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to delete list", query.lastError().text()); + } + + emit onListDeleted(uuid); +} + +QList ListService::getAll() +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + if (!query.exec("SELECT id, name FROM lists ORDER BY name COLLATE NOCASE ASC;")) { + throw makeSqlException("Failed to read lists", query.lastError().text()); + } + + QList lists; + while (query.next()) { + const QUuid uuid(query.value(0).toString()); + const QString name = query.value(1).toString(); + lists.append(List(uuid, name)); + } + + return lists; +} + +List ListService::getByUuid(QUuid uuid) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("SELECT name FROM lists WHERE id = :id LIMIT 1;"); + query.bindValue(":id", toDbUuid(uuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to read list by uuid", query.lastError().text()); + } + + if (!query.next()) { + throw std::runtime_error("List not found"); + } + + return List(uuid, query.value(0).toString()); +} diff --git a/listservice.h b/src/core/listservice.h similarity index 75% rename from listservice.h rename to src/core/listservice.h index 7a7ea35..cb98df6 100644 --- a/listservice.h +++ b/src/core/listservice.h @@ -2,24 +2,19 @@ #define LISTSERVICE_H #include -#include +#include +#include -#include "list.h" +#include "src/obj/list.h" -/* - * - * ListService is a singleton that query the "lists" over the datasource layer - * - */ class ListService : public QObject { Q_OBJECT + private: ListService(); static ListService* _instance; - QMap store; - public: static ListService* getInstance(); @@ -27,11 +22,13 @@ public: List update(QUuid uuid, QString newName); void remove(QUuid uuid); + QList getAll(); + List getByUuid(QUuid uuid); + signals: void onListCreated(List value); void onListUpdated(List value); void onListDeleted(QUuid uuid); - }; #endif // LISTSERVICE_H diff --git a/src/core/noteservice.cpp b/src/core/noteservice.cpp new file mode 100644 index 0000000..207b6ad --- /dev/null +++ b/src/core/noteservice.cpp @@ -0,0 +1,248 @@ +#include "noteservice.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace { +const QString kConnectionName = "todo_connection"; +const QString kDatabaseFileName = "todo.sqlite3"; + +QString toDbUuid(const QUuid &uuid) +{ + return uuid.toString(QUuid::WithoutBraces); +} + +std::runtime_error makeSqlException(const QString &context, const QString &error) +{ + return std::runtime_error(QString("%1: %2").arg(context, error).toStdString()); +} + +QSqlDatabase getOpenDatabase() +{ + QSqlDatabase db; + if (QSqlDatabase::contains(kConnectionName)) { + db = QSqlDatabase::database(kConnectionName); + } else { + const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + if (appDataPath.isEmpty()) { + throw std::runtime_error("Unable to resolve AppDataLocation for SQLite database"); + } + + QDir dir(appDataPath); + if (!dir.exists() && !dir.mkpath(".")) { + throw std::runtime_error("Unable to create application data directory for SQLite database"); + } + + db = QSqlDatabase::addDatabase("QSQLITE", kConnectionName); + db.setDatabaseName(dir.filePath(kDatabaseFileName)); + } + + if (!db.isOpen() && !db.open()) { + throw makeSqlException("Failed to open SQLite database", db.lastError().text()); + } + + QSqlQuery pragma(db); + if (!pragma.exec("PRAGMA foreign_keys = ON;")) { + throw makeSqlException("Failed to enable SQLite foreign keys", pragma.lastError().text()); + } + + return db; +} + +void ensureSchema(QSqlDatabase &db) +{ + QSqlQuery query(db); + + if (!query.exec("CREATE TABLE IF NOT EXISTS lists (" + "id TEXT PRIMARY KEY," + "name TEXT NOT NULL" + ");")) { + throw makeSqlException("Failed to create lists table", query.lastError().text()); + } + + if (!query.exec("CREATE TABLE IF NOT EXISTS notes (" + "id TEXT PRIMARY KEY," + "list_id TEXT NOT NULL," + "content TEXT NOT NULL," + "finished BOOL NOT NULL," + "FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE" + ");")) { + throw makeSqlException("Failed to create notes table", query.lastError().text()); + } + + if (!query.exec("CREATE INDEX IF NOT EXISTS idx_notes_list_id ON notes(list_id);")) { + throw makeSqlException("Failed to create notes index", query.lastError().text()); + } +} + +void ensureListExists(QSqlDatabase &db, const QUuid &listUuid) +{ + QSqlQuery query(db); + query.prepare("SELECT 1 FROM lists WHERE id = :id LIMIT 1;"); + query.bindValue(":id", toDbUuid(listUuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to validate list existence", query.lastError().text()); + } + + if (!query.next()) { + throw std::runtime_error("Parent list not found"); + } +} +} // namespace + +NoteService* NoteService::_instance = nullptr; + +NoteService::NoteService() +{ + QSqlDatabase db = getOpenDatabase(); + ensureSchema(db); +} + +NoteService *NoteService::getInstance() +{ + if (_instance == nullptr) { + _instance = new NoteService(); + } + + return _instance; +} + +QUuid NoteService::create(QUuid listUuid, QString value) +{ + QSqlDatabase db = getOpenDatabase(); + ensureListExists(db, listUuid); + + const QUuid newNoteUuid = QUuid::createUuid(); + + QSqlQuery query(db); + query.prepare("INSERT INTO notes(id, list_id, content, finished) VALUES(:id, :list_id, :content, false);"); + query.bindValue(":id", toDbUuid(newNoteUuid)); + query.bindValue(":list_id", toDbUuid(listUuid)); + query.bindValue(":content", value); + + if (!query.exec()) { + throw makeSqlException("Failed to insert note", query.lastError().text()); + } + + Note n = Note(listUuid, newNoteUuid, value, false); + emit onNoteCreated(n); + return newNoteUuid; +} + +QString NoteService::update(QUuid listUuid, QUuid noteUuid, QString newValue) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("UPDATE notes SET content = :content WHERE id = :id AND list_id = :list_id;"); + query.bindValue(":id", toDbUuid(noteUuid)); + query.bindValue(":list_id", toDbUuid(listUuid)); + query.bindValue(":content", newValue); + + if (!query.exec()) { + throw makeSqlException("Failed to update note", query.lastError().text()); + } + if (query.numRowsAffected() <= 0) { + throw std::runtime_error("Note not found"); + } + + Note n = Note(listUuid, noteUuid, newValue, false); + emit onNoteUpdated(n); + return newValue; +} + +void NoteService::setFinishedValue(QUuid listUuid, QUuid noteUuid, bool isFinished) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("UPDATE notes SET finished = :finished WHERE id = :id AND list_id = :list_id;"); + query.bindValue(":id", toDbUuid(noteUuid)); + query.bindValue(":list_id", toDbUuid(listUuid)); + query.bindValue(":finished", isFinished); + + if (!query.exec()) { + throw makeSqlException("Failed to update note", query.lastError().text()); + } + if (query.numRowsAffected() <= 0) { + throw std::runtime_error("Note not found"); + } + + std::optional note = getByUUID(noteUuid); + + if (!note.has_value()) { + throw std::runtime_error("database integrity corrupted"); + } + Note n = Note(listUuid, noteUuid, note.value().getContent(), isFinished); + emit onNoteUpdated(n); +} + +void NoteService::remove(QUuid listUuid, QUuid noteUuid) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("DELETE FROM notes WHERE id = :id AND list_id = :list_id;"); + query.bindValue(":id", toDbUuid(noteUuid)); + query.bindValue(":list_id", toDbUuid(listUuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to delete note", query.lastError().text()); + } + + emit onNoteDeleted(listUuid, noteUuid); +} + +QList NoteService::getByList(QUuid listUuid) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("SELECT id, content, finished FROM notes WHERE list_id = :list_id;"); + query.bindValue(":list_id", toDbUuid(listUuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to read notes by list", query.lastError().text()); + } + + QList notes; + while (query.next()) { + const QUuid noteId(query.value(0).toString()); + const QString content = query.value(1).toString(); + const bool finished = query.value(2).toBool(); + notes.append(Note(listUuid, noteId, content, finished)); + } + + return notes; +} + +std::optional NoteService::getByUUID(QUuid noteUuid) +{ + QSqlDatabase db = getOpenDatabase(); + + QSqlQuery query(db); + query.prepare("SELECT id, content, finished, list_id FROM notes WHERE id = :id;"); + query.bindValue(":id", toDbUuid(noteUuid)); + + if (!query.exec()) { + throw makeSqlException("Failed to read note by uuid", query.lastError().text()); + } + + if (query.next()) { + const QUuid noteId(query.value(0).toString()); + const QString content = query.value(1).toString(); + const bool finished = query.value(2).toBool(); + const QUuid listUuid(query.value(3).toString()); + + return std::optional(Note(listUuid, noteId, content, finished)); + } + + return std::nullopt; +} diff --git a/src/core/noteservice.h b/src/core/noteservice.h new file mode 100644 index 0000000..907220f --- /dev/null +++ b/src/core/noteservice.h @@ -0,0 +1,37 @@ +#ifndef NOTESERVICE_H +#define NOTESERVICE_H + +#include +#include +#include +#include +#include + +#include "src/obj/note.h" + +class NoteService : public QObject +{ + Q_OBJECT + +private: + NoteService(); + static NoteService* _instance; + +public: + static NoteService* getInstance(); + + QUuid create(QUuid listUuid, QString value); + QString update(QUuid listUuid, QUuid noteUuid, QString newValue); + void setFinishedValue(QUuid listUuid, QUuid noteUuid, bool isFinished); + void remove(QUuid listUuid, QUuid noteUuid); + + QList getByList(QUuid listUuid); + std::optional getByUUID(QUuid noteUuid); + +signals: + void onNoteCreated(Note value); + void onNoteUpdated(Note value); + void onNoteDeleted(QUuid listUuid, QUuid noteUuid); +}; + +#endif // NOTESERVICE_H diff --git a/src/gui/dialog/input/inputdialog.cpp b/src/gui/dialog/input/inputdialog.cpp new file mode 100644 index 0000000..899bedd --- /dev/null +++ b/src/gui/dialog/input/inputdialog.cpp @@ -0,0 +1,52 @@ +#include "inputdialog.h" +#include "ui_inputdialog.h" + +InputDialog::InputDialog(QWidget *parent, QString title, QString headline, QString message) : QDialog(parent), ui(new Ui::InputDialog) +{ + ui->setupUi(this); + this->setWindowTitle(title); + ui->errorLabel->setVisible(false); + ui->headline->setText(headline); + ui->message->setText(message); +} + +InputDialog::~InputDialog() +{ + delete ui; +} + +QString InputDialog::getInput() +{ + if (this->result() != Accepted) { + return ""; + } + return this->ui->lineEdit->text(); +} + +void InputDialog::on_buttonBox_accepted() +{ + if (ui->lineEdit->text().isEmpty()) { + setErrorLabel("Empty name is not valid"); + return; + } + accept(); +} + +void InputDialog::setErrorLabel(QString error) +{ + ui->errorLabel->setText(error); + ui->errorLabel->setVisible(true); +} + +void InputDialog::clearErrorLabel() +{ + ui->errorLabel->setVisible(false); + ui->errorLabel->setText("error"); +} + +void InputDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + if (ui->buttonBox->standardButton(button) == QDialogButtonBox::Discard) { + reject(); + } +} diff --git a/src/gui/dialog/input/inputdialog.h b/src/gui/dialog/input/inputdialog.h new file mode 100644 index 0000000..bda2d3f --- /dev/null +++ b/src/gui/dialog/input/inputdialog.h @@ -0,0 +1,33 @@ +#ifndef INPUTDIALOG_H +#define INPUTDIALOG_H + +#include +#include + +namespace Ui { +class InputDialog; +} + +class InputDialog : public QDialog +{ + Q_OBJECT + +public: + InputDialog(QWidget *parent, QString title, QString headline, QString message); + ~InputDialog(); + + QString getInput(); + +private slots: + void on_buttonBox_accepted(); + + void on_buttonBox_clicked(QAbstractButton *button); + +private: + Ui::InputDialog *ui; + + void setErrorLabel(QString error); + void clearErrorLabel(); +}; + +#endif // INPUTDIALOG_H diff --git a/dialog.ui b/src/gui/dialog/input/inputdialog.ui similarity index 92% rename from dialog.ui rename to src/gui/dialog/input/inputdialog.ui index 549d341..82dcc77 100644 --- a/dialog.ui +++ b/src/gui/dialog/input/inputdialog.ui @@ -1,7 +1,7 @@ - Dialog - + InputDialog + Qt::WindowModality::ApplicationModal @@ -139,6 +139,16 @@ border: 2px solid rgb(230, 230, 230); + + + + color: rgb(255, 89, 92); + + + error + + + diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp new file mode 100644 index 0000000..dbe1288 --- /dev/null +++ b/src/gui/mainwindow.cpp @@ -0,0 +1,183 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "src/gui/dialog/input/inputdialog.h" +#include "src/core/listservice.h" +#include "src/core/noteservice.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + /* + * Events + */ + + // ui + connect(ui->addListButton, &QPushButton::clicked, this, &MainWindow::openCreateListDialog); + connect(ui->lists, &QListWidget::currentRowChanged, this, &MainWindow::onListSelected); + connect(ui->saveNoteButton, &QPushButton::clicked, this, &MainWindow::onSaveNoteButtonClicked); + connect(ui->notes, &QListWidget::itemChanged, this, &MainWindow::onNoteChanged); + + // services + connect(ListService::getInstance(), &ListService::onListCreated, this, &MainWindow::onListCreated); + connect(ListService::getInstance(), &ListService::onListUpdated, this, &MainWindow::onListCreated); + connect(NoteService::getInstance(), &NoteService::onNoteCreated, this, &MainWindow::onNoteCreated); + + preload(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::openCreateListDialog(bool _) +{ + // create the input dialog + InputDialog d = InputDialog(this, "Create a list", "New List", "Give a name to this list"); + auto res = d.exec(); + + // execute, ignore if not saved + if (res != QDialog::Accepted) { + return; + } + + QString newListName = d.getInput(); + ListService::getInstance()->create(newListName); +} + +void MainWindow::onListCreated(List value) +{ + QListWidgetItem* item = new QListWidgetItem(); + item->setText(value.getName()); + item->setData(Qt::UserRole, QVariant(value.getUUID())); + + this->ui->lists->addItem(item); +} + +void MainWindow::onListUpdate(List value) +{ + QListWidgetItem *item = nullptr; + for (int i = 0; i > ui->lists->count(); i++) { + item = ui->lists->item(i); + + if (item->data(Qt::UserRole).toUuid() == value.getUUID()) { + item->setText(value.getName()); + return; + } + } +} + +void MainWindow::onNoteCreated(Note value) +{ + QListWidgetItem* item = new QListWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsEditable); + item->setCheckState((value.isFinished() ? Qt::Checked : Qt::Unchecked)); + item->setText(value.getContent()); + item->setData(Qt::UserRole, QVariant(value.getUUID())); + + this->ui->notes->addItem(item); +} + +void MainWindow::onNoteUpdated(Note value) +{ + QListWidgetItem *item = ui->lists->currentItem(); + if (item == nullptr) { + qDebug() << "item null"; + return; + } + + if (item->data(Qt::UserRole).toUuid() != value.getParentUUID()) { + qDebug() << "item uuid not matching"; + return; + } + + for (int i = 0; i > ui->notes->count(); i++) { + item = ui->notes->item(i); + + if (item->data(Qt::UserRole).toUuid() == value.getUUID()) { + item->setCheckState((value.isFinished() ? Qt::Checked : Qt::Unchecked)); + item->setText(value.getContent()); + return; + } + } +} + +void MainWindow::onListSelected(int i) +{ + if (i == -1) { + ui->notes->clear(); + ui->newNoteEdit->setDisabled(true); + ui->saveNoteButton->setDisabled(true); + ui->notes->setDisabled(true); + return; + } + QListWidgetItem *item = ui->lists->item(i); + QVariant uuid = item->data(Qt::UserRole); + + QList notes = NoteService::getInstance()->getByList(uuid.toUuid()); + + ui->notes->clear(); + foreach (Note n, notes) { + onNoteCreated(n); + } + + ui->newNoteEdit->setEnabled(true); + ui->saveNoteButton->setEnabled(true); + ui->notes->setEnabled(true); +} + +void MainWindow::onSaveNoteButtonClicked(bool _) +{ + QString content = ui->newNoteEdit->text(); + if (content.isEmpty()) { + return; + } + + int li = ui->lists->currentRow(); + if (li == -1) { + return; + } + QListWidgetItem *item = ui->lists->item(li); + QVariant listUUID = item->data(Qt::UserRole); + + NoteService::getInstance()->create(listUUID.toUuid(), content); + ui->newNoteEdit->clear(); +} + +void MainWindow::onNoteChanged(QListWidgetItem *item) +{ + NoteService *service = NoteService::getInstance(); + QString content = item->text(); + + QVariant uuid = item->data(Qt::UserRole); + std::optional opt = service->getByUUID(uuid.toUuid()); + if (opt.has_value()) { + Note note = opt.value(); + + if (content.isEmpty()) { + service->remove(note.getParentUUID(), note.getUUID()); + } else { + service->update(note.getParentUUID(), note.getUUID(), content); + service->setFinishedValue(note.getParentUUID(), note.getUUID(), item->checkState() == Qt::Checked ? true : false); + } + } +} + +void MainWindow::preload() +{ + QList lists = ListService::getInstance()->getAll(); + + foreach (List l, lists) { + onListCreated(l); + } + + if (lists.count() > 0) { + ui->lists->setCurrentItem(ui->lists->item(0)); + } +} diff --git a/mainwindow.h b/src/gui/mainwindow.h similarity index 57% rename from mainwindow.h rename to src/gui/mainwindow.h index b12489a..a01533f 100644 --- a/mainwindow.h +++ b/src/gui/mainwindow.h @@ -2,7 +2,10 @@ #define MAINWINDOW_H #include -#include "list.h" +#include + +#include "src/obj/list.h" +#include "src/obj/note.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -21,9 +24,17 @@ public: private slots: void openCreateListDialog(bool); void onListCreated(List value); + void onListUpdate(List value); + void onNoteCreated(Note value); + void onNoteUpdated(Note value); + void onListSelected(int i); + void onSaveNoteButtonClicked(bool); + void onNoteChanged(QListWidgetItem*); private: Ui::MainWindow *ui; + void preload(); + }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/src/gui/mainwindow.ui similarity index 69% rename from mainwindow.ui rename to src/gui/mainwindow.ui index 363818d..6aa97d6 100644 --- a/mainwindow.ui +++ b/src/gui/mainwindow.ui @@ -17,7 +17,7 @@ - MainWindow + ToDo background-color: rgb(249, 255, 251); @@ -128,7 +128,7 @@ QListView::item:selected { } QListView::item:selected:!active { - background: none; + background-color: rgb(170, 216, 130); } QListView::item:selected:active { @@ -210,6 +210,9 @@ QListView::item:hover { + + New list + border: none; padding: 6px; @@ -231,12 +234,17 @@ padding: 6px; - - - false + + + 0 - - QListView { + + + + false + + + QListView { show-decoration-selected: 1; /* make the selection span the entire width of the view */ background-color: rgb(242, 242, 242); border: none; @@ -245,8 +253,9 @@ padding: 6px; } QListView::item { - margin: 2px; - padding: 8px; + background-color: rgb(231, 231, 231); + margin: 10px; + padding: 10px; border: none; } @@ -262,36 +271,90 @@ QListView::item:selected:!active { background: none; } -QListView::item:selected:active { - background-color: rgb(154, 154, 154); -} - QListView::item:hover { background-color: rgb(204, 204, 204); } - - - QFrame::Shadow::Plain - - - 0 - - - QAbstractItemView::EditTrigger::NoEditTriggers - - - false - - - Qt::DropAction::IgnoreAction - - - QAbstractItemView::SelectionMode::NoSelection - - - Qt::DropAction::IgnoreAction - - + + + QFrame::Shadow::Plain + + + 0 + + + QAbstractItemView::EditTrigger::AllEditTriggers + + + false + + + Qt::DropAction::IgnoreAction + + + QAbstractItemView::SelectionMode::MultiSelection + + + Qt::DropAction::IgnoreAction + + + + + + + + + false + + + margin: 20px; +background-color: rgb(243, 243, 243); +padding: 8px; +color: rgb(13, 13, 13); +border: none; + + + Enter your new note here + + + + + + + background-color: rgb(170, 216, 130); +color: rgb(242, 242, 242); +border: none; +padding: 8px; + + + + + + + + + false + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 10 + 20 + + + + + + + diff --git a/main.cpp b/src/main.cpp similarity index 94% rename from main.cpp rename to src/main.cpp index 43b7ce9..04e53e0 100644 --- a/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "mainwindow.h" +#include "src/gui/mainwindow.h" #include #include diff --git a/list.cpp b/src/obj/list.cpp similarity index 100% rename from list.cpp rename to src/obj/list.cpp diff --git a/list.h b/src/obj/list.h similarity index 100% rename from list.h rename to src/obj/list.h diff --git a/src/obj/note.cpp b/src/obj/note.cpp new file mode 100644 index 0000000..08226f1 --- /dev/null +++ b/src/obj/note.cpp @@ -0,0 +1,52 @@ +#include "note.h" + +Note::Note(QUuid listParent, QUuid noteID, QString content, bool finished) +{ + this->id = noteID; + this->parent = listParent; + this->content = content; + this->finished = finished; +} + +Note::Note(QUuid listParent, QString content) +{ + this->id = QUuid::createUuid(); + this->parent = listParent; + this->content = content; + this->finished = false; +} + +QUuid Note::getUUID() +{ + return this->id; +} + +QUuid Note::getParentUUID() +{ + return this->parent; +} + +QString Note::getContent() +{ + return this->content; +} + +bool Note::isFinished() +{ + return this->finished; +} + +void Note::setContent(QString content) +{ + this->content = content; +} + +void Note::setParentUUID(QUuid parent) +{ + this->parent = parent; +} + +void Note::setFinished(bool value) +{ + this->finished = value; +} diff --git a/src/obj/note.h b/src/obj/note.h new file mode 100644 index 0000000..a140365 --- /dev/null +++ b/src/obj/note.h @@ -0,0 +1,30 @@ +#ifndef NOTE_H +#define NOTE_H + +#include +#include + +class Note +{ +public: + Note() {}; + Note(QUuid listParent, QUuid noteID, QString content, bool finished); + Note(QUuid listParent, QString content); + + QUuid getUUID(); + QUuid getParentUUID(); + QString getContent(); + bool isFinished(); + + void setContent(QString content); + void setParentUUID(QUuid parent); + void setFinished(bool value); +private: + QUuid id; + QUuid parent; + QString content; + bool finished; + +}; + +#endif // NOTE_H