Правый клик по карточке открывает общее меню быстрых действий: открыть, новая дочерняя задача, статус, управление сессией, копировать/вставить, удалить. На пустом месте — новая задача и вставка (в канбане — в статус колонки). TreeView и KanbanBoard получили хук setOnContextMenu; меню строится одним TaskContextMenu для обоих видов. Ctrl+C/Ctrl+V копируют задачи вместе с поддеревом.
88 lines
3 KiB
C++
88 lines
3 KiB
C++
#pragma once
|
|
|
|
#include "Menus.hpp"
|
|
#include "TaskBoard.hpp"
|
|
#include "TaskStore.hpp"
|
|
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
namespace tp::taskboard {
|
|
|
|
// Copies tasks with their whole subtrees and pastes them back as new tasks. Sessions, workspaces and
|
|
// dependencies are not copied: a pasted task is a fresh, never-run task.
|
|
class TaskClipboard {
|
|
public:
|
|
void copy(const TaskBoard& board, const std::vector<ui::NodeId>& ids);
|
|
std::vector<ui::NodeId> paste(TaskBoard& board, std::optional<ui::NodeId> parent) const;
|
|
|
|
[[nodiscard]] bool isEmpty() const { return mEntries.empty(); }
|
|
[[nodiscard]] size_t taskCount() const { return mEntries.size(); }
|
|
|
|
private:
|
|
struct Entry {
|
|
Task task;
|
|
std::optional<size_t> parent;
|
|
};
|
|
|
|
void copySubtree(const TaskBoard& board, ui::NodeId id, std::optional<size_t> parent);
|
|
|
|
std::vector<Entry> mEntries;
|
|
};
|
|
|
|
// Where the user asked for the menu: the tasks it acts on (empty over empty space) and, in the kanban, the
|
|
// status of the column, which new and pasted tasks take.
|
|
struct TaskMenuTarget {
|
|
std::vector<ui::NodeId> tasks;
|
|
std::optional<TaskStatus> column;
|
|
|
|
[[nodiscard]] bool isEmptySpace() const { return tasks.empty(); }
|
|
};
|
|
|
|
// The quick-action menu shown on a secondary click in the tree and the kanban. Both views build it from the
|
|
// same target description, so they always offer the same actions.
|
|
class TaskContextMenu {
|
|
public:
|
|
struct Actions {
|
|
std::function<void(ui::NodeId)> open;
|
|
std::function<void(std::optional<ui::NodeId> parent, std::optional<TaskStatus> status)> create;
|
|
std::function<void(const std::vector<ui::NodeId>&)> copy;
|
|
std::function<void(std::optional<ui::NodeId> parent, std::optional<TaskStatus> status)> paste;
|
|
std::function<void(const std::vector<ui::NodeId>&)> remove;
|
|
std::function<void(const std::vector<ui::NodeId>&, TaskStatus)> setStatus;
|
|
std::function<void(ui::NodeId)> openSession;
|
|
};
|
|
|
|
TaskContextMenu(const TaskBoard& board, const TaskClipboard& clipboard, Actions actions);
|
|
|
|
void setStore(TaskStore* store) { mStore = store; }
|
|
void setReadOnly(bool readOnly) { mReadOnly = readOnly; }
|
|
|
|
void build(const TaskMenuTarget& target);
|
|
void open(ui::Ui& ui, const TaskMenuTarget& target, Vec2F windowPoint);
|
|
|
|
[[nodiscard]] ui::Menu& menu() { return mMenu; }
|
|
[[nodiscard]] ui::Menu& statusMenu() { return mStatusMenu; }
|
|
[[nodiscard]] ui::Menu& sessionMenu() { return mSessionMenu; }
|
|
[[nodiscard]] const TaskMenuTarget& target() const { return mTarget; }
|
|
|
|
private:
|
|
void addTaskItems();
|
|
void addEmptySpaceItems();
|
|
void addClipboardItems(std::optional<ui::NodeId> pasteParent);
|
|
void fillStatusMenu();
|
|
void fillSessionMenu(ui::NodeId id);
|
|
[[nodiscard]] std::string pasteText(std::optional<ui::NodeId> parent) const;
|
|
|
|
const TaskBoard& mBoard;
|
|
const TaskClipboard& mClipboard;
|
|
Actions mActions;
|
|
TaskStore* mStore = nullptr;
|
|
bool mReadOnly = false;
|
|
TaskMenuTarget mTarget;
|
|
ui::Menu mMenu;
|
|
ui::Menu mStatusMenu;
|
|
ui::Menu mSessionMenu;
|
|
};
|
|
}
|