diff --git a/.clang-format b/.clang-format index 722a798..37d4bf7 100644 --- a/.clang-format +++ b/.clang-format @@ -7,3 +7,5 @@ SeparateDefinitionBlocks: Always AllowShortBlocksOnASingleLine: Never AllowShortIfStatementsOnASingleLine: false AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: Empty +AlignAfterOpenBracket: BlockIndent diff --git a/CMakeLists.txt b/CMakeLists.txt index f2dadf5..81bcb90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,8 +38,10 @@ qt_add_qml_module(mirai src/qml/AppButton.qml src/qml/AppCheckbox.qml src/qml/AppText.qml + src/qml/AppComboBox.qml src/qml/TaskItem.qml src/qml/forms/TaskForm.qml + src/qml/forms/FilesForm.qml src/qml/components/TabSelector.qml src/qml/components/Tag.qml src/qml/styles/MiraiColorPalette.qml diff --git a/src/Backend.cpp b/src/Backend.cpp index dbb7841..999d53c 100644 --- a/src/Backend.cpp +++ b/src/Backend.cpp @@ -5,8 +5,15 @@ */ #include "Backend.h" +#include "TaskItem.h" +#include "core/TaskItem.h" #include "core/TodoMd.h" +#include #include +#include +#include +#include +#include Backend::Backend() { @@ -33,8 +40,9 @@ Backend::Backend() qWarning() << "config.json should contains a 'files' string array"; exit(1); } - QString filePath = jsonFilesPath.toArray()[0].toString(); - mirai.loadFile(filePath.toStdString()); + for (const QJsonValueRef &filePath : jsonFilesPath.toArray()) { + mirai.loadFile(filePath.toString().toStdString()); + } view = mirai.getTasks(); rebuildQMLTasksList(); } @@ -47,10 +55,18 @@ void Backend::addTodo(QString newTodo, QString date) emit tasksChanged(); } -void Backend::addTodoFromRawFormat(QString text, QString date) +void Backend::addTodoFromRawFormat(QString filePath, QString text, QString date) { - mirai.addTask(mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString())); + auto file = mirai.getFileByPath(filePath.toStdString()); + if (!file.has_value()) { + qWarning() << "File path '" << filePath << "' doesn't exist" << Qt::endl; + return; + } + file.value().get().addTask( + mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString()) + ); mirai.save(); + view.lock()->update(); rebuildQMLTasksList(); emit tasksChanged(); } @@ -69,6 +85,20 @@ void Backend::removeTagFilter(QString tag) emit tasksChanged(); } +void Backend::addFileFilter(QString fileName) +{ + view.lock()->addFileFilter(fileName.toStdString()); + rebuildQMLTasksList(); + emit tasksChanged(); +} + +void Backend::removeFileFilter(QString fileName) +{ + view.lock()->removeFileFilter(fileName.toStdString()); + rebuildQMLTasksList(); + emit tasksChanged(); +} + void Backend::removeFilters() { view.lock()->removeFilters(); @@ -79,6 +109,7 @@ void Backend::removeFilters() void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date) { QMLTaskItem &taskItem = QMLTasks[todoIndex]; + taskItem.taskItem->setText(taskItem.taskItem->getText()); *(taskItem.taskItem) = mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString()); mirai.save(); @@ -89,7 +120,6 @@ void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date) void Backend::updateTodo(int todoIndex, QString state, QString text, QString date) { QMLTaskItem &taskItem = QMLTasks[todoIndex]; - taskItem.taskItem->state = state == "TODO" ? mirai::TODO : mirai::DONE; if (state == "DONE") { taskItem.taskItem->markAsDone(); } else if (state == "TODO") { @@ -120,6 +150,12 @@ void Backend::hideCompletedTasks(bool shouldHide) void Backend::rebuildQMLTasksList() { + // TODO MOVE TO ANOTHER FILE + QMLTasksFiles.clear(); + for (auto &file : mirai.getFiles()) { + QMLTasksFiles.push_back({.tasksFile = file.get()}); + } + // ---- QMLTasks.clear(); std::string lastDate = ""; std::time_t t = std::time(nullptr); @@ -133,8 +169,8 @@ void Backend::rebuildQMLTasksList() continue; } bool shouldShowDate = false; - if (lastDate != task.date) { - lastDate = task.date; + if (lastDate != task.getDate()) { + lastDate = task.getDate(); shouldShowDate = true; } QList qStringTags; @@ -142,7 +178,8 @@ void Backend::rebuildQMLTasksList() qStringTags.push_back(QString::fromStdString(tag)); } QMLTasks.push_back( - {.taskItem = &task, .shouldShowDate = shouldShowDate, .tags = qStringTags}); + {.taskItem = &task, .shouldShowDate = shouldShowDate, .tags = qStringTags} + ); } QMLTags.clear(); @@ -154,6 +191,11 @@ void Backend::rebuildQMLTasksList() for (auto &activeTagFilter : view.lock()->getActiveTagsFilter()) { QMLActiveTagsFilter.push_back(QString::fromStdString(activeTagFilter)); } + + QMLActiveFilesFilter.clear(); + for (auto &activeFileFilter : view.lock()->getActiveFilesFilter()) { + QMLActiveFilesFilter.push_back(QString::fromStdString(activeFileFilter)); + } } QVariant Backend::getTasks() @@ -171,7 +213,17 @@ QVariant Backend::getActiveTagsFilter() return QVariant::fromValue(QMLActiveTagsFilter); } +QVariant Backend::getActiveFilesFilter() +{ + return QVariant::fromValue(QMLActiveFilesFilter); +} + bool Backend::shouldHideCompletedTasks() { return shouldHideCompletedTasks_; } + +QVariant Backend::getFiles() +{ + return QVariant::fromValue(QMLTasksFiles); +} diff --git a/src/Backend.h b/src/Backend.h index 4bc5f4e..aa04e8f 100644 --- a/src/Backend.h +++ b/src/Backend.h @@ -30,8 +30,10 @@ class Backend : public QObject Q_OBJECT QML_ELEMENT Q_PROPERTY(QVariant tasks READ getTasks NOTIFY tasksChanged) + Q_PROPERTY(QVariant files READ getFiles NOTIFY filesChanged) Q_PROPERTY(QVariant tags READ getTags NOTIFY tasksChanged) Q_PROPERTY(QVariant activeTagsFilter READ getActiveTagsFilter NOTIFY tasksChanged) + Q_PROPERTY(QVariant activeFilesFilter READ getActiveFilesFilter NOTIFY tasksChanged) Q_PROPERTY(bool shouldHideCompletedTasks READ shouldHideCompletedTasks WRITE hideCompletedTasks NOTIFY tasksChanged) @@ -39,9 +41,11 @@ class Backend : public QObject Backend(); Q_INVOKABLE void addTodo(QString newTodo, QString date); - Q_INVOKABLE void addTodoFromRawFormat(QString text, QString date); + Q_INVOKABLE void addTodoFromRawFormat(QString filePath, QString text, QString date); Q_INVOKABLE void addTagFilter(QString tag); Q_INVOKABLE void removeTagFilter(QString tag); + Q_INVOKABLE void addFileFilter(QString fileName); + Q_INVOKABLE void removeFileFilter(QString fileName); Q_INVOKABLE void removeFilters(); Q_INVOKABLE void updateTodoFromRawFormat(int todoIndex, QString text, QString date); Q_INVOKABLE void updateTodo(int todoIndex, QString state, QString text, QString date); @@ -52,22 +56,27 @@ class Backend : public QObject void rebuildQMLTasksList(); QVariant getTasks(); + QVariant getFiles(); QVariant getTags(); QVariant getActiveTagsFilter(); + QVariant getActiveFilesFilter(); bool shouldHideCompletedTasks(); mirai::Mirai mirai; std::weak_ptr view; QList QMLTasks; + QList QMLTasksFiles; QList QMLTags; QList QMLActiveTagsFilter; + QList QMLActiveFilesFilter; bool shouldHideCompletedTasks_ = true; signals: void tasksChanged(); + void filesChanged(); }; #endif diff --git a/src/TaskItem.cpp b/src/TaskItem.cpp index 80bd79f..f7e8fe3 100644 --- a/src/TaskItem.cpp +++ b/src/TaskItem.cpp @@ -7,6 +7,16 @@ #include "TaskItem.h" #include "core/TodoMd.h" +QString QMLTasksFile::getName() +{ + return QString::fromStdString(tasksFile->getName()); +} + +QString QMLTasksFile::getPath() +{ + return QString::fromStdString(tasksFile->getPath()); +} + QString QMLTaskItem::getRawFormat() { return QString::fromStdString(mirai::TodoMdFormat::TaskToString(*taskItem)); diff --git a/src/TaskItem.h b/src/TaskItem.h index 90a3773..0f13c3f 100644 --- a/src/TaskItem.h +++ b/src/TaskItem.h @@ -13,6 +13,21 @@ #include #include "core/TaskItem.h" +#include "core/TasksFile.h" + +struct QMLTasksFile { + + Q_GADGET + Q_PROPERTY(QString name READ getName) + Q_PROPERTY(QString path READ getPath) + QML_VALUE_TYPE(tasksFile) + + public: + QString getName(); + QString getPath(); + + mirai::TasksFile *tasksFile; +}; struct QMLTaskItem { diff --git a/src/core/Mirai.cpp b/src/core/Mirai.cpp index 66ff748..fa64d38 100644 --- a/src/core/Mirai.cpp +++ b/src/core/Mirai.cpp @@ -6,7 +6,14 @@ #include "Mirai.h" #include "TaskItem.h" -#include "cpp-utils/vector.h" +#include "core/TasksFile.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include namespace mirai { @@ -16,10 +23,12 @@ void Mirai::loadFile(const std::string &path) auto tasksFile = TodoMdFormat::readFile(path); files->push_back(std::move(tasksFile)); tags.clear(); - for (auto &task : (*files)[0].getTasks()) { - for (auto &tag : task->getTags()) { - if (!vectorUtils::contains(tags, tag)) { - tags.push_back(tag); + for (auto &file : *files) { + for (auto &task : file->getTasks()) { + for (auto &tag : task->getTags()) { + if (!vectorUtils::contains(tags, tag)) { + tags.push_back(tag); + } } } } @@ -28,13 +37,16 @@ void Mirai::loadFile(const std::string &path) void Mirai::save() { for (auto &file : *files) { - TodoMdFormat::writeFile(file); + if (file->isDirty()) { + TodoMdFormat::writeFile(*file); + file->setDirty(false); + } } } -void Mirai::addTask(TaskItem taskItem) +void Mirai::addTask(TaskItemData taskItem) { - (*files)[0].addTask(taskItem); + (*files)[0]->addTask(taskItem); for (auto &view : views) { view->update(); } @@ -46,7 +58,7 @@ void Mirai::addTask(std::string text, std::string date) /*std::tm tm = *std::localtime(&t);*/ /*std::stringstream ssCreationDate;*/ /*ssCreationDate << std::put_time(&tm, "%Y-%m-%d");*/ - (*files)[0].addTask(text, date); + (*files)[0]->addTask(text, date); for (auto &view : views) { view->update(); } @@ -54,12 +66,31 @@ void Mirai::addTask(std::string text, std::string date) void Mirai::removeTask(const TaskItem *taskItem) { - (*files)[0].removeTask(taskItem); + for (auto &file : *files) { + file->removeTask(taskItem); + } for (auto &view : views) { view->update(); } } +std::vector> &Mirai::getFiles() +{ + return *files.get(); +} + +std::optional> Mirai::getFileByPath(const std::string &path) +{ + auto fileIterator = std::ranges::find_if(*files, [&](const std::unique_ptr &file) { + return file->getPath() == path; + }); + + if (fileIterator == files->end()) { + return std::nullopt; + } + return *(fileIterator->get()); +} + std::weak_ptr Mirai::getTasks() { auto view = std::make_shared(files); @@ -71,4 +102,5 @@ const std::vector &Mirai::getTags() { return tags; } + } // namespace mirai diff --git a/src/core/Mirai.h b/src/core/Mirai.h index ea00fd4..23c3c5a 100644 --- a/src/core/Mirai.h +++ b/src/core/Mirai.h @@ -12,7 +12,10 @@ #include "TasksView.h" #include "TodoMd.h" #include +#include #include +#include +#include namespace mirai { @@ -23,10 +26,13 @@ class Mirai public: void loadFile(const std::string &path); void save(); - void addTask(TaskItem taskItem); + void addTask(TaskItemData taskItem); void addTask(std::string text, std::string date); void removeTask(const TaskItem *taskItem); + void addFile(TasksFileConfig config); + std::optional> getFileByPath(const std::string &path); + std::vector> &getFiles(); std::weak_ptr getTasks(); const std::vector &getTags(); @@ -34,7 +40,8 @@ class Mirai // The `TasksFile`s are shared to the views, their lifetime can outlive // this (Mirai) object // because we can't control if the caller will keep the main object alive. - std::shared_ptr> files = std::make_shared>(); + std::shared_ptr>> files = + std::make_shared>>(); // We keep a vector of shared_ptr because we need the ref counting mechanism to know // if we have to remove the view (not used) or tell the view to update() on diff --git a/src/core/TaskItem.cpp b/src/core/TaskItem.cpp index e4931df..919c818 100644 --- a/src/core/TaskItem.cpp +++ b/src/core/TaskItem.cpp @@ -5,68 +5,86 @@ */ #include "TaskItem.h" -#include "cpp-utils/vector.h" +#include "TasksFile.h" +#include "core/utils.h" +#include namespace mirai { +TaskItem::TaskItem(TasksFile *parent, TaskItemData data) : parent(parent), data(data) +{ +} + void TaskItem::markAsDone() { - state = DONE; + data.state = DONE; + onChange(); } void TaskItem::markAsUndone() { - state = TODO; + data.state = TODO; + onChange(); } void TaskItem::setDate(const std::string &date) { - this->date = date; + this->data.date = date; + onChange(); } void TaskItem::setText(const std::string &text) { - this->text = text; + this->data.text = text; + onChange(); } const std::string &TaskItem::getText() const { - return text; + return data.text; } const TaskItemState &TaskItem::getState() const { - return state; + return data.state; } const std::string &TaskItem::getDate() const { - return date; + return data.date; } const std::string &TaskItem::getStartTime() const { - return startTime; + return data.startTime; } const std::string &TaskItem::getEndTime() const { - return endTime; + return data.endTime; } const Tags &TaskItem::getTags() const { - return tags; + return data.tags; } bool TaskItem::hasDate() const { - return isDate(date); + return isDate(data.date); } bool TaskItem::hasTag(const std::string &tag) const { - return vectorUtils::contains(tags, tag); + return vectorUtils::contains(data.tags, tag); } + +void TaskItem::onChange() +{ + if (parent) { + parent->setDirty(true); + } +} + } // namespace mirai diff --git a/src/core/TaskItem.h b/src/core/TaskItem.h index b963d5e..2e61d61 100644 --- a/src/core/TaskItem.h +++ b/src/core/TaskItem.h @@ -8,24 +8,39 @@ #define MIRAI_TASKITEM_H #include "using.h" -#include "utils.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include namespace mirai { enum TaskItemState { TODO, DONE }; -struct TaskItem { +struct TaskItemData { + std::string text; + TaskItemState state; + std::string date; + std::string startTime; + std::string endTime; + Tags tags; +}; + +class TasksFile; + +class TaskItem +{ + friend TasksFile; + + private: public: + TaskItem(TasksFile *parent, const TaskItemData data); + + TaskItem &operator=(const TaskItemData &newData) + { + data = newData; + onChange(); + return *this; + }; + void markAsDone(); void markAsUndone(); @@ -43,12 +58,11 @@ struct TaskItem { bool hasTag(const std::string &tag) const; bool hasDate() const; - std::string text; - TaskItemState state; - std::string date; - std::string startTime; - std::string endTime; - Tags tags; + private: + void onChange(); + + TasksFile *parent; + TaskItemData data; }; } // namespace mirai diff --git a/src/core/TasksFile.cpp b/src/core/TasksFile.cpp index 20d33e9..27737cb 100644 --- a/src/core/TasksFile.cpp +++ b/src/core/TasksFile.cpp @@ -6,7 +6,11 @@ #include "TasksFile.h" #include "TaskItem.h" +#include +#include +#include #include +#include namespace mirai { @@ -14,11 +18,28 @@ namespace mirai TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path) { for (const auto &task : params.tasks) { - tasks.push_back(std::make_unique(task)); + auto taskPtr = std::make_unique(task); + tasks.push_back(std::move(taskPtr)); } sortByDate(); } +void TasksFile::onTaskChanged(TaskItem &taskItem) +{ + std::cout << "THIS PTR: " << this << std::endl; + setDirty(true); +} + +void TasksFile::setDirty(bool shouldBeDirty) +{ + isDirty_ = shouldBeDirty; +} + +bool TasksFile::isDirty() const +{ + return isDirty_; +} + const std::string &TasksFile::getName() const { return name; @@ -34,38 +55,47 @@ TasksPtrs &TasksFile::getTasks() return tasks; } -void TasksFile::addTask(TaskItem taskItem) +void TasksFile::addTask(TaskItemData taskItem) { - tasks.push_back(std::make_unique(taskItem)); + tasks.push_back(std::make_unique(this, taskItem)); + setDirty(true); sortByDate(); } void TasksFile::addTask(std::string text, std::string date) { auto newTask = std::make_unique( - TaskItem{.text = text, .state = TODO, .date = date == "" ? "No date" : date}); + TaskItem{this, {.text = text, .state = TODO, .date = date == "" ? "No date" : date}} + ); tasks.push_back(std::move(newTask)); sortByDate(); } void TasksFile::removeTask(const TaskItem *taskToRemove) { - tasks.erase(std::remove_if(tasks.begin(), tasks.end(), - [&](const std::unique_ptr &taskInFilter) { - return taskInFilter.get() == taskToRemove; - })); + auto findFunction = [&](const std::unique_ptr &taskInFilter) { + return taskInFilter.get() == taskToRemove; + }; + auto taskToDelete = std::remove_if(tasks.begin(), tasks.end(), findFunction); + if (taskToDelete == tasks.end()) { + return; + } + tasks.erase(taskToDelete); + setDirty(true); } void TasksFile::sortByDate() { - std::sort(tasks.begin(), tasks.end(), - [](const std::unique_ptr &t1, const std::unique_ptr &t2) { - if (t1->hasDate() && !t2->hasDate()) { - return true; - } else if (!t1->hasDate() && t2->hasDate()) { - return false; - } - return t1->date < t2->date; - }); + std::sort( + tasks.begin(), tasks.end(), + [](const std::unique_ptr &t1, const std::unique_ptr &t2) { + if (t1->hasDate() && !t2->hasDate()) { + return true; + } else if (!t1->hasDate() && t2->hasDate()) { + return false; + } + return t1->getDate() < t2->getDate(); + } + ); } } // namespace mirai diff --git a/src/core/TasksFile.h b/src/core/TasksFile.h index a8ebf96..bd4ba8a 100644 --- a/src/core/TasksFile.h +++ b/src/core/TasksFile.h @@ -8,18 +8,7 @@ #define MIRAI_TASKSFILE_H #include "TaskItem.h" -#include "using.h" -#include "utils.h" -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include #include @@ -28,6 +17,11 @@ namespace mirai using TasksPtrs = std::vector>; +struct TasksFileConfig { + std::string name; + std::string path; +}; + struct TasksFileConstructor { const std::string &name; const std::string &path; @@ -38,10 +32,11 @@ class TasksFile { public: + TasksFile(const TasksFile &) = delete; + TasksFile &operator=(const TasksFile &) = delete; TasksFile(TasksFileConstructor params); - void addTask(std::string text, std::string date); - void addTask(TaskItem TaskItem); + void addTask(TaskItemData TaskItem); void removeTask(const TaskItem *taskToRemove); void sortByDate(); @@ -49,10 +44,16 @@ class TasksFile const std::string &getPath() const; TasksPtrs &getTasks(); + void setDirty(bool shouldBeDirty); + bool isDirty() const; + private: + void onTaskChanged(TaskItem &); + std::string name; std::string path; TasksPtrs tasks; + bool isDirty_ = false; }; } // namespace mirai diff --git a/src/core/TasksView.cpp b/src/core/TasksView.cpp index 198b899..c0e6944 100644 --- a/src/core/TasksView.cpp +++ b/src/core/TasksView.cpp @@ -5,17 +5,18 @@ */ #include "TasksView.h" -#include "cpp-utils/vector.h" +#include "utils.h" #include #include #include +#include #include #include namespace mirai { -TasksView::TasksView(std::shared_ptr> files) : files(files) +TasksView::TasksView(std::shared_ptr>> files) : files(files) { update(); } @@ -34,7 +35,10 @@ void TasksView::update() { tasksToShow.clear(); for (auto &file : *files) { - for (auto &task : file.getTasks()) { + if (filesFilter.size() > 0 && !vectorUtils::contains(filesFilter, file->getName())) { + continue; + } + for (auto &task : file->getTasks()) { if (tagsFilter.size() > 0 && !vectorUtils::containsAll(tagsFilter, task->getTags())) { continue; } @@ -51,8 +55,28 @@ void TasksView::addTagFilter(const std::string &tag) void TasksView::removeTagFilter(const std::string &tag) { - tagsFilter.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), - [&](const auto &tagInFilter) { return tag == tagInFilter; })); + tagsFilter + .erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), [&](const auto &tagInFilter) { + return tag == tagInFilter; + })); + + update(); +} + +void TasksView::addFileFilter(const std::string &fileName) +{ + filesFilter.push_back(fileName); + update(); +} + +void TasksView::removeFileFilter(const std::string &fileName) +{ + filesFilter.erase(std::remove_if( + filesFilter.begin(), filesFilter.end(), + [&](const auto &fileInFilter) { + return fileName == fileInFilter; + } + )); update(); } @@ -66,4 +90,9 @@ const std::vector &TasksView::getActiveTagsFilter() { return tagsFilter; } + +const std::vector &TasksView::getActiveFilesFilter() +{ + return filesFilter; +} } // namespace mirai diff --git a/src/core/TasksView.h b/src/core/TasksView.h index 17bb646..3b67144 100644 --- a/src/core/TasksView.h +++ b/src/core/TasksView.h @@ -19,7 +19,7 @@ class TasksView { public: - TasksView(std::shared_ptr> files); + TasksView(std::shared_ptr>> files); TaskItem &operator[](int index); @@ -27,13 +27,17 @@ class TasksView void update(); void addTagFilter(const std::string &tag); void removeTagFilter(const std::string &tag); + void addFileFilter(const std::string &fileName); + void removeFileFilter(const std::string &fileName); void removeFilters(); const Tags &getActiveTagsFilter(); + const std::vector &getActiveFilesFilter(); private: - std::shared_ptr> files; + std::shared_ptr>> files; std::vector tasksToShow; Tags tagsFilter; + std::vector filesFilter; }; } // namespace mirai #endif diff --git a/src/core/TodoMd.cpp b/src/core/TodoMd.cpp index 38e426c..173dc83 100644 --- a/src/core/TodoMd.cpp +++ b/src/core/TodoMd.cpp @@ -6,12 +6,16 @@ #include "TodoMd.h" #include "TaskItem.h" -#include "cpp-utils/vector.h" +#include "core/TasksFile.h" +#include "utils.h" +#include +#include +#include namespace mirai { -TasksFile TodoMdFormat::readFile(const std::string &path) +std::unique_ptr TodoMdFormat::readFile(const std::string &path) { std::ifstream file(path); if (!file.is_open()) { @@ -27,18 +31,22 @@ TasksFile TodoMdFormat::readFile(const std::string &path) std::string name = line.substr(2); std::string currentDate = ""; + auto tasksFile = std::make_unique( + TasksFileConstructor{.name = name, .path = path, .tasks = taskItems} + ); + while (std::getline(file, line)) { if (line.substr(0, 3) == "## ") { currentDate = line.substr(3); } else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") { - TaskItem taskItem = StringToTask(line, currentDate); - taskItem.setDate(currentDate); - taskItems.push_back(taskItem); + TaskItemData taskItemData = StringToTask(line, currentDate); + taskItemData.date = currentDate; + tasksFile->addTask(taskItemData); } } file.close(); - TasksFile tasks({.name = name, .path = path, .tasks = taskItems}); - return tasks; + tasksFile->setDirty(false); + return tasksFile; } Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata) @@ -67,9 +75,9 @@ void TodoMdFormat::writeFile(TasksFile &tasks) file << "# " << tasks.getName() << "\n"; for (const auto &task : tasks.getTasks()) { - if (currentDate != task->date) { - currentDate = task->date; - file << "\n## " << (task->date != "" ? task->date : "No date") << "\n\n"; + if (currentDate != task->getDate()) { + currentDate = task->getDate(); + file << "\n## " << (task->getDate() != "" ? task->getDate() : "No date") << "\n\n"; } file << TaskToString(*task) << '\n'; } @@ -78,16 +86,17 @@ void TodoMdFormat::writeFile(TasksFile &tasks) std::string TodoMdFormat::fieldWithSpace(const std::string &field) { - if (field.length() == 0) + if (field.length() == 0) { return ""; + } return (field + " "); } -TaskItem TodoMdFormat::StringToTask(const std::string &str, const std::string &date) +TaskItemData TodoMdFormat::StringToTask(const std::string &str, const std::string &date) { std::smatch matches; - std::regex regex( - "- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?"); + std::regex regex("- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?" + ); std::regex_match(str, matches, regex); /*std::cout << "line " << str << std::endl;*/ @@ -102,12 +111,14 @@ TaskItem TodoMdFormat::StringToTask(const std::string &str, const std::string &d std::string text = stringUtils::trim(matches[5]); - TaskItem taskItem = {.text = text, - .state = str.substr(0, 5) == "- [X]" ? DONE : TODO, - .date = date, - .startTime = matches[3], - .endTime = matches[4], - .tags = extractTagsFromMetadata(matches[7])}; + TaskItemData taskItem{ + .text = text, + .state = str.substr(0, 5) == "- [X]" ? DONE : TODO, + .date = date, + .startTime = matches[3], + .endTime = matches[4], + .tags = extractTagsFromMetadata(matches[7]) + }; return taskItem; } diff --git a/src/core/TodoMd.h b/src/core/TodoMd.h index 3e0d206..89c76bd 100644 --- a/src/core/TodoMd.h +++ b/src/core/TodoMd.h @@ -9,16 +9,7 @@ #include "TaskItem.h" #include "TasksFile.h" -#include "utils.h" -#include -#include -#include -#include -#include -#include -#include #include -#include namespace mirai { @@ -26,11 +17,11 @@ namespace mirai class TodoMdFormat { public: - static TasksFile readFile(const std::string &path); + static std::unique_ptr readFile(const std::string &path); static void writeFile(TasksFile &tasks); static std::string TaskToString(const TaskItem &task); - static TaskItem StringToTask(const std::string &str, const std::string &date); + static TaskItemData StringToTask(const std::string &str, const std::string &date); private: static std::string fieldWithSpace(const std::string &field); diff --git a/src/main.cpp b/src/main.cpp index ec81fb8..a9cbf3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,11 @@ int main(int argc, char *argv[]) const QUrl url(u"qrc:/qt/qml/Mirai/src/qml/Main.qml"_qs); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, - []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + []() { + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection + ); engine.load(url); return app.exec(); diff --git a/src/qml/AppComboBox.qml b/src/qml/AppComboBox.qml new file mode 100644 index 0000000..ca321d6 --- /dev/null +++ b/src/qml/AppComboBox.qml @@ -0,0 +1,27 @@ +/* + * Mirai. Copyright (C) 2024 Vyn + * This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only) + * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt + */ + +import QtQuick +import QtQuick.Controls +import Mirai + +ComboBox { + id: control + + contentItem: AppText { + anchors.fill: parent + text: control.currentText + verticalAlignment: Text.AlignVCenter + leftPadding: 10 + } + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 32 + color: MiraiColorPalette.fieldBackground + radius: 4 + } +} diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 8fe1604..54d9bc8 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -28,6 +28,11 @@ Window { property QtObject selected: MiraiColorPalette } + function openSettings() { + filesForm.reset() + filesFormPopup.open() + } + function newTask() { taskForm.taskToEdit = undefined taskForm.taskToEditIndex = -1 @@ -118,6 +123,25 @@ Window { Layout.fillHeight: true } + Popup { + id: filesFormPopup + width: parent.width * 0.75 + implicitHeight: filesForm.height + padding * 2 + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height * 0.4) / 2) + padding: 8 + background: Rectangle { + border.color: colorPalette.selected.modalBorder + border.width: 2 + color: colorPalette.selected.pane + radius: 4 + } + FilesForm { + id: filesForm + width: parent.width + } + } + Popup { id: taskFormPopup width: parent.width * 0.75 diff --git a/src/qml/SideMenu.qml b/src/qml/SideMenu.qml index a0c4135..7056667 100644 --- a/src/qml/SideMenu.qml +++ b/src/qml/SideMenu.qml @@ -11,6 +11,45 @@ import Mirai ColumnLayout { + AppText { + text: "Files" + font.pixelSize: 32 + } + + Item { Layout.preferredHeight: 16 } + + Repeater { + model: backend.files + Rectangle { + Layout.preferredHeight: childrenRect.height + Layout.fillWidth: true + color: backend.activeFilesFilter.includes(modelData.name) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent" + radius: 4 + AppText { + text: modelData.name + padding: 4 + } + MouseArea { + anchors.fill: parent + onClicked: { + if (backend.activeFilesFilter.includes(modelData.name)) { + backend.removeFileFilter(modelData.name) + } else { + backend.addFileFilter(modelData.name) + } + } + HoverHandler { + id: mouse + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + cursorShape: Qt.PointingHandCursor + } + } + + } + } + + Item { Layout.preferredHeight: 16 } + AppText { text: "Tags" font.pixelSize: 32 @@ -58,4 +97,11 @@ ColumnLayout { backend.hideCompletedTasks(!backend.shouldHideCompletedTasks) } } + + /*AppButton { + text: `Settings` + onClicked: { + root.openSettings() + } + }*/ } diff --git a/src/qml/forms/FilesForm.qml b/src/qml/forms/FilesForm.qml new file mode 100644 index 0000000..8f05d4f --- /dev/null +++ b/src/qml/forms/FilesForm.qml @@ -0,0 +1,52 @@ +/* + * Mirai. Copyright (C) 2024 Vyn + * This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only) + * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt + */ + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import Mirai + +// WIP + +ColumnLayout { + id: form + spacing: 6 + signal confirmed + + function reset() { + internal.paths = backend.files.map(file => { + return {path: file.path, name: file.name} + }) + } + + QtObject { + id: internal + property var paths + } + + Repeater { + model: internal.paths + ColumnLayout { + AppLineEdit { + text: modelData.name + } + + AppLineEdit { + text: modelData.path + } + } + } + + AppButton { + text: "Save" + onClicked: { + backend. + form.confirmed() + } + } +} + diff --git a/src/qml/forms/TaskForm.qml b/src/qml/forms/TaskForm.qml index e5206f7..1522c22 100644 --- a/src/qml/forms/TaskForm.qml +++ b/src/qml/forms/TaskForm.qml @@ -21,11 +21,32 @@ ColumnLayout { newTodoContent.text = taskToEdit?.rawFormat ?? "- [ ] " newTodoDate.text = taskToEdit?.date ?? "" } - + AppText { - text: "New task" + text: "New/Edit task" } + AppComboBox { + id: file + textRole: "text" + valueRole: "value" + // Set the initial currentIndex to the value stored in the backend. + Component.onCompleted: currentIndex = 0 + model: backend.files.map(file => ( + { value: file.path, text: qsTr(file.name) } + )) + onActivated: { + console.log(currentValue) + } + } + + DateField { + id: newTodoDate + text: taskToEdit?.date ?? "" + textFieldComponent.placeholderText: "No date" + Layout.fillWidth: true + } + AppLineEdit { id: newTodoContent Layout.fillWidth: true @@ -39,17 +60,13 @@ ColumnLayout { if (taskToEdit && taskToEditIndex !== undefined) { backend.updateTodoFromRawFormat(taskToEditIndex, newTodoContent.text, newTodoDate.text) } else { - backend.addTodoFromRawFormat(newTodoContent.text, newTodoDate.text) + backend.addTodoFromRawFormat( + file.currentValue, + newTodoContent.text, + newTodoDate.text + ) } form.confirmed() } } - - DateField { - id: newTodoDate - text: taskToEdit?.date ?? "" - textFieldComponent.placeholderText: "No date" - Layout.fillWidth: true - } } -