#pragma once // Step 354: Menu Bar with key symbols. // Headless data model used by tests and UI integration points. #include #include #include #include "KeySymbolRenderer.h" struct MenuItem { std::string id; std::string label; std::string shortcut; // text form, e.g. Ctrl+S std::string symbols; // symbol/text depending platform, e.g. ⌃S bool isSeparator = false; bool enabled = true; bool hasSubmenu = false; std::vector submenuItems; static MenuItem separator() { MenuItem m; m.isSeparator = true; return m; } }; struct Menu { std::string title; std::vector items; }; class MenuBar { public: void addMenu(const Menu& menu) { menus_.push_back(menu); } const std::vector& getMenus() const { return menus_; } size_t menuCount() const { return menus_.size(); } const Menu* getMenu(const std::string& title) const { for (const auto& m : menus_) { if (m.title == title) return &m; } return nullptr; } bool hasMenu(const std::string& title) const { return getMenu(title) != nullptr; } MenuItem findItem(const std::string& id) const { for (const auto& menu : menus_) { for (const auto& item : menu.items) { if (item.id == id) return item; if (item.hasSubmenu) { for (const auto& sub : item.submenuItems) { if (sub.id == id) return sub; } } } } return {}; } bool hasItem(const std::string& id) const { return !findItem(id).id.empty(); } int separatorCount(const std::string& menuTitle) const { const Menu* menu = getMenu(menuTitle); if (!menu) return 0; int count = 0; for (const auto& item : menu->items) { if (item.isSeparator) count++; } return count; } bool hasSubmenu(const std::string& itemId) const { for (const auto& menu : menus_) { for (const auto& item : menu.items) { if (item.id == itemId) return item.hasSubmenu && !item.submenuItems.empty(); } } return false; } void openMenu(const std::string& title) { openMenu_ = title; } bool isMenuOpen(const std::string& title) const { return openMenu_ == title; } bool isAnyMenuOpen() const { return !openMenu_.empty(); } std::optional selectItem(const std::string& itemId) { MenuItem item = findItem(itemId); if (item.id.empty() || !item.enabled || item.isSeparator) return std::nullopt; openMenu_.clear(); // selecting an action closes menus return item.id; } static MenuBar buildDefaults(const KeybindingRegistry& reg, const WhetstoneTheme& theme = getDefaultDarkTheme()) { MenuBar bar; appendFileMenu(bar, reg, theme); appendEditMenu(bar, reg, theme); appendViewMenu(bar, reg, theme); appendToolsMenu(bar, reg, theme); appendWorkflowMenu(bar, reg, theme); appendHelpMenu(bar, reg, theme); return bar; } private: static MenuItem makeActionItem(const KeybindingRegistry& reg, const WhetstoneTheme& theme, const std::string& id, const std::string& fallback) { MenuItem item; item.id = id; item.label = fallback; for (const auto& action : reg.getActions()) { if (action.id == id) { item.label = action.label; break; } } auto layout = KeySymbolRenderer::renderActionWithKey(item.label, id, reg, theme); item.shortcut = layout.shortcut; item.symbols = layout.symbols; return item; } static void appendFileMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "File"; m.items.push_back(makeActionItem(reg, theme, "new-file", "New")); m.items.push_back(makeActionItem(reg, theme, "open-file", "Open File")); m.items.push_back(MenuItem::separator()); m.items.push_back(makeActionItem(reg, theme, "save-buffer", "Save")); m.items.push_back(makeActionItem(reg, theme, "save-all", "Save All")); m.items.push_back(MenuItem::separator()); m.items.push_back(makeActionItem(reg, theme, "close-buffer", "Close")); bar.addMenu(m); } static void appendEditMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "Edit"; m.items.push_back(makeActionItem(reg, theme, "undo", "Undo")); m.items.push_back(makeActionItem(reg, theme, "redo", "Redo")); m.items.push_back(MenuItem::separator()); m.items.push_back(makeActionItem(reg, theme, "find-in-file", "Find")); m.items.push_back(makeActionItem(reg, theme, "find-in-project", "Find in Project")); bar.addMenu(m); } static void appendViewMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "View"; m.items.push_back(makeActionItem(reg, theme, "toggle-file-tree", "Toggle File Tree")); m.items.push_back(makeActionItem(reg, theme, "toggle-ast-view", "Toggle AST")); m.items.push_back(makeActionItem(reg, theme, "toggle-diagnostics", "Toggle Diagnostics")); m.items.push_back(makeActionItem(reg, theme, "toggle-annotations", "Toggle Annotations")); m.items.push_back(MenuItem::separator()); m.items.push_back(makeActionItem(reg, theme, "reset-layout", "Reset Layout")); bar.addMenu(m); } static void appendToolsMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "Tools"; m.items.push_back(makeActionItem(reg, theme, "run-pipeline", "Run Pipeline")); m.items.push_back(makeActionItem(reg, theme, "generate-code", "Generate Code")); m.items.push_back(makeActionItem(reg, theme, "infer-annotations", "Infer Annotations")); m.items.push_back(makeActionItem(reg, theme, "validate", "Validate")); m.items.push_back(MenuItem::separator()); m.items.push_back(makeActionItem(reg, theme, "export-semanno", "Export Semanno")); bar.addMenu(m); } static void appendWorkflowMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "Workflow"; m.items.push_back(makeActionItem(reg, theme, "create-workflow", "Create Workflow")); m.items.push_back(makeActionItem(reg, theme, "route-all", "Route All")); m.items.push_back(makeActionItem(reg, theme, "view-ready-tasks", "View Ready Tasks")); m.items.push_back(makeActionItem(reg, theme, "save-workflow", "Save Workflow")); bar.addMenu(m); } static void appendHelpMenu(MenuBar& bar, const KeybindingRegistry& reg, const WhetstoneTheme& theme) { Menu m; m.title = "Help"; MenuItem about = makeActionItem(reg, theme, "about", "About"); MenuItem shortcuts = makeActionItem(reg, theme, "keyboard-shortcuts", "Keyboard Shortcuts"); MenuItem docs; docs.id = "docs"; docs.label = "Documentation"; docs.hasSubmenu = true; docs.submenuItems.push_back(makeActionItem(reg, theme, "docs-getting-started", "Getting Started")); docs.submenuItems.push_back(makeActionItem(reg, theme, "docs-keybindings", "Keybindings")); m.items.push_back(about); m.items.push_back(shortcuts); m.items.push_back(MenuItem::separator()); m.items.push_back(docs); bar.addMenu(m); } std::vector menus_; std::string openMenu_; };