Modules/Taskboard/public/TaskContextMenu.hpp
claude-opus-5-5 ba1f72844f Taskboard: контекстное меню задачи в дереве и канбане
Правый клик по карточке открывает общее меню быстрых действий: открыть,
новая дочерняя задача, статус, управление сессией, копировать/вставить,
удалить. На пустом месте — новая задача и вставка (в канбане — в статус
колонки). TreeView и KanbanBoard получили хук setOnContextMenu; меню
строится одним TaskContextMenu для обоих видов. Ctrl+C/Ctrl+V копируют
задачи вместе с поддеревом.
2026-10-28 14:22:35 +03:00

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