add todos
This commit is contained in:
@@ -13,11 +13,6 @@ Popover::Popover(std::string icon, std::string name) {
|
||||
popover = new Gtk::Popover();
|
||||
popover->set_parent(*this);
|
||||
popover->set_autohide(true);
|
||||
|
||||
popover->signal_closed().connect([this]() {
|
||||
this->add_css_class("minimized");
|
||||
this->remove_css_class("restored");
|
||||
});
|
||||
}
|
||||
|
||||
Popover::~Popover() {
|
||||
@@ -28,8 +23,6 @@ void Popover::on_toggle_window() {
|
||||
if (popover->get_visible()) {
|
||||
popover->popdown();
|
||||
} else {
|
||||
this->remove_css_class("minimized");
|
||||
this->add_css_class("restored");
|
||||
popover->popup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
TodoEntry::TodoEntry(int id, std::string text, sigc::signal<void(int)> signal_dismissed)
|
||||
: Gtk::Box(Gtk::Orientation::HORIZONTAL) {
|
||||
this->id = id;
|
||||
this->text = text;
|
||||
this->signal_dismissed = signal_dismissed;
|
||||
|
||||
|
||||
|
||||
165
src/services/sqliteTodoAdapter.cpp
Normal file
165
src/services/sqliteTodoAdapter.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "services/todoAdapter.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sqlite3.h>
|
||||
|
||||
namespace {
|
||||
std::string getDbPath() {
|
||||
const char *homeDir = getenv("HOME");
|
||||
if (!homeDir) {
|
||||
return "todos.db";
|
||||
}
|
||||
|
||||
std::string path = std::string(homeDir) + "/.config/bar";
|
||||
if (!std::filesystem::exists(path)) {
|
||||
std::filesystem::create_directories(path);
|
||||
}
|
||||
|
||||
return path + "/todos.db";
|
||||
}
|
||||
|
||||
class SqliteTodoAdapter final : public ITodoAdapter {
|
||||
public:
|
||||
~SqliteTodoAdapter() override {
|
||||
if (db_) {
|
||||
sqlite3_close(db_);
|
||||
db_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool init() override {
|
||||
std::string dbPath = getDbPath();
|
||||
std::cout << "Opening database at: " << dbPath << std::endl;
|
||||
|
||||
int rc = sqlite3_open(dbPath.c_str(), &db_);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "Can't open database: " << sqlite3_errmsg(db_) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *sql = "CREATE TABLE IF NOT EXISTS todos ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"text TEXT NOT NULL);";
|
||||
char *zErrMsg = nullptr;
|
||||
rc = sqlite3_exec(db_, sql, nullptr, nullptr, &zErrMsg);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL error (create table): " << (zErrMsg ? zErrMsg : "") << std::endl;
|
||||
sqlite3_free(zErrMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<TodoRecord> listTodos() override {
|
||||
std::vector<TodoRecord> results;
|
||||
if (!db_) {
|
||||
return results;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
const char *sql = "SELECT id, text FROM todos";
|
||||
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "Failed to fetch data: " << sqlite3_errmsg(db_) << std::endl;
|
||||
return results;
|
||||
}
|
||||
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
int id = sqlite3_column_int(stmt, 0);
|
||||
const unsigned char *txt = sqlite3_column_text(stmt, 1);
|
||||
results.push_back({id, txt ? reinterpret_cast<const char *>(txt) : ""});
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return results;
|
||||
}
|
||||
|
||||
int addTodo(const std::string &text) override {
|
||||
if (!db_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
const char *sql = "INSERT INTO todos (text) VALUES (?1);";
|
||||
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL prepare error (insert): " << sqlite3_errmsg(db_) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, text.c_str(), -1, SQLITE_TRANSIENT);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
std::cerr << "SQL step error (insert): " << sqlite3_errmsg(db_) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return static_cast<int>(sqlite3_last_insert_rowid(db_));
|
||||
}
|
||||
|
||||
bool removeTodo(int id) override {
|
||||
if (!db_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
const char *sql = "DELETE FROM todos WHERE id = ?1;";
|
||||
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL prepare error (delete): " << sqlite3_errmsg(db_) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_bind_int(stmt, 1, id);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
std::cerr << "SQL step error (delete): " << sqlite3_errmsg(db_) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateTodo(int id, const std::string &text) override {
|
||||
if (!db_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
const char *sql = "UPDATE todos SET text = ?1 WHERE id = ?2;";
|
||||
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL prepare error (update): " << sqlite3_errmsg(db_) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, text.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(stmt, 2, id);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
std::cerr << "SQL step error (update): " << sqlite3_errmsg(db_) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
sqlite3 *db_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Factory kept local to this TU for now; TodoService uses it.
|
||||
std::unique_ptr<ITodoAdapter> makeSqliteTodoAdapter() {
|
||||
return std::make_unique<SqliteTodoAdapter>();
|
||||
}
|
||||
@@ -4,27 +4,34 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
std::unique_ptr<ITodoAdapter> makeSqliteTodoAdapter();
|
||||
|
||||
TodoService::TodoService(sigc::signal<void()> refreshSignal) {
|
||||
this->refreshSignal = refreshSignal;
|
||||
this->adapter = makeSqliteTodoAdapter();
|
||||
this->init();
|
||||
}
|
||||
|
||||
TodoService::~TodoService() {}
|
||||
TodoService::~TodoService() {
|
||||
this->adapter.reset();
|
||||
}
|
||||
|
||||
std::map<int, TodoEntry *> TodoService::getTodos() {
|
||||
return this->todos;
|
||||
}
|
||||
|
||||
void TodoService::init() {
|
||||
std::vector<std::string> items = {
|
||||
"Buy groceries",
|
||||
"Finish the report",
|
||||
"Call Alice",
|
||||
"Schedule dentist appointment"};
|
||||
|
||||
for (auto item : items) {
|
||||
this->addTodo(item, false);
|
||||
if (!this->adapter) {
|
||||
std::cerr << "Todo adapter not set" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->adapter->init()) {
|
||||
std::cerr << "Todo adapter init failed" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
this->load();
|
||||
}
|
||||
|
||||
void TodoService::removeTodo(int id) {
|
||||
@@ -33,24 +40,70 @@ void TodoService::removeTodo(int id) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
todos.erase(id);
|
||||
if (this->adapter) {
|
||||
this->adapter->removeTodo(id);
|
||||
}
|
||||
|
||||
todos.erase(id);
|
||||
this->refreshSignal.emit();
|
||||
}
|
||||
|
||||
TodoEntry *TodoService::addTodo(std::string text, bool emitSignal) {
|
||||
TodoEntry *TodoService::addTodo(std::string text, bool emitSignal, bool persist) {
|
||||
int id = nextId;
|
||||
|
||||
if (persist) {
|
||||
if (!this->adapter) {
|
||||
return nullptr;
|
||||
}
|
||||
int newId = this->adapter->addTodo(text);
|
||||
if (newId < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
id = newId;
|
||||
}
|
||||
|
||||
auto signal = sigc::signal<void(int)>();
|
||||
signal.connect(sigc::mem_fun(*this, &TodoService::removeTodo));
|
||||
TodoEntry *todo = Gtk::make_managed<TodoEntry>(nextId, text, signal);
|
||||
TodoEntry *todo = Gtk::make_managed<TodoEntry>(id, text, signal);
|
||||
|
||||
todos[nextId] = todo;
|
||||
todos[id] = todo;
|
||||
|
||||
nextId++;
|
||||
if (id >= nextId) {
|
||||
nextId = id + 1;
|
||||
}
|
||||
|
||||
if (emitSignal) {
|
||||
this->refreshSignal.emit();
|
||||
}
|
||||
|
||||
return todo;
|
||||
}
|
||||
|
||||
void TodoService::updateTodo(int id, std::string text) {}
|
||||
|
||||
void TodoService::load() {
|
||||
if (!this->adapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Loading todos from database..." << std::endl;
|
||||
auto rows = this->adapter->listTodos();
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (const auto &row : rows) {
|
||||
count++;
|
||||
int id = row.id;
|
||||
std::string text = row.text;
|
||||
|
||||
auto signal = sigc::signal<void(int)>();
|
||||
signal.connect(sigc::mem_fun(*this, &TodoService::removeTodo));
|
||||
TodoEntry *todo = Gtk::make_managed<TodoEntry>(id, text, signal);
|
||||
|
||||
todos[id] = todo;
|
||||
if (id >= nextId) {
|
||||
nextId = id + 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Loaded " << count << " todos." << std::endl;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ TodoPopover::TodoPopover(std::string icon, std::string title) : Popover(icon, ti
|
||||
std::string text = entry->get_text();
|
||||
if (!text.empty()) {
|
||||
this->todoService->addTodo(text);
|
||||
|
||||
entry->set_text("");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user