mirror of
https://codeberg.org/vyn/mirai.git
synced 2025-07-02 01:13:19 +00:00
Support multiple files
This commit is contained in:
parent
f8f49233dc
commit
689eea07a7
22 changed files with 528 additions and 131 deletions
|
@ -7,3 +7,5 @@ SeparateDefinitionBlocks: Always
|
|||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,8 +5,15 @@
|
|||
*/
|
||||
|
||||
#include "Backend.h"
|
||||
#include "TaskItem.h"
|
||||
#include "core/TaskItem.h"
|
||||
#include "core/TodoMd.h"
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsonvalue.h>
|
||||
#include <qlogging.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
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<QString> 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);
|
||||
}
|
||||
|
|
|
@ -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<mirai::TasksView> view;
|
||||
|
||||
QList<QMLTaskItem> QMLTasks;
|
||||
QList<QMLTasksFile> QMLTasksFiles;
|
||||
QList<QString> QMLTags;
|
||||
QList<QString> QMLActiveTagsFilter;
|
||||
QList<QString> QMLActiveFilesFilter;
|
||||
|
||||
bool shouldHideCompletedTasks_ = true;
|
||||
|
||||
signals:
|
||||
|
||||
void tasksChanged();
|
||||
void filesChanged();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -13,6 +13,21 @@
|
|||
#include <memory>
|
||||
|
||||
#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 {
|
||||
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
|
||||
#include "Mirai.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
|
||||
{
|
||||
|
@ -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<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()
|
||||
{
|
||||
auto view = std::make_shared<TasksView>(files);
|
||||
|
@ -71,4 +102,5 @@ const std::vector<std::string> &Mirai::getTags()
|
|||
{
|
||||
return tags;
|
||||
}
|
||||
|
||||
} // namespace mirai
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
#include "TasksView.h"
|
||||
#include "TodoMd.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
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<std::reference_wrapper<TasksFile>> getFileByPath(const std::string &path);
|
||||
|
||||
std::vector<std::unique_ptr<TasksFile>> &getFiles();
|
||||
std::weak_ptr<TasksView> getTasks();
|
||||
const std::vector<std::string> &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<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
|
||||
// if we have to remove the view (not used) or tell the view to update() on
|
||||
|
|
|
@ -5,68 +5,86 @@
|
|||
*/
|
||||
|
||||
#include "TaskItem.h"
|
||||
#include "cpp-utils/vector.h"
|
||||
#include "TasksFile.h"
|
||||
#include "core/utils.h"
|
||||
#include <iostream>
|
||||
|
||||
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
|
||||
|
|
|
@ -8,24 +8,39 @@
|
|||
#define MIRAI_TASKITEM_H
|
||||
|
||||
#include "using.h"
|
||||
#include "utils.h"
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
|
||||
#include "TasksFile.h"
|
||||
#include "TaskItem.h"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
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<TaskItem>(task));
|
||||
auto taskPtr = std::make_unique<TaskItem>(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>(taskItem));
|
||||
tasks.push_back(std::make_unique<TaskItem>(this, taskItem));
|
||||
setDirty(true);
|
||||
sortByDate();
|
||||
}
|
||||
|
||||
void TasksFile::addTask(std::string text, std::string date)
|
||||
{
|
||||
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));
|
||||
sortByDate();
|
||||
}
|
||||
|
||||
void TasksFile::removeTask(const TaskItem *taskToRemove)
|
||||
{
|
||||
tasks.erase(std::remove_if(tasks.begin(), tasks.end(),
|
||||
[&](const std::unique_ptr<TaskItem> &taskInFilter) {
|
||||
return taskInFilter.get() == taskToRemove;
|
||||
}));
|
||||
auto findFunction = [&](const std::unique_ptr<TaskItem> &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<TaskItem> &t1, const std::unique_ptr<TaskItem> &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<TaskItem> &t1, const std::unique_ptr<TaskItem> &t2) {
|
||||
if (t1->hasDate() && !t2->hasDate()) {
|
||||
return true;
|
||||
} else if (!t1->hasDate() && t2->hasDate()) {
|
||||
return false;
|
||||
}
|
||||
return t1->getDate() < t2->getDate();
|
||||
}
|
||||
);
|
||||
}
|
||||
} // namespace mirai
|
||||
|
|
|
@ -8,18 +8,7 @@
|
|||
#define MIRAI_TASKSFILE_H
|
||||
|
||||
#include "TaskItem.h"
|
||||
#include "using.h"
|
||||
#include "utils.h"
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -28,6 +17,11 @@ namespace mirai
|
|||
|
||||
using TasksPtrs = std::vector<std::unique_ptr<TaskItem>>;
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -5,17 +5,18 @@
|
|||
*/
|
||||
|
||||
#include "TasksView.h"
|
||||
#include "cpp-utils/vector.h"
|
||||
#include "utils.h"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -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<std::string> &TasksView::getActiveTagsFilter()
|
|||
{
|
||||
return tagsFilter;
|
||||
}
|
||||
|
||||
const std::vector<std::string> &TasksView::getActiveFilesFilter()
|
||||
{
|
||||
return filesFilter;
|
||||
}
|
||||
} // namespace mirai
|
||||
|
|
|
@ -19,7 +19,7 @@ class TasksView
|
|||
{
|
||||
|
||||
public:
|
||||
TasksView(std::shared_ptr<std::vector<TasksFile>> files);
|
||||
TasksView(std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> 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<std::string> &getActiveFilesFilter();
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::vector<TasksFile>> files;
|
||||
std::shared_ptr<std::vector<std::unique_ptr<TasksFile>>> files;
|
||||
std::vector<TaskItem *> tasksToShow;
|
||||
Tags tagsFilter;
|
||||
std::vector<std::string> filesFilter;
|
||||
};
|
||||
} // namespace mirai
|
||||
#endif
|
||||
|
|
|
@ -6,12 +6,16 @@
|
|||
|
||||
#include "TodoMd.h"
|
||||
#include "TaskItem.h"
|
||||
#include "cpp-utils/vector.h"
|
||||
#include "core/TasksFile.h"
|
||||
#include "utils.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
namespace mirai
|
||||
{
|
||||
|
||||
TasksFile TodoMdFormat::readFile(const std::string &path)
|
||||
std::unique_ptr<TasksFile> 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<TasksFile>(
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,7 @@
|
|||
|
||||
#include "TaskItem.h"
|
||||
#include "TasksFile.h"
|
||||
#include "utils.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mirai
|
||||
{
|
||||
|
@ -26,11 +17,11 @@ namespace mirai
|
|||
class TodoMdFormat
|
||||
{
|
||||
public:
|
||||
static TasksFile readFile(const std::string &path);
|
||||
static std::unique_ptr<TasksFile> 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);
|
||||
|
|
|
@ -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();
|
||||
|
|
27
src/qml/AppComboBox.qml
Normal file
27
src/qml/AppComboBox.qml
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
52
src/qml/forms/FilesForm.qml
Normal file
52
src/qml/forms/FilesForm.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue