Support multiple files

This commit is contained in:
Vyn 2024-04-14 14:11:41 +02:00
parent f8f49233dc
commit 689eea07a7
22 changed files with 528 additions and 131 deletions

View file

@ -7,3 +7,5 @@ SeparateDefinitionBlocks: Always
AllowShortBlocksOnASingleLine: Never AllowShortBlocksOnASingleLine: Never
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: Empty
AlignAfterOpenBracket: BlockIndent

View file

@ -38,8 +38,10 @@ qt_add_qml_module(mirai
src/qml/AppButton.qml src/qml/AppButton.qml
src/qml/AppCheckbox.qml src/qml/AppCheckbox.qml
src/qml/AppText.qml src/qml/AppText.qml
src/qml/AppComboBox.qml
src/qml/TaskItem.qml src/qml/TaskItem.qml
src/qml/forms/TaskForm.qml src/qml/forms/TaskForm.qml
src/qml/forms/FilesForm.qml
src/qml/components/TabSelector.qml src/qml/components/TabSelector.qml
src/qml/components/Tag.qml src/qml/components/Tag.qml
src/qml/styles/MiraiColorPalette.qml src/qml/styles/MiraiColorPalette.qml

View file

@ -5,8 +5,15 @@
*/ */
#include "Backend.h" #include "Backend.h"
#include "TaskItem.h"
#include "core/TaskItem.h"
#include "core/TodoMd.h" #include "core/TodoMd.h"
#include <iostream>
#include <ostream> #include <ostream>
#include <qjsonarray.h>
#include <qjsonvalue.h>
#include <qlogging.h>
#include <qvariant.h>
Backend::Backend() Backend::Backend()
{ {
@ -33,8 +40,9 @@ Backend::Backend()
qWarning() << "config.json should contains a 'files' string array"; qWarning() << "config.json should contains a 'files' string array";
exit(1); exit(1);
} }
QString filePath = jsonFilesPath.toArray()[0].toString(); for (const QJsonValueRef &filePath : jsonFilesPath.toArray()) {
mirai.loadFile(filePath.toStdString()); mirai.loadFile(filePath.toString().toStdString());
}
view = mirai.getTasks(); view = mirai.getTasks();
rebuildQMLTasksList(); rebuildQMLTasksList();
} }
@ -47,10 +55,18 @@ void Backend::addTodo(QString newTodo, QString date)
emit tasksChanged(); 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(); mirai.save();
view.lock()->update();
rebuildQMLTasksList(); rebuildQMLTasksList();
emit tasksChanged(); emit tasksChanged();
} }
@ -69,6 +85,20 @@ void Backend::removeTagFilter(QString tag)
emit tasksChanged(); 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() void Backend::removeFilters()
{ {
view.lock()->removeFilters(); view.lock()->removeFilters();
@ -79,6 +109,7 @@ void Backend::removeFilters()
void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date) void Backend::updateTodoFromRawFormat(int todoIndex, QString text, QString date)
{ {
QMLTaskItem &taskItem = QMLTasks[todoIndex]; QMLTaskItem &taskItem = QMLTasks[todoIndex];
taskItem.taskItem->setText(taskItem.taskItem->getText());
*(taskItem.taskItem) = *(taskItem.taskItem) =
mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString()); mirai::TodoMdFormat::StringToTask(text.toStdString(), date.toStdString());
mirai.save(); 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) void Backend::updateTodo(int todoIndex, QString state, QString text, QString date)
{ {
QMLTaskItem &taskItem = QMLTasks[todoIndex]; QMLTaskItem &taskItem = QMLTasks[todoIndex];
taskItem.taskItem->state = state == "TODO" ? mirai::TODO : mirai::DONE;
if (state == "DONE") { if (state == "DONE") {
taskItem.taskItem->markAsDone(); taskItem.taskItem->markAsDone();
} else if (state == "TODO") { } else if (state == "TODO") {
@ -120,6 +150,12 @@ void Backend::hideCompletedTasks(bool shouldHide)
void Backend::rebuildQMLTasksList() void Backend::rebuildQMLTasksList()
{ {
// TODO MOVE TO ANOTHER FILE
QMLTasksFiles.clear();
for (auto &file : mirai.getFiles()) {
QMLTasksFiles.push_back({.tasksFile = file.get()});
}
// ----
QMLTasks.clear(); QMLTasks.clear();
std::string lastDate = ""; std::string lastDate = "";
std::time_t t = std::time(nullptr); std::time_t t = std::time(nullptr);
@ -133,8 +169,8 @@ void Backend::rebuildQMLTasksList()
continue; continue;
} }
bool shouldShowDate = false; bool shouldShowDate = false;
if (lastDate != task.date) { if (lastDate != task.getDate()) {
lastDate = task.date; lastDate = task.getDate();
shouldShowDate = true; shouldShowDate = true;
} }
QList<QString> qStringTags; QList<QString> qStringTags;
@ -142,7 +178,8 @@ void Backend::rebuildQMLTasksList()
qStringTags.push_back(QString::fromStdString(tag)); qStringTags.push_back(QString::fromStdString(tag));
} }
QMLTasks.push_back( QMLTasks.push_back(
{.taskItem = &task, .shouldShowDate = shouldShowDate, .tags = qStringTags}); {.taskItem = &task, .shouldShowDate = shouldShowDate, .tags = qStringTags}
);
} }
QMLTags.clear(); QMLTags.clear();
@ -154,6 +191,11 @@ void Backend::rebuildQMLTasksList()
for (auto &activeTagFilter : view.lock()->getActiveTagsFilter()) { for (auto &activeTagFilter : view.lock()->getActiveTagsFilter()) {
QMLActiveTagsFilter.push_back(QString::fromStdString(activeTagFilter)); QMLActiveTagsFilter.push_back(QString::fromStdString(activeTagFilter));
} }
QMLActiveFilesFilter.clear();
for (auto &activeFileFilter : view.lock()->getActiveFilesFilter()) {
QMLActiveFilesFilter.push_back(QString::fromStdString(activeFileFilter));
}
} }
QVariant Backend::getTasks() QVariant Backend::getTasks()
@ -171,7 +213,17 @@ QVariant Backend::getActiveTagsFilter()
return QVariant::fromValue(QMLActiveTagsFilter); return QVariant::fromValue(QMLActiveTagsFilter);
} }
QVariant Backend::getActiveFilesFilter()
{
return QVariant::fromValue(QMLActiveFilesFilter);
}
bool Backend::shouldHideCompletedTasks() bool Backend::shouldHideCompletedTasks()
{ {
return shouldHideCompletedTasks_; return shouldHideCompletedTasks_;
} }
QVariant Backend::getFiles()
{
return QVariant::fromValue(QMLTasksFiles);
}

View file

@ -30,8 +30,10 @@ class Backend : public QObject
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QVariant tasks READ getTasks NOTIFY tasksChanged) 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 tags READ getTags NOTIFY tasksChanged)
Q_PROPERTY(QVariant activeTagsFilter READ getActiveTagsFilter 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 Q_PROPERTY(bool shouldHideCompletedTasks READ shouldHideCompletedTasks WRITE hideCompletedTasks
NOTIFY tasksChanged) NOTIFY tasksChanged)
@ -39,9 +41,11 @@ class Backend : public QObject
Backend(); Backend();
Q_INVOKABLE void addTodo(QString newTodo, QString date); 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 addTagFilter(QString tag);
Q_INVOKABLE void removeTagFilter(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 removeFilters();
Q_INVOKABLE void updateTodoFromRawFormat(int todoIndex, QString text, QString date); Q_INVOKABLE void updateTodoFromRawFormat(int todoIndex, QString text, QString date);
Q_INVOKABLE void updateTodo(int todoIndex, QString state, 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(); void rebuildQMLTasksList();
QVariant getTasks(); QVariant getTasks();
QVariant getFiles();
QVariant getTags(); QVariant getTags();
QVariant getActiveTagsFilter(); QVariant getActiveTagsFilter();
QVariant getActiveFilesFilter();
bool shouldHideCompletedTasks(); bool shouldHideCompletedTasks();
mirai::Mirai mirai; mirai::Mirai mirai;
std::weak_ptr<mirai::TasksView> view; std::weak_ptr<mirai::TasksView> view;
QList<QMLTaskItem> QMLTasks; QList<QMLTaskItem> QMLTasks;
QList<QMLTasksFile> QMLTasksFiles;
QList<QString> QMLTags; QList<QString> QMLTags;
QList<QString> QMLActiveTagsFilter; QList<QString> QMLActiveTagsFilter;
QList<QString> QMLActiveFilesFilter;
bool shouldHideCompletedTasks_ = true; bool shouldHideCompletedTasks_ = true;
signals: signals:
void tasksChanged(); void tasksChanged();
void filesChanged();
}; };
#endif #endif

View file

@ -7,6 +7,16 @@
#include "TaskItem.h" #include "TaskItem.h"
#include "core/TodoMd.h" #include "core/TodoMd.h"
QString QMLTasksFile::getName()
{
return QString::fromStdString(tasksFile->getName());
}
QString QMLTasksFile::getPath()
{
return QString::fromStdString(tasksFile->getPath());
}
QString QMLTaskItem::getRawFormat() QString QMLTaskItem::getRawFormat()
{ {
return QString::fromStdString(mirai::TodoMdFormat::TaskToString(*taskItem)); return QString::fromStdString(mirai::TodoMdFormat::TaskToString(*taskItem));

View file

@ -13,6 +13,21 @@
#include <memory> #include <memory>
#include "core/TaskItem.h" #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 { struct QMLTaskItem {

View file

@ -6,7 +6,14 @@
#include "Mirai.h" #include "Mirai.h"
#include "TaskItem.h" #include "TaskItem.h"
#include "cpp-utils/vector.h" #include "core/TasksFile.h"
#include "utils.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <optional>
#include <ostream>
#include <vector>
namespace mirai namespace mirai
{ {
@ -16,10 +23,12 @@ void Mirai::loadFile(const std::string &path)
auto tasksFile = TodoMdFormat::readFile(path); auto tasksFile = TodoMdFormat::readFile(path);
files->push_back(std::move(tasksFile)); files->push_back(std::move(tasksFile));
tags.clear(); tags.clear();
for (auto &task : (*files)[0].getTasks()) { for (auto &file : *files) {
for (auto &tag : task->getTags()) { for (auto &task : file->getTasks()) {
if (!vectorUtils::contains(tags, tag)) { for (auto &tag : task->getTags()) {
tags.push_back(tag); if (!vectorUtils::contains(tags, tag)) {
tags.push_back(tag);
}
} }
} }
} }
@ -28,13 +37,16 @@ void Mirai::loadFile(const std::string &path)
void Mirai::save() void Mirai::save()
{ {
for (auto &file : *files) { 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) { for (auto &view : views) {
view->update(); view->update();
} }
@ -46,7 +58,7 @@ void Mirai::addTask(std::string text, std::string date)
/*std::tm tm = *std::localtime(&t);*/ /*std::tm tm = *std::localtime(&t);*/
/*std::stringstream ssCreationDate;*/ /*std::stringstream ssCreationDate;*/
/*ssCreationDate << std::put_time(&tm, "%Y-%m-%d");*/ /*ssCreationDate << std::put_time(&tm, "%Y-%m-%d");*/
(*files)[0].addTask(text, date); (*files)[0]->addTask(text, date);
for (auto &view : views) { for (auto &view : views) {
view->update(); view->update();
} }
@ -54,12 +66,31 @@ void Mirai::addTask(std::string text, std::string date)
void Mirai::removeTask(const TaskItem *taskItem) void Mirai::removeTask(const TaskItem *taskItem)
{ {
(*files)[0].removeTask(taskItem); for (auto &file : *files) {
file->removeTask(taskItem);
}
for (auto &view : views) { for (auto &view : views) {
view->update(); view->update();
} }
} }
std::vector<std::unique_ptr<TasksFile>> &Mirai::getFiles()
{
return *files.get();
}
std::optional<std::reference_wrapper<TasksFile>> Mirai::getFileByPath(const std::string &path)
{
auto fileIterator = std::ranges::find_if(*files, [&](const std::unique_ptr<TasksFile> &file) {
return file->getPath() == path;
});
if (fileIterator == files->end()) {
return std::nullopt;
}
return *(fileIterator->get());
}
std::weak_ptr<TasksView> Mirai::getTasks() std::weak_ptr<TasksView> Mirai::getTasks()
{ {
auto view = std::make_shared<TasksView>(files); auto view = std::make_shared<TasksView>(files);
@ -71,4 +102,5 @@ const std::vector<std::string> &Mirai::getTags()
{ {
return tags; return tags;
} }
} // namespace mirai } // namespace mirai

View file

@ -12,7 +12,10 @@
#include "TasksView.h" #include "TasksView.h"
#include "TodoMd.h" #include "TodoMd.h"
#include <algorithm> #include <algorithm>
#include <functional>
#include <memory> #include <memory>
#include <optional>
#include <string>
namespace mirai namespace mirai
{ {
@ -23,10 +26,13 @@ class Mirai
public: public:
void loadFile(const std::string &path); void loadFile(const std::string &path);
void save(); void save();
void addTask(TaskItem taskItem); void addTask(TaskItemData taskItem);
void addTask(std::string text, std::string date); void addTask(std::string text, std::string date);
void removeTask(const TaskItem *taskItem); void removeTask(const TaskItem *taskItem);
void addFile(TasksFileConfig config);
std::optional<std::reference_wrapper<TasksFile>> getFileByPath(const std::string &path);
std::vector<std::unique_ptr<TasksFile>> &getFiles();
std::weak_ptr<TasksView> getTasks(); std::weak_ptr<TasksView> getTasks();
const std::vector<std::string> &getTags(); const std::vector<std::string> &getTags();
@ -34,7 +40,8 @@ class Mirai
// The `TasksFile`s are shared to the views, their lifetime can outlive // The `TasksFile`s are shared to the views, their lifetime can outlive
// this (Mirai) object // this (Mirai) object
// because we can't control if the caller will keep the main object alive. // because we can't control if the caller will keep the main object alive.
std::shared_ptr<std::vector<TasksFile>> files = std::make_shared<std::vector<TasksFile>>(); std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files =
std::make_shared<std::vector<std::unique_ptr<TasksFile>>>();
// We keep a vector of shared_ptr because we need the ref counting mechanism to know // 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 // if we have to remove the view (not used) or tell the view to update() on

View file

@ -5,68 +5,86 @@
*/ */
#include "TaskItem.h" #include "TaskItem.h"
#include "cpp-utils/vector.h" #include "TasksFile.h"
#include "core/utils.h"
#include <iostream>
namespace mirai namespace mirai
{ {
TaskItem::TaskItem(TasksFile *parent, TaskItemData data) : parent(parent), data(data)
{
}
void TaskItem::markAsDone() void TaskItem::markAsDone()
{ {
state = DONE; data.state = DONE;
onChange();
} }
void TaskItem::markAsUndone() void TaskItem::markAsUndone()
{ {
state = TODO; data.state = TODO;
onChange();
} }
void TaskItem::setDate(const std::string &date) void TaskItem::setDate(const std::string &date)
{ {
this->date = date; this->data.date = date;
onChange();
} }
void TaskItem::setText(const std::string &text) void TaskItem::setText(const std::string &text)
{ {
this->text = text; this->data.text = text;
onChange();
} }
const std::string &TaskItem::getText() const const std::string &TaskItem::getText() const
{ {
return text; return data.text;
} }
const TaskItemState &TaskItem::getState() const const TaskItemState &TaskItem::getState() const
{ {
return state; return data.state;
} }
const std::string &TaskItem::getDate() const const std::string &TaskItem::getDate() const
{ {
return date; return data.date;
} }
const std::string &TaskItem::getStartTime() const const std::string &TaskItem::getStartTime() const
{ {
return startTime; return data.startTime;
} }
const std::string &TaskItem::getEndTime() const const std::string &TaskItem::getEndTime() const
{ {
return endTime; return data.endTime;
} }
const Tags &TaskItem::getTags() const const Tags &TaskItem::getTags() const
{ {
return tags; return data.tags;
} }
bool TaskItem::hasDate() const bool TaskItem::hasDate() const
{ {
return isDate(date); return isDate(data.date);
} }
bool TaskItem::hasTag(const std::string &tag) const 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 } // namespace mirai

View file

@ -8,24 +8,39 @@
#define MIRAI_TASKITEM_H #define MIRAI_TASKITEM_H
#include "using.h" #include "using.h"
#include "utils.h" #include <functional>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string> #include <string>
#include <vector>
namespace mirai namespace mirai
{ {
enum TaskItemState { TODO, DONE }; 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: public:
TaskItem(TasksFile *parent, const TaskItemData data);
TaskItem &operator=(const TaskItemData &newData)
{
data = newData;
onChange();
return *this;
};
void markAsDone(); void markAsDone();
void markAsUndone(); void markAsUndone();
@ -43,12 +58,11 @@ struct TaskItem {
bool hasTag(const std::string &tag) const; bool hasTag(const std::string &tag) const;
bool hasDate() const; bool hasDate() const;
std::string text; private:
TaskItemState state; void onChange();
std::string date;
std::string startTime; TasksFile *parent;
std::string endTime; TaskItemData data;
Tags tags;
}; };
} // namespace mirai } // namespace mirai

View file

@ -6,7 +6,11 @@
#include "TasksFile.h" #include "TasksFile.h"
#include "TaskItem.h" #include "TaskItem.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <memory> #include <memory>
#include <ostream>
namespace mirai namespace mirai
{ {
@ -14,11 +18,28 @@ namespace mirai
TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path) TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path)
{ {
for (const auto &task : params.tasks) { for (const auto &task : params.tasks) {
tasks.push_back(std::make_unique<TaskItem>(task)); auto taskPtr = std::make_unique<TaskItem>(task);
tasks.push_back(std::move(taskPtr));
} }
sortByDate(); 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 const std::string &TasksFile::getName() const
{ {
return name; return name;
@ -34,38 +55,47 @@ TasksPtrs &TasksFile::getTasks()
return tasks; return tasks;
} }
void TasksFile::addTask(TaskItem taskItem) void TasksFile::addTask(TaskItemData taskItem)
{ {
tasks.push_back(std::make_unique<TaskItem>(taskItem)); tasks.push_back(std::make_unique<TaskItem>(this, taskItem));
setDirty(true);
sortByDate(); sortByDate();
} }
void TasksFile::addTask(std::string text, std::string date) void TasksFile::addTask(std::string text, std::string date)
{ {
auto newTask = std::make_unique<TaskItem>( auto newTask = std::make_unique<TaskItem>(
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)); tasks.push_back(std::move(newTask));
sortByDate(); sortByDate();
} }
void TasksFile::removeTask(const TaskItem *taskToRemove) void TasksFile::removeTask(const TaskItem *taskToRemove)
{ {
tasks.erase(std::remove_if(tasks.begin(), tasks.end(), auto findFunction = [&](const std::unique_ptr<TaskItem> &taskInFilter) {
[&](const std::unique_ptr<TaskItem> &taskInFilter) { return taskInFilter.get() == taskToRemove;
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() void TasksFile::sortByDate()
{ {
std::sort(tasks.begin(), tasks.end(), std::sort(
[](const std::unique_ptr<TaskItem> &t1, const std::unique_ptr<TaskItem> &t2) { tasks.begin(), tasks.end(),
if (t1->hasDate() && !t2->hasDate()) { [](const std::unique_ptr<TaskItem> &t1, const std::unique_ptr<TaskItem> &t2) {
return true; if (t1->hasDate() && !t2->hasDate()) {
} else if (!t1->hasDate() && t2->hasDate()) { return true;
return false; } else if (!t1->hasDate() && t2->hasDate()) {
} return false;
return t1->date < t2->date; }
}); return t1->getDate() < t2->getDate();
}
);
} }
} // namespace mirai } // namespace mirai

View file

@ -8,18 +8,7 @@
#define MIRAI_TASKSFILE_H #define MIRAI_TASKSFILE_H
#include "TaskItem.h" #include "TaskItem.h"
#include "using.h"
#include "utils.h"
#include <algorithm>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory> #include <memory>
#include <ostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
@ -28,6 +17,11 @@ namespace mirai
using TasksPtrs = std::vector<std::unique_ptr<TaskItem>>; using TasksPtrs = std::vector<std::unique_ptr<TaskItem>>;
struct TasksFileConfig {
std::string name;
std::string path;
};
struct TasksFileConstructor { struct TasksFileConstructor {
const std::string &name; const std::string &name;
const std::string &path; const std::string &path;
@ -38,10 +32,11 @@ class TasksFile
{ {
public: public:
TasksFile(const TasksFile &) = delete;
TasksFile &operator=(const TasksFile &) = delete;
TasksFile(TasksFileConstructor params); TasksFile(TasksFileConstructor params);
void addTask(std::string text, std::string date); void addTask(std::string text, std::string date);
void addTask(TaskItem TaskItem); void addTask(TaskItemData TaskItem);
void removeTask(const TaskItem *taskToRemove); void removeTask(const TaskItem *taskToRemove);
void sortByDate(); void sortByDate();
@ -49,10 +44,16 @@ class TasksFile
const std::string &getPath() const; const std::string &getPath() const;
TasksPtrs &getTasks(); TasksPtrs &getTasks();
void setDirty(bool shouldBeDirty);
bool isDirty() const;
private: private:
void onTaskChanged(TaskItem &);
std::string name; std::string name;
std::string path; std::string path;
TasksPtrs tasks; TasksPtrs tasks;
bool isDirty_ = false;
}; };
} // namespace mirai } // namespace mirai

View file

@ -5,17 +5,18 @@
*/ */
#include "TasksView.h" #include "TasksView.h"
#include "cpp-utils/vector.h" #include "utils.h"
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <memory>
#include <ostream> #include <ostream>
#include <string> #include <string>
namespace mirai namespace mirai
{ {
TasksView::TasksView(std::shared_ptr<std::vector<TasksFile>> files) : files(files) TasksView::TasksView(std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files) : files(files)
{ {
update(); update();
} }
@ -34,7 +35,10 @@ void TasksView::update()
{ {
tasksToShow.clear(); tasksToShow.clear();
for (auto &file : *files) { 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())) { if (tagsFilter.size() > 0 && !vectorUtils::containsAll(tagsFilter, task->getTags())) {
continue; continue;
} }
@ -51,8 +55,28 @@ void TasksView::addTagFilter(const std::string &tag)
void TasksView::removeTagFilter(const std::string &tag) void TasksView::removeTagFilter(const std::string &tag)
{ {
tagsFilter.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), tagsFilter
[&](const auto &tagInFilter) { return tag == tagInFilter; })); .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(); update();
} }
@ -66,4 +90,9 @@ const std::vector<std::string> &TasksView::getActiveTagsFilter()
{ {
return tagsFilter; return tagsFilter;
} }
const std::vector<std::string> &TasksView::getActiveFilesFilter()
{
return filesFilter;
}
} // namespace mirai } // namespace mirai

View file

@ -19,7 +19,7 @@ class TasksView
{ {
public: public:
TasksView(std::shared_ptr<std::vector<TasksFile>> files); TasksView(std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files);
TaskItem &operator[](int index); TaskItem &operator[](int index);
@ -27,13 +27,17 @@ class TasksView
void update(); void update();
void addTagFilter(const std::string &tag); void addTagFilter(const std::string &tag);
void removeTagFilter(const std::string &tag); void removeTagFilter(const std::string &tag);
void addFileFilter(const std::string &fileName);
void removeFileFilter(const std::string &fileName);
void removeFilters(); void removeFilters();
const Tags &getActiveTagsFilter(); const Tags &getActiveTagsFilter();
const std::vector<std::string> &getActiveFilesFilter();
private: private:
std::shared_ptr<std::vector<TasksFile>> files; std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files;
std::vector<TaskItem *> tasksToShow; std::vector<TaskItem *> tasksToShow;
Tags tagsFilter; Tags tagsFilter;
std::vector<std::string> filesFilter;
}; };
} // namespace mirai } // namespace mirai
#endif #endif

View file

@ -6,12 +6,16 @@
#include "TodoMd.h" #include "TodoMd.h"
#include "TaskItem.h" #include "TaskItem.h"
#include "cpp-utils/vector.h" #include "core/TasksFile.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <memory>
namespace mirai namespace mirai
{ {
TasksFile TodoMdFormat::readFile(const std::string &path) std::unique_ptr<TasksFile> TodoMdFormat::readFile(const std::string &path)
{ {
std::ifstream file(path); std::ifstream file(path);
if (!file.is_open()) { if (!file.is_open()) {
@ -27,18 +31,22 @@ TasksFile TodoMdFormat::readFile(const std::string &path)
std::string name = line.substr(2); std::string name = line.substr(2);
std::string currentDate = ""; std::string currentDate = "";
auto tasksFile = std::make_unique<TasksFile>(
TasksFileConstructor{.name = name, .path = path, .tasks = taskItems}
);
while (std::getline(file, line)) { while (std::getline(file, line)) {
if (line.substr(0, 3) == "## ") { if (line.substr(0, 3) == "## ") {
currentDate = line.substr(3); currentDate = line.substr(3);
} else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") { } else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") {
TaskItem taskItem = StringToTask(line, currentDate); TaskItemData taskItemData = StringToTask(line, currentDate);
taskItem.setDate(currentDate); taskItemData.date = currentDate;
taskItems.push_back(taskItem); tasksFile->addTask(taskItemData);
} }
} }
file.close(); file.close();
TasksFile tasks({.name = name, .path = path, .tasks = taskItems}); tasksFile->setDirty(false);
return tasks; return tasksFile;
} }
Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata) Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata)
@ -67,9 +75,9 @@ void TodoMdFormat::writeFile(TasksFile &tasks)
file << "# " << tasks.getName() << "\n"; file << "# " << tasks.getName() << "\n";
for (const auto &task : tasks.getTasks()) { for (const auto &task : tasks.getTasks()) {
if (currentDate != task->date) { if (currentDate != task->getDate()) {
currentDate = task->date; currentDate = task->getDate();
file << "\n## " << (task->date != "" ? task->date : "No date") << "\n\n"; file << "\n## " << (task->getDate() != "" ? task->getDate() : "No date") << "\n\n";
} }
file << TaskToString(*task) << '\n'; file << TaskToString(*task) << '\n';
} }
@ -78,16 +86,17 @@ void TodoMdFormat::writeFile(TasksFile &tasks)
std::string TodoMdFormat::fieldWithSpace(const std::string &field) std::string TodoMdFormat::fieldWithSpace(const std::string &field)
{ {
if (field.length() == 0) if (field.length() == 0) {
return ""; return "";
}
return (field + " "); 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::smatch matches;
std::regex regex( std::regex regex("- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?"
"- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?"); );
std::regex_match(str, matches, regex); std::regex_match(str, matches, regex);
/*std::cout << "line " << str << std::endl;*/ /*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]); std::string text = stringUtils::trim(matches[5]);
TaskItem taskItem = {.text = text, TaskItemData taskItem{
.state = str.substr(0, 5) == "- [X]" ? DONE : TODO, .text = text,
.date = date, .state = str.substr(0, 5) == "- [X]" ? DONE : TODO,
.startTime = matches[3], .date = date,
.endTime = matches[4], .startTime = matches[3],
.tags = extractTagsFromMetadata(matches[7])}; .endTime = matches[4],
.tags = extractTagsFromMetadata(matches[7])
};
return taskItem; return taskItem;
} }

View file

@ -9,16 +9,7 @@
#include "TaskItem.h" #include "TaskItem.h"
#include "TasksFile.h" #include "TasksFile.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <ostream>
#include <regex>
#include <stdexcept>
#include <string> #include <string>
#include <vector>
namespace mirai namespace mirai
{ {
@ -26,11 +17,11 @@ namespace mirai
class TodoMdFormat class TodoMdFormat
{ {
public: public:
static TasksFile readFile(const std::string &path); static std::unique_ptr<TasksFile> readFile(const std::string &path);
static void writeFile(TasksFile &tasks); static void writeFile(TasksFile &tasks);
static std::string TaskToString(const TaskItem &task); 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: private:
static std::string fieldWithSpace(const std::string &field); static std::string fieldWithSpace(const std::string &field);

View file

@ -36,7 +36,11 @@ int main(int argc, char *argv[])
const QUrl url(u"qrc:/qt/qml/Mirai/src/qml/Main.qml"_qs); const QUrl url(u"qrc:/qt/qml/Mirai/src/qml/Main.qml"_qs);
QObject::connect( QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app, &engine, &QQmlApplicationEngine::objectCreationFailed, &app,
[]() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); []() {
QCoreApplication::exit(-1);
},
Qt::QueuedConnection
);
engine.load(url); engine.load(url);
return app.exec(); return app.exec();

27
src/qml/AppComboBox.qml Normal file
View file

@ -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
}
}

View file

@ -28,6 +28,11 @@ Window {
property QtObject selected: MiraiColorPalette property QtObject selected: MiraiColorPalette
} }
function openSettings() {
filesForm.reset()
filesFormPopup.open()
}
function newTask() { function newTask() {
taskForm.taskToEdit = undefined taskForm.taskToEdit = undefined
taskForm.taskToEditIndex = -1 taskForm.taskToEditIndex = -1
@ -118,6 +123,25 @@ Window {
Layout.fillHeight: true 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 { Popup {
id: taskFormPopup id: taskFormPopup
width: parent.width * 0.75 width: parent.width * 0.75

View file

@ -11,6 +11,45 @@ import Mirai
ColumnLayout { 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 { AppText {
text: "Tags" text: "Tags"
font.pixelSize: 32 font.pixelSize: 32
@ -58,4 +97,11 @@ ColumnLayout {
backend.hideCompletedTasks(!backend.shouldHideCompletedTasks) backend.hideCompletedTasks(!backend.shouldHideCompletedTasks)
} }
} }
/*AppButton {
text: `Settings`
onClicked: {
root.openSettings()
}
}*/
} }

View file

@ -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()
}
}
}

View file

@ -21,11 +21,32 @@ ColumnLayout {
newTodoContent.text = taskToEdit?.rawFormat ?? "- [ ] " newTodoContent.text = taskToEdit?.rawFormat ?? "- [ ] "
newTodoDate.text = taskToEdit?.date ?? "" newTodoDate.text = taskToEdit?.date ?? ""
} }
AppText { 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 { AppLineEdit {
id: newTodoContent id: newTodoContent
Layout.fillWidth: true Layout.fillWidth: true
@ -39,17 +60,13 @@ ColumnLayout {
if (taskToEdit && taskToEditIndex !== undefined) { if (taskToEdit && taskToEditIndex !== undefined) {
backend.updateTodoFromRawFormat(taskToEditIndex, newTodoContent.text, newTodoDate.text) backend.updateTodoFromRawFormat(taskToEditIndex, newTodoContent.text, newTodoDate.text)
} else { } else {
backend.addTodoFromRawFormat(newTodoContent.text, newTodoDate.text) backend.addTodoFromRawFormat(
file.currentValue,
newTodoContent.text,
newTodoDate.text
)
} }
form.confirmed() form.confirmed()
} }
} }
DateField {
id: newTodoDate
text: taskToEdit?.date ?? ""
textFieldComponent.placeholderText: "No date"
Layout.fillWidth: true
}
} }