diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index eeafaa522..ec945dd17 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -277,9 +277,11 @@ namespace hex { namespace impl { - using DrawCallback = std::function; - using LayoutFunction = std::function; - using ClickCallback = std::function; + using DrawCallback = std::function; + using MenuCallback = std::function; + using EnabledCallback = std::function; + using LayoutFunction = std::function; + using ClickCallback = std::function; struct Layout { std::string unlocalizedName; @@ -291,8 +293,10 @@ namespace hex { }; struct MenuItem { - std::string unlocalizedName; - DrawCallback callback; + std::vector unlocalizedNames; + Shortcut shortcut; + MenuCallback callback; + EnabledCallback enabledCallback; }; struct SidebarItem { @@ -306,10 +310,16 @@ namespace hex { ClickCallback callback; }; + constexpr static auto SeparatorValue = "$SEPARATOR$"; + constexpr static auto SubMenuValue = "$SUBMENU$"; + } void registerMainMenuItem(const std::string &unlocalizedName, u32 priority); - void addMenuItem(const std::string &unlocalizedMainMenuName, u32 priority, const impl::DrawCallback &function); + void addMenuItem(const std::string &unlocalizedMainMenuNames, u32 priority, const impl::DrawCallback &function); + void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; }); + void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; }); + void addMenuItemSeparator(std::vector unlocalizedMainMenuNames, u32 priority); void addWelcomeScreenEntry(const impl::DrawCallback &function); void addFooterItem(const impl::DrawCallback &function); diff --git a/lib/libimhex/include/hex/api/keybinding.hpp b/lib/libimhex/include/hex/api/keybinding.hpp index 48f391aa0..b68ea4464 100644 --- a/lib/libimhex/include/hex/api/keybinding.hpp +++ b/lib/libimhex/include/hex/api/keybinding.hpp @@ -6,6 +6,7 @@ #include #include #include +#include struct ImGuiWindow; @@ -136,15 +137,30 @@ namespace hex { auto operator<=>(const Key &) const = default; + [[nodiscard]] constexpr u32 getKeyCode() const { return this->m_key; } private: u32 m_key; }; + + constexpr static auto CTRL = Key(static_cast(0x1000'0000)); + constexpr static auto ALT = Key(static_cast(0x2000'0000)); + constexpr static auto SHIFT = Key(static_cast(0x4000'0000)); + constexpr static auto SUPER = Key(static_cast(0x8000'0000)); + + #if defined (OS_MACOS) + constexpr static auto CTRLCMD = SUPER; + #else + constexpr static auto CTRLCMD = CTRL; + #endif + class Shortcut { public: Shortcut() = default; Shortcut(Keys key) : m_keys({ key }) { } + const static inline auto None = Keys(0); + Shortcut operator+(const Key &other) const { Shortcut result = *this; result.m_keys.insert(other); @@ -166,6 +182,168 @@ namespace hex { return this->m_keys == other.m_keys; } + std::string toString() const { + std::string result; + + #if defined(OS_MACOS) + constexpr static auto CTRL_NAME = "CTRL"; + constexpr static auto ALT_NAME = "OPT"; + constexpr static auto SHIFT_NAME = "SHIFT"; + constexpr static auto SUPER_NAME = "CMD"; + #else + constexpr static auto CTRL_NAME = "CTRL"; + constexpr static auto ALT_NAME = "ALT"; + constexpr static auto SHIFT_NAME = "SHIFT"; + constexpr static auto SUPER_NAME = "SUPER"; + #endif + + constexpr static auto Concatination = " + "; + + auto keys = this->m_keys; + if (keys.erase(CTRL) > 0) { + result += CTRL_NAME; + result += Concatination; + } + if (keys.erase(ALT) > 0) { + result += ALT_NAME; + result += Concatination; + } + if (keys.erase(SHIFT) > 0) { + result += SHIFT_NAME; + result += Concatination; + } + if (keys.erase(SUPER) > 0) { + result += SUPER_NAME; + result += Concatination; + } + + for (const auto &key : keys) { + switch (Keys(key.getKeyCode())) { + case Keys::Space: result += "SPACE"; break; + case Keys::Apostrophe: result += "'"; break; + case Keys::Comma: result += ","; break; + case Keys::Minus: result += "-"; break; + case Keys::Period: result += "."; break; + case Keys::Slash: result += "/"; break; + case Keys::Num0: result += "0"; break; + case Keys::Num1: result += "1"; break; + case Keys::Num2: result += "2"; break; + case Keys::Num3: result += "3"; break; + case Keys::Num4: result += "4"; break; + case Keys::Num5: result += "5"; break; + case Keys::Num6: result += "6"; break; + case Keys::Num7: result += "7"; break; + case Keys::Num8: result += "8"; break; + case Keys::Num9: result += "9"; break; + case Keys::Semicolon: result += ";"; break; + case Keys::Equals: result += "="; break; + case Keys::A: result += "A"; break; + case Keys::B: result += "B"; break; + case Keys::C: result += "C"; break; + case Keys::D: result += "D"; break; + case Keys::E: result += "E"; break; + case Keys::F: result += "F"; break; + case Keys::G: result += "G"; break; + case Keys::H: result += "H"; break; + case Keys::I: result += "I"; break; + case Keys::J: result += "J"; break; + case Keys::K: result += "K"; break; + case Keys::L: result += "L"; break; + case Keys::M: result += "M"; break; + case Keys::N: result += "N"; break; + case Keys::O: result += "O"; break; + case Keys::P: result += "P"; break; + case Keys::Q: result += "Q"; break; + case Keys::R: result += "R"; break; + case Keys::S: result += "S"; break; + case Keys::T: result += "T"; break; + case Keys::U: result += "U"; break; + case Keys::V: result += "V"; break; + case Keys::W: result += "W"; break; + case Keys::X: result += "X"; break; + case Keys::Y: result += "Y"; break; + case Keys::Z: result += "Z"; break; + case Keys::LeftBracket: result += "["; break; + case Keys::Backslash: result += "\\"; break; + case Keys::RightBracket: result += "]"; break; + case Keys::GraveAccent: result += "`"; break; + case Keys::World1: result += "WORLD1"; break; + case Keys::World2: result += "WORLD2"; break; + case Keys::Escape: result += "ESC"; break; + case Keys::Enter: result += "ENTER"; break; + case Keys::Tab: result += "TAB"; break; + case Keys::Backspace: result += "BACKSPACE"; break; + case Keys::Insert: result += "INSERT"; break; + case Keys::Delete: result += "DELETE"; break; + case Keys::Right: result += "RIGHT"; break; + case Keys::Left: result += "LEFT"; break; + case Keys::Down: result += "DOWN"; break; + case Keys::Up: result += "UP"; break; + case Keys::PageUp: result += "PAGEUP"; break; + case Keys::PageDown: result += "PAGEDOWN"; break; + case Keys::Home: result += "HOME"; break; + case Keys::End: result += "END"; break; + case Keys::CapsLock: result += "CAPSLOCK"; break; + case Keys::ScrollLock: result += "SCROLLLOCK"; break; + case Keys::NumLock: result += "NUMLOCK"; break; + case Keys::PrintScreen: result += "PRINTSCREEN"; break; + case Keys::Pause: result += "PAUSE"; break; + case Keys::F1: result += "F1"; break; + case Keys::F2: result += "F2"; break; + case Keys::F3: result += "F3"; break; + case Keys::F4: result += "F4"; break; + case Keys::F5: result += "F5"; break; + case Keys::F6: result += "F6"; break; + case Keys::F7: result += "F7"; break; + case Keys::F8: result += "F8"; break; + case Keys::F9: result += "F9"; break; + case Keys::F10: result += "F10"; break; + case Keys::F11: result += "F11"; break; + case Keys::F12: result += "F12"; break; + case Keys::F13: result += "F13"; break; + case Keys::F14: result += "F14"; break; + case Keys::F15: result += "F15"; break; + case Keys::F16: result += "F16"; break; + case Keys::F17: result += "F17"; break; + case Keys::F18: result += "F18"; break; + case Keys::F19: result += "F19"; break; + case Keys::F20: result += "F20"; break; + case Keys::F21: result += "F21"; break; + case Keys::F22: result += "F22"; break; + case Keys::F23: result += "F23"; break; + case Keys::F24: result += "F24"; break; + case Keys::F25: result += "F25"; break; + case Keys::KeyPad0: result += "KP0"; break; + case Keys::KeyPad1: result += "KP1"; break; + case Keys::KeyPad2: result += "KP2"; break; + case Keys::KeyPad3: result += "KP3"; break; + case Keys::KeyPad4: result += "KP4"; break; + case Keys::KeyPad5: result += "KP5"; break; + case Keys::KeyPad6: result += "KP6"; break; + case Keys::KeyPad7: result += "KP7"; break; + case Keys::KeyPad8: result += "KP8"; break; + case Keys::KeyPad9: result += "KP9"; break; + case Keys::KeyPadDecimal: result += "KPDECIMAL"; break; + case Keys::KeyPadDivide: result += "KPDIVIDE"; break; + case Keys::KeyPadMultiply: result += "KPMULTIPLY"; break; + case Keys::KeyPadSubtract: result += "KPSUBTRACT"; break; + case Keys::KeyPadAdd: result += "KPADD"; break; + case Keys::KeyPadEnter: result += "KPENTER"; break; + case Keys::KeyPadEqual: result += "KPEQUAL"; break; + case Keys::Menu: result += "MENU"; break; + default: + continue; + } + + result += " + "; + } + + if (result.ends_with(" + ")) + result = result.substr(0, result.size() - 3); + + return result; + } + private: friend Shortcut operator+(const Key &lhs, const Key &rhs); @@ -179,29 +357,6 @@ namespace hex { return result; } - constexpr static auto CTRL = Key(static_cast(0x1000'0000)); - constexpr static auto ALT = Key(static_cast(0x2000'0000)); - constexpr static auto SHIFT = Key(static_cast(0x4000'0000)); - constexpr static auto SUPER = Key(static_cast(0x8000'0000)); - -#if defined(OS_MACOS) - constexpr static auto CTRLCMD = SUPER; - - constexpr static auto CTRL_NAME = "CTRL"; - constexpr static auto ALT_NAME = "OPT"; - constexpr static auto SHIFT_NAME = "SHIFT"; - constexpr static auto SUPER_NAME = "CMD"; - constexpr static auto CTRLCMD_NAME = SUPER_NAME; -#else - constexpr static auto CTRLCMD = CTRL; - - constexpr static auto CTRL_NAME = "CTRL"; - constexpr static auto ALT_NAME = "ALT"; - constexpr static auto SHIFT_NAME = "SHIFT"; - constexpr static auto SUPER_NAME = "SUPER"; - constexpr static auto CTRLCMD_NAME = CTRL_NAME; -#endif - class ShortcutManager { public: static void addGlobalShortcut(const Shortcut &shortcut, const std::function &callback); diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 5479d7d32..65177748e 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -474,11 +474,29 @@ namespace hex { getMainMenuItems().insert({ priority, { unlocalizedName } }); } - void addMenuItem(const std::string &unlocalizedMainMenuName, u32 priority, const impl::DrawCallback &function) { - log::debug("Added new menu item to menu {} with priority {}", unlocalizedMainMenuName, priority); + void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { + log::debug("Added new menu item to menu {} with priority {}", wolv::util::combineStrings(unlocalizedMainMenuNames, " -> "), priority); getMenuItems().insert({ - priority, {unlocalizedMainMenuName, function} + priority, { unlocalizedMainMenuNames, shortcut, function, enabledCallback } + }); + + ShortcutManager::addGlobalShortcut(shortcut, function); + } + + void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { + log::debug("Added new menu item sub menu to menu {} with priority {}", wolv::util::combineStrings(unlocalizedMainMenuNames, " -> "), priority); + + unlocalizedMainMenuNames.emplace_back(impl::SubMenuValue); + getMenuItems().insert({ + priority, { unlocalizedMainMenuNames, {}, function, enabledCallback } + }); + } + + void addMenuItemSeparator(std::vector unlocalizedMainMenuNames, u32 priority) { + unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue); + getMenuItems().insert({ + priority, { unlocalizedMainMenuNames, {}, []{}, []{ return true; } } }); } diff --git a/main/source/window/window.cpp b/main/source/window/window.cpp index 8b5721b34..2dc33ba88 100644 --- a/main/source/window/window.cpp +++ b/main/source/window/window.cpp @@ -272,6 +272,27 @@ namespace hex { } } + static void createNestedMenu(std::span menuItems, const Shortcut &shortcut, const std::function &callback, const std::function &enabledCallback) { + const auto &name = menuItems.front(); + + if (name == ContentRegistry::Interface::impl::SeparatorValue) { + ImGui::Separator(); + return; + } + + if (name == ContentRegistry::Interface::impl::SubMenuValue) { + callback(); + } else if (menuItems.size() == 1) { + if (ImGui::MenuItem(LangEntry(name), shortcut.toString().c_str(), false, enabledCallback())) + callback(); + } else { + if (ImGui::BeginMenu(LangEntry(name))) { + createNestedMenu({ menuItems.begin() + 1, menuItems.end() }, shortcut, callback, enabledCallback); + ImGui::EndMenu(); + } + } + } + void Window::frameBegin() { // Start new ImGui Frame ImGui_ImplOpenGL3_NewFrame(); @@ -389,16 +410,9 @@ namespace hex { } } - std::set encounteredMenus; for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) { - if (ImGui::BeginMenu(LangEntry(menuItem.unlocalizedName))) { - auto [iter, inserted] = encounteredMenus.insert(menuItem.unlocalizedName); - if (!inserted) - ImGui::Separator(); - - menuItem.callback(); - ImGui::EndMenu(); - } + const auto &[unlocalizedNames, shortcut, callback, enabledCallback] = menuItem; + createNestedMenu(unlocalizedNames, shortcut, callback, enabledCallback); } this->drawTitleBar(); @@ -509,6 +523,16 @@ namespace hex { // Run all deferred calls TaskManager::runDeferredCalls(); + // Draw main menu popups + for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) { + const auto &[unlocalizedNames, shortcut, callback, enabledCallback] = menuItem; + + if (ImGui::BeginPopup(unlocalizedNames.front().c_str())) { + createNestedMenu({ unlocalizedNames.begin() + 1, unlocalizedNames.end() }, shortcut, callback, enabledCallback); + ImGui::EndPopup(); + } + } + EventManager::post(); } diff --git a/plugins/builtin/romfs/lang/base.json b/plugins/builtin/romfs/lang/base.json index 1dfdf0caa..9434c034b 100644 --- a/plugins/builtin/romfs/lang/base.json +++ b/plugins/builtin/romfs/lang/base.json @@ -154,7 +154,7 @@ "hex.builtin.menu.file.open_project", "hex.builtin.menu.file.open_recent", "hex.builtin.menu.file.quit", - "hex.builtin.menu.file.reload_file", + "hex.builtin.menu.file.reload_provider", "hex.builtin.menu.file.save_project", "hex.builtin.menu.file.save_project_as", "hex.builtin.menu.help", diff --git a/plugins/builtin/romfs/lang/de_DE.json b/plugins/builtin/romfs/lang/de_DE.json index 62d158c86..2e684247f 100644 --- a/plugins/builtin/romfs/lang/de_DE.json +++ b/plugins/builtin/romfs/lang/de_DE.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "Projekt öffnen...", "hex.builtin.menu.file.open_recent": "Zuletzt geöffnete Dateien", "hex.builtin.menu.file.quit": "ImHex beenden", - "hex.builtin.menu.file.reload_file": "Datei neu laden", + "hex.builtin.menu.file.reload_provider": "Provider neu laden", "hex.builtin.menu.file.save_project": "Projekt speichern", "hex.builtin.menu.file.save_project_as": "Projekt speichern unter...", "hex.builtin.menu.help": "Hilfe", diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 544868125..72f8da7bb 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -150,6 +150,9 @@ "hex.builtin.menu.file.export.ips.popup.missing_eof_error": "Missing IPS EOF record!", "hex.builtin.menu.file.export.ips": "IPS Patch", "hex.builtin.menu.file.export.ips32": "IPS32 Patch", + "hex.builtin.menu.file.export.bookmark": "Bookmark", + "hex.builtin.menu.file.export.pattern": "Pattern File", + "hex.builtin.menu.file.export.data_processor": "Data Processor Workspace", "hex.builtin.menu.file.export.popup.create": "Cannot export data. Failed to create file!", "hex.builtin.menu.file.export.title": "Export File", "hex.builtin.menu.file.import": "Import...", @@ -159,14 +162,19 @@ "hex.builtin.menu.file.import.ips": "IPS Patch", "hex.builtin.menu.file.import.ips32": "IPS32 Patch", "hex.builtin.menu.file.import.modified_file": "Modified File", + "hex.builtin.menu.file.import.bookmark": "Bookmark", + "hex.builtin.menu.file.import.pattern": "Pattern File", + "hex.builtin.menu.file.import.data_processor": "Data Processor Workspace", + "hex.builtin.menu.file.import.custom_encoding": "Custom Encoding File", "hex.builtin.menu.file.open_file": "Open File...", "hex.builtin.menu.file.open_other": "Open Other...", - "hex.builtin.menu.file.open_project": "Open Project...", + "hex.builtin.menu.file.project": "Project", + "hex.builtin.menu.file.project.open": "Open Project...", + "hex.builtin.menu.file.project.save": "Save Project", + "hex.builtin.menu.file.project.save_as": "Save Project As...", "hex.builtin.menu.file.open_recent": "Open Recent", "hex.builtin.menu.file.quit": "Quit ImHex", - "hex.builtin.menu.file.reload_file": "Reload File", - "hex.builtin.menu.file.save_project": "Save Project", - "hex.builtin.menu.file.save_project_as": "Save Project As...", + "hex.builtin.menu.file.reload_provider": "Reload Provider", "hex.builtin.menu.help": "Help", "hex.builtin.menu.layout": "Layout", "hex.builtin.menu.view": "View", diff --git a/plugins/builtin/romfs/lang/it_IT.json b/plugins/builtin/romfs/lang/it_IT.json index 9bff14977..781b63b51 100644 --- a/plugins/builtin/romfs/lang/it_IT.json +++ b/plugins/builtin/romfs/lang/it_IT.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "Apri un Progetto...", "hex.builtin.menu.file.open_recent": "File recenti", "hex.builtin.menu.file.quit": "Uscita ImHex", - "hex.builtin.menu.file.reload_file": "", + "hex.builtin.menu.file.reload_provider": "", "hex.builtin.menu.file.save_project": "Salva Progetto", "hex.builtin.menu.file.save_project_as": "", "hex.builtin.menu.help": "Aiuto", diff --git a/plugins/builtin/romfs/lang/ja_JP.json b/plugins/builtin/romfs/lang/ja_JP.json index 40749889d..ae1b2d72d 100644 --- a/plugins/builtin/romfs/lang/ja_JP.json +++ b/plugins/builtin/romfs/lang/ja_JP.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "プロジェクトを開く…", "hex.builtin.menu.file.open_recent": "最近使用したファイル", "hex.builtin.menu.file.quit": "ImHexを終了", - "hex.builtin.menu.file.reload_file": "", + "hex.builtin.menu.file.reload_provider": "", "hex.builtin.menu.file.save_project": "プロジェクトを保存", "hex.builtin.menu.file.save_project_as": "", "hex.builtin.menu.help": "ヘルプ", diff --git a/plugins/builtin/romfs/lang/ko_KR.json b/plugins/builtin/romfs/lang/ko_KR.json index bcf641010..0963034f0 100644 --- a/plugins/builtin/romfs/lang/ko_KR.json +++ b/plugins/builtin/romfs/lang/ko_KR.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "프로젝트 열기...", "hex.builtin.menu.file.open_recent": "최근 파일", "hex.builtin.menu.file.quit": "ImHex 종료하기", - "hex.builtin.menu.file.reload_file": "", + "hex.builtin.menu.file.reload_provider": "", "hex.builtin.menu.file.save_project": "프로젝트 저장", "hex.builtin.menu.file.save_project_as": "", "hex.builtin.menu.help": "도움말", diff --git a/plugins/builtin/romfs/lang/pt_BR.json b/plugins/builtin/romfs/lang/pt_BR.json index c9eb47126..eff550801 100644 --- a/plugins/builtin/romfs/lang/pt_BR.json +++ b/plugins/builtin/romfs/lang/pt_BR.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "Abrir Projeto...", "hex.builtin.menu.file.open_recent": "Abrir Recentes", "hex.builtin.menu.file.quit": "Sair do ImHex", - "hex.builtin.menu.file.reload_file": "", + "hex.builtin.menu.file.reload_provider": "", "hex.builtin.menu.file.save_project": "Salvar Projeto", "hex.builtin.menu.file.save_project_as": "", "hex.builtin.menu.help": "Ajuda", diff --git a/plugins/builtin/romfs/lang/zh_CN.json b/plugins/builtin/romfs/lang/zh_CN.json index 63748f576..e1fe4b755 100644 --- a/plugins/builtin/romfs/lang/zh_CN.json +++ b/plugins/builtin/romfs/lang/zh_CN.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "打开项目...", "hex.builtin.menu.file.open_recent": "最近打开", "hex.builtin.menu.file.quit": "退出 ImHex", - "hex.builtin.menu.file.reload_file": "重新加载文件", + "hex.builtin.menu.file.reload_file": "", "hex.builtin.menu.file.save_project": "保存项目", "hex.builtin.menu.file.save_project_as": "另存项目为...", "hex.builtin.menu.help": "帮助", diff --git a/plugins/builtin/romfs/lang/zh_TW.json b/plugins/builtin/romfs/lang/zh_TW.json index 4d3e67f4c..68b1b2c26 100644 --- a/plugins/builtin/romfs/lang/zh_TW.json +++ b/plugins/builtin/romfs/lang/zh_TW.json @@ -159,7 +159,7 @@ "hex.builtin.menu.file.open_project": "開啟專案...", "hex.builtin.menu.file.open_recent": "開啟近期", "hex.builtin.menu.file.quit": "退出 ImHex", - "hex.builtin.menu.file.reload_file": "重新載入檔案", + "hex.builtin.menu.file.reload_file": "", "hex.builtin.menu.file.save_project": "儲存專案", "hex.builtin.menu.file.save_project_as": "另存專案為...", "hex.builtin.menu.help": "幫助", diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index 6c15cedf6..957b50e34 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -20,368 +20,419 @@ namespace hex::plugin::builtin { static bool g_demoWindowOpen = false; - void handleIPSError(IPSError error) { - TaskManager::doLater([error]{ - switch (error) { - case IPSError::InvalidPatchHeader: - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang); - break; - case IPSError::AddressOutOfRange: - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang); - break; - case IPSError::PatchTooLarge: - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang); - break; - case IPSError::InvalidPatchFormat: - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang); - break; - case IPSError::MissingEOF: - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang); - break; - } - }); + namespace { + + bool noRunningTasks() { + return TaskManager::getRunningTaskCount() == 0; + } + + bool noRunningTaskAndValidProvider() { + return noRunningTasks() && ImHexApi::Provider::isValid(); + } + } + namespace { + + void handleIPSError(IPSError error) { + TaskManager::doLater([error]{ + switch (error) { + case IPSError::InvalidPatchHeader: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang); + break; + case IPSError::AddressOutOfRange: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang); + break; + case IPSError::PatchTooLarge: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang); + break; + case IPSError::InvalidPatchFormat: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang); + break; + case IPSError::MissingEOF: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang); + break; + } + }); + } + + } + + // Import + namespace { + + void importBase64() { + fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { + wolv::io::File inputFile(path, wolv::io::File::Mode::Read); + if (!inputFile.isValid()) { + View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.open_error"_lang); + return; + } + + auto base64 = inputFile.readBytes(); + + if (!base64.empty()) { + auto data = crypt::decode64(base64); + + if (data.empty()) + View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang); + else { + fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const std::fs::path &path) { + wolv::io::File outputFile(path, wolv::io::File::Mode::Create); + + if (!outputFile.isValid()) + View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang); + + outputFile.write(data); + }); + } + } else { + View::showErrorPopup("hex.builtin.popup.file_open_error"_lang); + } + }); + } + + void importIPSPatch() { + fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { + auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); + auto patch = hex::loadIPSPatch(patchData); + if (!patch.has_value()) { + handleIPSError(patch.error()); + return; + } + + task.setMaxValue(patch->size()); + + auto provider = ImHexApi::Provider::get(); + + u64 progress = 0; + for (auto &[address, value] : *patch) { + provider->addPatch(address, &value, 1); + progress++; + task.update(progress); + } + + provider->createUndoPoint(); + }); + }); + } + + void importIPS32Patch() { + fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { + auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); + auto patch = hex::loadIPS32Patch(patchData); + if (!patch.has_value()) { + handleIPSError(patch.error()); + return; + } + + task.setMaxValue(patch->size()); + + auto provider = ImHexApi::Provider::get(); + + u64 progress = 0; + for (auto &[address, value] : *patch) { + provider->addPatch(address, &value, 1); + progress++; + task.update(progress); + } + + provider->createUndoPoint(); + }); + }); + } + + void importModifiedFile() { + fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { + auto provider = ImHexApi::Provider::get(); + auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); + + if (patchData.size() != provider->getActualSize()) { + View::showErrorPopup("hex.builtin.menu.file.import.modified_file.popup.invalid_size"_lang); + return; + } + + const auto baseAddress = provider->getBaseAddress(); + + std::map patches; + for (u64 i = 0; i < patchData.size(); i++) { + u8 value = 0; + provider->read(baseAddress + i, &value, 1); + + if (value != patchData[i]) + patches[baseAddress + i] = patchData[i]; + } + + task.setMaxValue(patches.size()); + + u64 progress = 0; + for (auto &[address, value] : patches) { + provider->addPatch(address, &value, 1); + progress++; + task.update(progress); + } + + provider->createUndoPoint(); + }); + }); + } + + } + + // Export + namespace { + + void exportBase64() { + fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) { + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &) { + wolv::io::File outputFile(path, wolv::io::File::Mode::Create); + if (!outputFile.isValid()) { + TaskManager::doLater([] { + View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang); + }); + return; + } + + auto provider = ImHexApi::Provider::get(); + std::vector bytes(3000); + for (u64 address = 0; address < provider->getActualSize(); address += 3000) { + bytes.resize(std::min(3000, provider->getActualSize() - address)); + provider->read(provider->getBaseAddress() + address, bytes.data(), bytes.size()); + + outputFile.write(crypt::encode64(bytes)); + } + }); + }); + } + + void exportIPSPatch() { + auto provider = ImHexApi::Provider::get(); + + Patches patches = provider->getPatches(); + + // Make sure there's no patch at address 0x00454F46 because that would cause the patch to contain the sequence "EOF" which signals the end of the patch + if (!patches.contains(0x00454F45) && patches.contains(0x00454F46)) { + u8 value = 0; + provider->read(0x00454F45, &value, sizeof(u8)); + patches[0x00454F45] = value; + } + + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) { + auto data = generateIPSPatch(patches); + + TaskManager::doLater([data] { + fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { + auto file = wolv::io::File(path, wolv::io::File::Mode::Create); + if (!file.isValid()) { + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); + return; + } + + if (data.has_value()) + file.write(data.value()); + else { + handleIPSError(data.error()); + } + }); + }); + }); + } + + void exportIPS32Patch() { + auto provider = ImHexApi::Provider::get(); + + Patches patches = provider->getPatches(); + + // Make sure there's no patch at address 0x45454F46 because that would cause the patch to contain the sequence "*EOF" which signals the end of the patch + if (!patches.contains(0x45454F45) && patches.contains(0x45454F46)) { + u8 value = 0; + provider->read(0x45454F45, &value, sizeof(u8)); + patches[0x45454F45] = value; + } + + TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) { + auto data = generateIPS32Patch(patches); + + TaskManager::doLater([data] { + fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { + auto file = wolv::io::File(path, wolv::io::File::Mode::Create); + if (!file.isValid()) { + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); + return; + } + + if (data.has_value()) + file.write(data.value()); + else + handleIPSError(data.error()); + }); + }); + }); + } + + } + + + static void createFileMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.file", 1000); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1050, [&] { - bool taskRunning = TaskManager::getRunningTaskCount() > 0; + /* Create File */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.create_file" }, 1050, CTRLCMD + Keys::N, [] { + auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); + if (newProvider != nullptr && !newProvider->open()) + hex::ImHexApi::Provider::remove(newProvider); + else + EventManager::post(newProvider); + }, noRunningTasks); - if (ImGui::MenuItem("hex.builtin.menu.file.create_file"_lang, (CTRLCMD_NAME + " + N"s).c_str(), false, !taskRunning)) { - auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); - if (newProvider != nullptr && !newProvider->open()) - hex::ImHexApi::Provider::remove(newProvider); - else - EventManager::post(newProvider); + /* Open File */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, 1100, CTRLCMD + Keys::O, [] { + EventManager::post("Open File"); + }, noRunningTasks); + + /* Open Other */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, 1150, Shortcut::None, [] { + for (const auto &unlocalizedProviderName : ContentRegistry::Provider::getEntries()) { + if (ImGui::MenuItem(LangEntry(unlocalizedProviderName))) + ImHexApi::Provider::createProvider(unlocalizedProviderName); } + }, noRunningTasks); - if (ImGui::MenuItem("hex.builtin.menu.file.open_file"_lang, (CTRLCMD_NAME + " + O"s).c_str(), false, !taskRunning)) { - EventManager::post("Open File"); - } + /* Reload Provider */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.reload_provider"}, 1250, CTRLCMD + Keys::R, [] { + auto provider = ImHexApi::Provider::get(); - if (ImGui::BeginMenu("hex.builtin.menu.file.open_other"_lang, !taskRunning)) { + provider->close(); + if (!provider->open()) + ImHexApi::Provider::remove(provider, true); + }, noRunningTaskAndValidProvider); - for (const auto &unlocalizedProviderName : ContentRegistry::Provider::getEntries()) { - if (ImGui::MenuItem(LangEntry(unlocalizedProviderName))) { - ImHexApi::Provider::createProvider(unlocalizedProviderName); - } - } - - ImGui::EndMenu(); - } - - if (ImGui::MenuItem("hex.builtin.menu.file.reload_file"_lang, (CTRLCMD_NAME + " + R"s).c_str(), false, !taskRunning && ImHexApi::Provider::isValid())) { - auto provider = ImHexApi::Provider::get(); - - provider->close(); - if (!provider->open()) - ImHexApi::Provider::remove(provider, true); - } - }); - - /* File close, quit imhex */ - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 5000, [&] { - bool providerValid = ImHexApi::Provider::isValid(); - bool taskRunning = TaskManager::getRunningTaskCount() > 0; - - if (ImGui::MenuItem("hex.builtin.menu.file.close"_lang, (CTRLCMD_NAME + " + W"s).c_str(), false, providerValid && !taskRunning)) { - ImHexApi::Provider::remove(ImHexApi::Provider::get()); - } - - if (ImGui::MenuItem("hex.builtin.menu.file.quit"_lang, "Alt + F4")) { - ImHexApi::Common::closeImHex(); - } - }); /* Project open / save */ - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1150, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - bool taskRunning = TaskManager::getRunningTaskCount() > 0; + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.open" }, 1400, + ALT + Keys::O, + openProject, noRunningTasks); - if (ImGui::MenuItem("hex.builtin.menu.file.open_project"_lang, "", false, !taskRunning)) { - openProject(); - } + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save" }, 1450, + ALT + Keys::S, + saveProject, [&] { return noRunningTaskAndValidProvider() && ProjectFile::hasPath(); }); - if (ImGui::MenuItem("hex.builtin.menu.file.save_project"_lang, (ALT_NAME + " + S"s).c_str(), false, providerValid && ProjectFile::hasPath())) { - saveProject(); - } - - if (ImGui::MenuItem("hex.builtin.menu.file.save_project_as"_lang, (ALT_NAME + " + "s + SHIFT_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) { - saveProjectAs(); - } - }); - - /* Import / Export */ - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1300, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - bool taskRunning = TaskManager::getRunningTaskCount() > 0; - - /* Import */ - if (ImGui::BeginMenu("hex.builtin.menu.file.import"_lang, !taskRunning)) { - if (ImGui::MenuItem("hex.builtin.menu.file.import.base64"_lang)) { - - fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { - wolv::io::File inputFile(path, wolv::io::File::Mode::Read); - if (!inputFile.isValid()) { - View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.open_error"_lang); - return; - } - - auto base64 = inputFile.readBytes(); - - if (!base64.empty()) { - auto data = crypt::decode64(base64); - - if (data.empty()) - View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang); - else { - fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const std::fs::path &path) { - wolv::io::File outputFile(path, wolv::io::File::Mode::Create); - - if (!outputFile.isValid()) - View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang); - - outputFile.write(data); - }); - } - } else { - View::showErrorPopup("hex.builtin.popup.file_open_error"_lang); - } - }); - } - - ImGui::Separator(); - - if (ImGui::MenuItem("hex.builtin.menu.file.import.ips"_lang, nullptr, false)) { - - fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { - auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); - auto patch = hex::loadIPSPatch(patchData); - if (!patch.has_value()) { - handleIPSError(patch.error()); - return; - } - - task.setMaxValue(patch->size()); - - auto provider = ImHexApi::Provider::get(); - - u64 progress = 0; - for (auto &[address, value] : *patch) { - provider->addPatch(address, &value, 1); - progress++; - task.update(progress); - } - - provider->createUndoPoint(); - }); - }); - } - - if (ImGui::MenuItem("hex.builtin.menu.file.import.ips32"_lang, nullptr, false)) { - fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { - auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); - auto patch = hex::loadIPS32Patch(patchData); - if (!patch.has_value()) { - handleIPSError(patch.error()); - return; - } - - task.setMaxValue(patch->size()); - - auto provider = ImHexApi::Provider::get(); - - u64 progress = 0; - for (auto &[address, value] : *patch) { - provider->addPatch(address, &value, 1); - progress++; - task.update(progress); - } - - provider->createUndoPoint(); - }); - }); - } - - ImGui::Separator(); - - if (ImGui::MenuItem("hex.builtin.menu.file.import.modified_file"_lang, nullptr, false)) { - fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { - auto provider = ImHexApi::Provider::get(); - auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes(); - - if (patchData.size() != provider->getActualSize()) { - View::showErrorPopup("hex.builtin.menu.file.import.modified_file.popup.invalid_size"_lang); - return; - } - - const auto baseAddress = provider->getBaseAddress(); - - std::map patches; - for (u64 i = 0; i < patchData.size(); i++) { - u8 value = 0; - provider->read(baseAddress + i, &value, 1); - - if (value != patchData[i]) - patches[baseAddress + i] = patchData[i]; - } - - task.setMaxValue(patches.size()); - - u64 progress = 0; - for (auto &[address, value] : patches) { - provider->addPatch(address, &value, 1); - progress++; - task.update(progress); - } - - provider->createUndoPoint(); - }); - }); - } - - ImGui::EndMenu(); - } + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save_as" }, 1500, + ALT + SHIFT + Keys::S, + saveProjectAs, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable(); }); - /* Export */ - if (ImGui::BeginMenu("hex.builtin.menu.file.export"_lang, providerValid && provider->isWritable())) { - if (ImGui::MenuItem("hex.builtin.menu.file.export.base64"_lang)) { + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 2000); - fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) { - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &) { - wolv::io::File outputFile(path, wolv::io::File::Mode::Create); - if (!outputFile.isValid()) { - TaskManager::doLater([] { - View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang); - }); - return; - } + /* Import */ + { + /* Base 64 */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.base64" }, 2050, + Shortcut::None, + importBase64, + noRunningTasks); - auto provider = ImHexApi::Provider::get(); - std::vector bytes(3000); - for (u64 address = 0; address < provider->getActualSize(); address += 3000) { - bytes.resize(std::min(3000, provider->getActualSize() - address)); - provider->read(provider->getBaseAddress() + address, bytes.data(), bytes.size()); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.import" }, 2100); - outputFile.write(crypt::encode64(bytes)); - } - }); - }); - } + /* IPS */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips"}, 2150, + Shortcut::None, + importIPSPatch, + ImHexApi::Provider::isValid); - ImGui::Separator(); + /* IPS32 */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips32"}, 2200, + Shortcut::None, + importIPS32Patch, + ImHexApi::Provider::isValid); - if (ImGui::MenuItem("hex.builtin.menu.file.export.ips"_lang, nullptr, false)) { - Patches patches = provider->getPatches(); + /* Modified File */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.modified_file" }, 2300, + Shortcut::None, + importModifiedFile, + [&] { return noRunningTaskAndValidProvider() && ImHexApi::Provider::get()->isWritable(); }); + } - // Make sure there's no patch at address 0x00454F46 because that would cause the patch to contain the sequence "EOF" which signals the end of the patch - if (!patches.contains(0x00454F45) && patches.contains(0x00454F46)) { - u8 value = 0; - provider->read(0x00454F45, &value, sizeof(u8)); - patches[0x00454F45] = value; - } + /* Export */ + { + /* Base 64 */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.base64" }, 6000, + Shortcut::None, + exportBase64, + ImHexApi::Provider::isValid); - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) { - auto data = generateIPSPatch(patches); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6050); - TaskManager::doLater([data] { - fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { - auto file = wolv::io::File(path, wolv::io::File::Mode::Create); - if (!file.isValid()) { - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); - return; - } + /* IPS */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips" }, 6100, + Shortcut::None, + exportIPSPatch, + ImHexApi::Provider::isValid); - if (data.has_value()) - file.write(data.value()); - else { - handleIPSError(data.error()); - } - }); - }); - }); - } + /* IPS32 */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips32" }, 6150, + Shortcut::None, + exportIPS32Patch, + ImHexApi::Provider::isValid); + } - if (ImGui::MenuItem("hex.builtin.menu.file.export.ips32"_lang, nullptr, false)) { - Patches patches = provider->getPatches(); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 10000); - // Make sure there's no patch at address 0x45454F46 because that would cause the patch to contain the sequence "*EOF" which signals the end of the patch - if (!patches.contains(0x45454F45) && patches.contains(0x45454F46)) { - u8 value = 0; - provider->read(0x45454F45, &value, sizeof(u8)); - patches[0x45454F45] = value; - } + /* Close Provider */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.close"}, 10050, CTRLCMD + Keys::W, [] { + ImHexApi::Provider::remove(ImHexApi::Provider::get()); + }, noRunningTaskAndValidProvider); - TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) { - auto data = generateIPS32Patch(patches); - - TaskManager::doLater([data] { - fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { - auto file = wolv::io::File(path, wolv::io::File::Mode::Create); - if (!file.isValid()) { - View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); - return; - } - - if (data.has_value()) - file.write(data.value()); - else - handleIPSError(data.error()); - }); - }); - }); - } - - ImGui::EndMenu(); - } + /* Quit ImHex */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.quit"}, 10100, ALT + Keys::F4, [] { + ImHexApi::Common::closeImHex(); }); } static void createEditMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.edit", 2000); - /* Provider Undo / Redo */ - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1000, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - - if (ImGui::MenuItem("hex.builtin.menu.edit.undo"_lang, (CTRLCMD_NAME + " + Z"s).c_str(), false, providerValid && provider->canUndo())) + /* Undo */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.undo" }, 1000, CTRLCMD + Keys::Z, [] { + auto provider = ImHexApi::Provider::get(); provider->undo(); - if (ImGui::MenuItem("hex.builtin.menu.edit.redo"_lang, (CTRLCMD_NAME + " + Y"s).c_str(), false, providerValid && provider->canRedo())) + }, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->canUndo(); }); + + /* Redo */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.redo" }, 1050, CTRLCMD + Keys::Y, [] { + auto provider = ImHexApi::Provider::get(); provider->redo(); - }); + }, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->canRedo(); }); } static void createViewMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.view", 3000); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.view", 1000, [] { + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.view" }, 1000, [] { for (auto &[name, view] : ContentRegistry::Views::getEntries()) { if (view->hasViewMenuItemEntry()) ImGui::MenuItem(LangEntry(view->getUnlocalizedName()), "", &view->getWindowOpenState()); } + + ImGui::Separator(); + + #if defined(DEBUG) + ImGui::MenuItem("hex.builtin.menu.view.demo"_lang, "", &g_demoWindowOpen); + #endif }); - #if defined(DEBUG) - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.view", 2000, [] { - ImGui::MenuItem("hex.builtin.menu.view.demo"_lang, "", &g_demoWindowOpen); - }); - #endif } static void createLayoutMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.layout", 4000); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.layout", 1000, [] { + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.layout" }, 1000, [] { for (auto &[layoutName, func] : ContentRegistry::Interface::getLayouts()) { if (ImGui::MenuItem(LangEntry(layoutName), "", false, ImHexApi::Provider::isValid())) { auto dock = ImHexApi::System::getMainDockSpaceId(); diff --git a/plugins/builtin/source/content/views/view_about.cpp b/plugins/builtin/source/content/views/view_about.cpp index 69d181557..5adcb8379 100644 --- a/plugins/builtin/source/content/views/view_about.cpp +++ b/plugins/builtin/source/content/views/view_about.cpp @@ -12,16 +12,14 @@ namespace hex::plugin::builtin { ViewAbout::ViewAbout() : View("hex.builtin.view.help.about.name") { - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 1000, [&, this] { - if (ImGui::MenuItem("hex.builtin.view.help.about.name"_lang, "")) { - TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.help.about.name").c_str()); }); - this->m_aboutWindowOpen = true; - this->getWindowOpenState() = true; - } + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.about.name" }, 1000, Shortcut::None, [this] { + TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.help.about.name").c_str()); }); + this->m_aboutWindowOpen = true; + this->getWindowOpenState() = true; + }); - if (ImGui::MenuItem("hex.builtin.view.help.documentation"_lang, "")) { - hex::openWebpage("https://imhex.werwolv.net/docs"); - } + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.documentation" }, 1050, Shortcut::None, [] { + hex::openWebpage("https://imhex.werwolv.net/docs"); }); } diff --git a/plugins/builtin/source/content/views/view_bookmarks.cpp b/plugins/builtin/source/content/views/view_bookmarks.cpp index dcd60a716..a81ffed0c 100644 --- a/plugins/builtin/source/content/views/view_bookmarks.cpp +++ b/plugins/builtin/source/content/views/view_bookmarks.cpp @@ -376,33 +376,37 @@ namespace hex::plugin::builtin { } void ViewBookmarks::registerMenuItems() { - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1050, [&] { - bool providerValid = ImHexApi::Provider::isValid(); - auto selection = ImHexApi::HexEditor::getSelection(); + /* Create bookmark */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.bookmark.create" }, 1900, CTRL + Keys::B, [&] { + auto selection = ImHexApi::HexEditor::getSelection(); + ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {}); + }, []{ return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid(); }); - if (ImGui::MenuItem("hex.builtin.menu.edit.bookmark.create"_lang, nullptr, false, selection.has_value() && providerValid)) { - ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {}); - } - }); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 4000, [&] { - bool providerValid = ImHexApi::Provider::isValid(); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.import" }, 3000); - if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.import"_lang, nullptr, false, providerValid)) { - fs::openFileBrowser(fs::DialogMode::Open, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) { - try { - importBookmarks(ImHexApi::Provider::get(), nlohmann::json::parse(wolv::io::File(path, wolv::io::File::Mode::Read).readString())); - } catch (...) { } - }); - } - if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.export"_lang, nullptr, false, providerValid && !ProviderExtraData::getCurrent().bookmarks.empty())) { - fs::openFileBrowser(fs::DialogMode::Save, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) { - nlohmann::json json; - exportBookmarks(ImHexApi::Provider::get(), json); + /* Import bookmarks */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.bookmark" }, 3050, Shortcut::None, []{ + fs::openFileBrowser(fs::DialogMode::Open, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) { + try { + importBookmarks(ImHexApi::Provider::get(), nlohmann::json::parse(wolv::io::File(path, wolv::io::File::Mode::Read).readString())); + } catch (...) { } + }); + }, ImHexApi::Provider::isValid); - wolv::io::File(path, wolv::io::File::Mode::Create).write(json.dump(4)); - }); - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6200); + + + /* Export bookmarks */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.bookmark" }, 6250, Shortcut::None, []{ + fs::openFileBrowser(fs::DialogMode::Save, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) { + nlohmann::json json; + exportBookmarks(ImHexApi::Provider::get(), json); + + wolv::io::File(path, wolv::io::File::Mode::Create).write(json.dump(4)); + }); + }, []{ + return ImHexApi::Provider::isValid() && !ProviderExtraData::getCurrent().bookmarks.empty(); }); } diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index 44122dd7b..34c4eed5b 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -124,6 +124,8 @@ namespace hex::plugin::builtin { if (!formatWriteFunction.empty()) { editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector { pattern->setValue(value); + + return { }; }; } diff --git a/plugins/builtin/source/content/views/view_data_processor.cpp b/plugins/builtin/source/content/views/view_data_processor.cpp index bd8564793..378f8e6ff 100644 --- a/plugins/builtin/source/content/views/view_data_processor.cpp +++ b/plugins/builtin/source/content/views/view_data_processor.cpp @@ -66,30 +66,33 @@ namespace hex::plugin::builtin { ViewDataProcessor::processNodes(workspace); }); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&] { - bool providerValid = ImHexApi::Provider::isValid(); - + /* Import bookmarks */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.data_processor" }, 4050, Shortcut::None, [this]{ auto &data = ProviderExtraData::getCurrent().dataProcessor; - if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.load_processor"_lang, nullptr, false, providerValid)) { - fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, - [&](const std::fs::path &path) { - wolv::io::File file(path, wolv::io::File::Mode::Read); - if (file.isValid()) { - ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString()); - this->m_updateNodePositions = true; - } - }); - } + fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, + [&](const std::fs::path &path) { + wolv::io::File file(path, wolv::io::File::Mode::Read); + if (file.isValid()) { + ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString()); + this->m_updateNodePositions = true; + } + }); + }, ImHexApi::Provider::isValid); - if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && providerValid)) { - fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, - [&](const std::fs::path &path) { - wolv::io::File file(path, wolv::io::File::Mode::Create); - if (file.isValid()) - file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4)); - }); - } + /* Export bookmarks */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.data_processor" }, 8050, Shortcut::None, []{ + auto &data = ProviderExtraData::getCurrent().dataProcessor; + + fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, + [&](const std::fs::path &path) { + wolv::io::File file(path, wolv::io::File::Mode::Create); + if (file.isValid()) + file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4)); + }); + }, []{ + auto &data = ProviderExtraData::getCurrent().dataProcessor; + return !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && ImHexApi::Provider::isValid(); }); ContentRegistry::FileHandler::add({ ".hexnode" }, [this](const auto &path) { diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 89b77962f..4f01ecac7 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -560,23 +560,7 @@ namespace hex::plugin::builtin { // Right click menu if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) - ImGui::OpenPopup("hex.builtin.menu.edit"_lang); - - if (ImGui::BeginPopup("hex.builtin.menu.edit"_lang)) { - bool needsSeparator = false; - for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) { - if (menuItem.unlocalizedName != "hex.builtin.menu.edit") - continue; - - if (needsSeparator) - ImGui::Separator(); - - menuItem.callback(); - needsSeparator = true; - } - - ImGui::EndPopup(); - } + EventManager::post("hex.builtin.menu.edit"); } void ViewHexEditor::drawContent() { @@ -667,6 +651,24 @@ namespace hex::plugin::builtin { ImGui::SetClipboardText(buffer.c_str()); } + static void copyCustomEncoding(const EncodingFile &customEncoding, const Region &selection) { + auto provider = ImHexApi::Provider::get(); + + std::vector buffer(customEncoding.getLongestSequence(), 0x00); + std::string string; + + u64 offset = selection.getStartAddress(); + while (offset < selection.getEndAddress()) { + provider->read(offset, buffer.data(), std::min(buffer.size(), selection.size - (offset - selection.getStartAddress()))); + auto [result, size] = customEncoding.getEncodingFor(buffer); + + string += result; + offset += size; + }; + + ImGui::SetClipboardText(string.c_str()); + } + void ViewHexEditor::registerShortcuts() { // Save operations ShortcutManager::addShortcut(this, CTRLCMD + Keys::S, [] { @@ -953,159 +955,235 @@ namespace hex::plugin::builtin { } void ViewHexEditor::registerMenuItems() { - // Basic operations - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1100, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.save"_lang, (CTRLCMD_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) { - save(); - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 1300); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.save_as"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) { - saveAs(); - } - }); + /* Save */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.save"_lang }, 1350, + CTRL + Keys::S, + save, + [] { + auto provider = ImHexApi::Provider::get(); + bool providerValid = ImHexApi::Provider::isValid(); + + return providerValid && provider->isWritable(); + }); + + /* Save As */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.save_as"_lang }, 1375, + CTRL + SHIFT + Keys::S, + saveAs, + [] { + auto provider = ImHexApi::Provider::get(); + bool providerValid = ImHexApi::Provider::isValid(); + + return providerValid && provider->isWritable(); + }); + + /* Load Encoding File */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.custom_encoding" }, 5050, + Shortcut::None, + [this]{ + std::vector paths; + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Encodings)) { + std::error_code error; + for (const auto &entry : std::fs::recursive_directory_iterator(path, error)) { + if (!entry.is_regular_file()) continue; + + paths.push_back(entry); + } + } + + View::showFileChooserPopup(paths, { {"Thingy Table File", "tbl"} }, false, + [this](const auto &path) { + this->m_hexEditor.setCustomEncoding(EncodingFile(EncodingFile::Type::Thingy, path)); + }); + }, + ImHexApi::Provider::isValid); + + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 1500); + + /* Search */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.search" }, 1550, + CTRLCMD + Keys::F, + [this] { + this->openPopup(); + }, + ImHexApi::Provider::isValid); + + /* Goto */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.goto" }, 1600, + CTRLCMD + Keys::G, + [this] { + this->openPopup(); + }, + ImHexApi::Provider::isValid); + + /* Select */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.select" }, 1650, + CTRLCMD + SHIFT + Keys::A, + [this] { + this->openPopup(); + }, + ImHexApi::Provider::isValid); - // Metadata save/load - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1200, [&, this] { - bool providerValid = ImHexApi::Provider::isValid(); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.load_encoding_file"_lang, nullptr, false, providerValid)) { - std::vector paths; - for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Encodings)) { - std::error_code error; - for (const auto &entry : std::fs::recursive_directory_iterator(path, error)) { - if (!entry.is_regular_file()) continue; + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1100); - paths.push_back(entry); - } - } + /* Copy */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy" }, 1150, + CTRLCMD + Keys::C, + [] { + auto selection = ImHexApi::HexEditor::getSelection(); + if (selection.has_value() && selection != Region::Invalid()) + copyBytes(*selection); + }, + ImHexApi::HexEditor::isSelectionValid); - View::showFileChooserPopup(paths, { {"Thingy Table File", "tbl"} }, false, - [this](const auto &path) { - this->m_hexEditor.setCustomEncoding(EncodingFile(EncodingFile::Type::Thingy, path)); - }); - } - }); + /* Copy As */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.ascii" }, 1200, + CTRLCMD + SHIFT + Keys::C, + [] { + auto selection = ImHexApi::HexEditor::getSelection(); + if (selection.has_value() && selection != Region::Invalid()) + copyString(*selection); + }, + ImHexApi::HexEditor::isSelectionValid); + /* Copy address */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.address" }, 1250, + Shortcut::None, + [] { + auto selection = ImHexApi::HexEditor::getSelection(); + if (selection.has_value() && selection != Region::Invalid()) + ImGui::SetClipboardText(hex::format("0x{:08X}", selection->getStartAddress()).c_str()); + }, + ImHexApi::HexEditor::isSelectionValid); - // Search / Goto - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1400, [&, this] { - bool providerValid = ImHexApi::Provider::isValid(); + /* Copy custom encoding */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.custom_encoding" }, 1300, + Shortcut::None, + [this] { + auto selection = ImHexApi::HexEditor::getSelection(); + auto customEncoding = this->m_hexEditor.getCustomEncoding(); + if (customEncoding.has_value() && selection.has_value() && selection != Region::Invalid()) + copyCustomEncoding(*customEncoding, *selection); + }, + [this] { + return ImHexApi::HexEditor::isSelectionValid() && this->m_hexEditor.getCustomEncoding().has_value(); + }); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.search"_lang, (CTRLCMD_NAME + " + F"s).c_str(), false, providerValid)) { - this->openPopup(); - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as" }, 1350); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.goto"_lang, (CTRLCMD_NAME + " + G"s).c_str(), false, providerValid)) { - this->openPopup(); - } + /* Copy as... */ + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as" }, 1400, []{ + auto selection = ImHexApi::HexEditor::getSelection(); + auto provider = ImHexApi::Provider::get(); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.select"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + A"s).c_str(), false, providerValid)) { - this->openPopup(); - } - }); - - - // Copy / Paste - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1100, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - auto selection = ImHexApi::HexEditor::getSelection(); - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.copy"_lang, (CTRLCMD_NAME + " + C"s).c_str(), false, selection.has_value())) - copyBytes(*selection); - - if (ImGui::BeginMenu("hex.builtin.view.hex_editor.menu.edit.copy_as"_lang, selection.has_value() && providerValid)) { - if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.ascii"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + C"s).c_str())) - copyString(*selection); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.address"_lang)) - ImGui::SetClipboardText(hex::format("0x{:08X}", selection->getStartAddress()).c_str()); - - auto &customEncoding = this->m_hexEditor.getCustomEncoding(); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.custom_encoding"_lang, "", false, customEncoding.has_value())) { - std::vector buffer(customEncoding->getLongestSequence(), 0x00); - std::string string; - - u64 offset = selection->getStartAddress(); - while (offset < selection->getEndAddress()) { - provider->read(offset, buffer.data(), std::min(buffer.size(), selection->size - (offset - selection->getStartAddress()))); - auto [result, size] = customEncoding->getEncodingFor(buffer); - - string += result; - offset += size; - }; - - ImGui::SetClipboardText(string.c_str()); - } - ImGui::Separator(); - - for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::getEntries()) { - if (ImGui::MenuItem(LangEntry(unlocalizedName))) { - ImGui::SetClipboardText( + for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::getEntries()) { + if (ImGui::MenuItem(LangEntry(unlocalizedName))) { + ImGui::SetClipboardText( callback( - provider, - selection->getStartAddress() + provider->getBaseAddress() + provider->getCurrentPageAddress(), - selection->size - ).c_str() - ); - } - } - - ImGui::EndMenu(); - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.paste"_lang, (CTRLCMD_NAME + " + V"s).c_str(), false, selection.has_value())) - pasteBytes(*selection, true); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.paste_all"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + V"s).c_str(), false, selection.has_value())) - pasteBytes(*selection, false); - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.select_all"_lang, (CTRLCMD_NAME + " + A"s).c_str(), false, selection.has_value() && providerValid)) - ImHexApi::HexEditor::setSelection(provider->getBaseAddress(), provider->getActualSize()); - }); - - // Popups - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1200, [&] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = ImHexApi::Provider::isValid(); - auto selection = ImHexApi::HexEditor::getSelection(); - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.set_base"_lang, nullptr, false, providerValid && provider->isReadable())) { - this->openPopup(provider->getBaseAddress()); - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.resize"_lang, nullptr, false, providerValid && provider->isResizable())) { - this->openPopup(provider->getActualSize()); - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.insert"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) { - this->openPopup(selection->getStartAddress(), 0x00); - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.remove"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) { - this->openPopup(selection->getStartAddress(), selection->getSize()); - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.jump_to"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value() && selection->getSize() <= sizeof(u64))) { - u64 value = 0; - provider->read(selection->getStartAddress(), &value, selection->getSize()); - - if (value < provider->getBaseAddress() + provider->getActualSize()) { - ImHexApi::HexEditor::setSelection(value, 1); - } - } - - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.open_in_new_provider"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) { - auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true); - if (auto *viewProvider = dynamic_cast(newProvider); viewProvider != nullptr) { - viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider()); - if (viewProvider->open()) - EventManager::post(viewProvider); + provider, + selection->getStartAddress() + provider->getBaseAddress() + provider->getCurrentPageAddress(), + selection->size + ).c_str() + ); } } }); + + /* Paste */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste" }, 1450, CTRL + Keys::V, + [] { + pasteBytes(*ImHexApi::HexEditor::getSelection(), true); + }, + ImHexApi::HexEditor::isSelectionValid); + + /* Paste All */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste_all" }, 1500, CTRL + SHIFT + Keys::V, + [] { + pasteBytes(*ImHexApi::HexEditor::getSelection(), false); + }, + ImHexApi::HexEditor::isSelectionValid); + + /* Select All */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.select_all" }, 1550, CTRL + Keys::A, + [] { + auto provider = ImHexApi::Provider::get(); + ImHexApi::HexEditor::setSelection(provider->getBaseAddress(), provider->getActualSize()); + }, + ImHexApi::HexEditor::isSelectionValid); + + + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1600); + + /* Set Base Address */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.set_base" }, 1650, Shortcut::None, + [this] { + auto provider = ImHexApi::Provider::get(); + this->openPopup(provider->getBaseAddress()); + }, + [] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isReadable(); }); + + /* Resize */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.resize" }, 1700, Shortcut::None, + [this] { + auto provider = ImHexApi::Provider::get(); + this->openPopup(provider->getBaseAddress()); + }, + [] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); }); + + /* Insert */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.insert" }, 1750, Shortcut::None, + [this] { + auto selection = ImHexApi::HexEditor::getSelection(); + + this->openPopup(selection->getStartAddress(), 0x00); + }, + [] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); }); + + /* Remove */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.remove" }, 1800, Shortcut::None, + [this] { + auto selection = ImHexApi::HexEditor::getSelection(); + + this->openPopup(selection->getStartAddress(), selection->getSize()); + }, + [] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); }); + + /* Jump to */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.jump_to" }, 1850, Shortcut::None, + [] { + auto provider = ImHexApi::Provider::get(); + auto selection = ImHexApi::HexEditor::getSelection(); + + u64 value = 0; + provider->read(selection->getStartAddress(), &value, selection->getSize()); + + if (value < provider->getBaseAddress() + provider->getActualSize()) { + ImHexApi::HexEditor::setSelection(value, 1); + } + }, + [] { return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && ImHexApi::HexEditor::getSelection()->getSize() <= sizeof(u64); }); + // Popups + + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1900); + + /* Open in new provider */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.open_in_new_provider" }, 1950, Shortcut::None, + [] { + auto selection = ImHexApi::HexEditor::getSelection(); + + auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true); + if (auto *viewProvider = dynamic_cast(newProvider); viewProvider != nullptr) { + viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider()); + if (viewProvider->open()) + EventManager::post(viewProvider); + } + }, + [] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid(); }); } } \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 5d6870269..ff001ba11 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -866,108 +866,109 @@ namespace hex::plugin::builtin { } void ViewPatternEditor::registerMenuItems() { - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 2000, [&, this] { - bool providerValid = ImHexApi::Provider::isValid(); - auto provider = ImHexApi::Provider::get(); + /* Import Pattern */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.pattern" }, 4050, Shortcut::None, + [this] { + auto provider = ImHexApi::Provider::get(); + std::vector paths; - if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.load_pattern"_lang, nullptr, false, providerValid)) { - std::vector paths; + for (const auto &imhexPath : fs::getDefaultPaths(fs::ImHexPath::Patterns)) { + if (!wolv::io::fs::exists(imhexPath)) continue; - for (const auto &imhexPath : fs::getDefaultPaths(fs::ImHexPath::Patterns)) { - if (!wolv::io::fs::exists(imhexPath)) continue; + std::error_code error; + for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) { + if (entry.is_regular_file() && entry.path().extension() == ".hexpat") { + paths.push_back(entry.path()); + } + } + } - std::error_code error; - for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) { - if (entry.is_regular_file() && entry.path().extension() == ".hexpat") { - paths.push_back(entry.path()); - } - } - } + View::showFileChooserPopup(paths, { { "Pattern File", "hexpat" } }, false, + [this, provider](const std::fs::path &path) { + this->loadPatternFile(path, provider); + }); + }, ImHexApi::Provider::isValid); - View::showFileChooserPopup(paths, { { "Pattern File", "hexpat" } }, false, - [this, provider](const std::fs::path &path) { - this->loadPatternFile(path, provider); - }); - } + /* Export Pattern */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, 7050, Shortcut::None, + [this] { + fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} }, + [this](const auto &path) { + wolv::io::File file(path, wolv::io::File::Mode::Create); - if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.save_pattern"_lang, nullptr, false, providerValid)) { - fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} }, - [this](const auto &path) { - wolv::io::File file(path, wolv::io::File::Mode::Create); + file.write(wolv::util::trim(this->m_textEditor.GetText())); + }); + }, ImHexApi::Provider::isValid); - file.write(wolv::util::trim(this->m_textEditor.GetText())); - }); - } - }); + const auto appendEditorText = [this](const std::string &text){ + this->m_textEditor.SetCursorPosition(TextEditor::Coordinates { this->m_textEditor.GetTotalLines(), 0 }); + this->m_textEditor.InsertText(hex::format("\n{0}", text)); + this->m_hasUnevaluatedChanges = true; + }; - // Place Pattern Type... - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 3000, [this] { - bool available = ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0; + const auto appendVariable = [&](const std::string &type) { auto selection = ImHexApi::HexEditor::getSelection(); - const auto &types = this->m_parserRuntime->getInternals().parser->getTypes(); + appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress())); + }; - const auto appendEditorText = [this](const std::string &text){ - this->m_textEditor.SetCursorPosition(TextEditor::Coordinates { this->m_textEditor.GetTotalLines(), 0 }); - this->m_textEditor.InsertText(hex::format("\n{0}", text)); - this->m_hasUnevaluatedChanges = true; - }; + const auto appendArray = [&](const std::string &type, size_t size) { + auto selection = ImHexApi::HexEditor::getSelection(); - const auto appendVariable = [&](const std::string &type) { - appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress())); - }; + appendEditorText(hex::format("{0} {0}_array_at_0x{1:02X}[0x{2:02X}] @ 0x{1:02X};", type, selection->getStartAddress(), (selection->getSize() + (size - 1)) / size)); + }; - const auto appendArray = [&](const std::string &type, size_t size) { - appendEditorText(hex::format("{0} {0}_array_at_0x{1:02X}[0x{2:02X}] @ 0x{1:02X};", type, selection->getStartAddress(), (selection->getSize() + (size - 1)) / size)); - }; + constexpr static std::array, 21> Types = {{ + { "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 }, + { "s8", 1 }, { "s16", 2 }, { "s24", 3 }, { "s32", 4 }, { "s48", 6 }, { "s64", 8 }, { "s96", 12 }, { "s128", 16 }, + { "float", 4 }, { "double", 8 }, + { "bool", 1 }, { "char", 1 }, { "char16", 2 } + }}; - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern"_lang, available)) { - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin"_lang)) { - constexpr static std::array, 21> Types = {{ - { "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 }, - { "s8", 1 }, { "s16", 2 }, { "s24", 3 }, { "s32", 4 }, { "s48", 6 }, { "s64", 8 }, { "s96", 12 }, { "s128", 16 }, - { "float", 4 }, { "double", 8 }, - { "bool", 1 }, { "char", 1 }, { "char16", 2 } - }}; + /* Place pattern... */ + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin" }, 3000, + [&]{ + if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) { + for (const auto &[type, size] : Types) + if (ImGui::MenuItem(type)) + appendVariable(type); + ImGui::EndMenu(); + } - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) { - for (const auto &[type, size] : Types) - if (ImGui::MenuItem(type)) - appendVariable(type); - ImGui::EndMenu(); - } + if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) { + for (const auto &[type, size] : Types) + if (ImGui::MenuItem(type)) + appendArray(type, size); + ImGui::EndMenu(); + } + }, [this] { + return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0; + }); - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) { - for (const auto &[type, size] : Types) - if (ImGui::MenuItem(type)) - appendArray(type, size); - ImGui::EndMenu(); - } + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom" }, 3050, + [&]{ + const auto &types = this->m_parserRuntime->getInternals().parser->getTypes(); + auto selection = ImHexApi::HexEditor::getSelection(); - ImGui::EndMenu(); - } + for (const auto &[typeName, type] : types) { + if (type->isTemplateType()) + continue; - bool hasPlaceableTypes = std::any_of(types.begin(), types.end(), [](const auto &type) { return !type.second->isTemplateType(); }); - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) { - for (const auto &[typeName, type] : types) { - if (type->isTemplateType()) - continue; + createNestedMenu(hex::splitString(typeName, "::"), [&] { + std::string variableName; + for (char &c : hex::replaceStrings(typeName, "::", "_")) + variableName += static_cast(std::tolower(c)); + variableName += hex::format("_at_0x{:02X}", selection->getStartAddress()); - createNestedMenu(hex::splitString(typeName, "::"), [&] { - std::string variableName; - for (char &c : hex::replaceStrings(typeName, "::", "_")) - variableName += static_cast(std::tolower(c)); - variableName += hex::format("_at_0x{:02X}", selection->getStartAddress()); + appendEditorText(hex::format("{0} {1} @ 0x{2:02X};", typeName, variableName, selection->getStartAddress())); + }); + } + }, [this] { + const auto &types = this->m_parserRuntime->getInternals().parser->getTypes(); + bool hasPlaceableTypes = std::any_of(types.begin(), types.end(), [](const auto &type) { return !type.second->isTemplateType(); }); - appendEditorText(hex::format("{0} {1} @ 0x{2:02X};", typeName, variableName, selection->getStartAddress())); - }); - } - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } - }); + return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0 && hasPlaceableTypes; + }); } void ViewPatternEditor::registerHandlers() { diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index ef4f46a04..67b0f3fc9 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -18,11 +18,11 @@ namespace hex::plugin::builtin { } }); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 2000, [&, this] { - if (ImGui::MenuItem("hex.builtin.view.settings.name"_lang)) { - TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.settings.name").c_str()); }); - this->getWindowOpenState() = true; - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 4000); + + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.settings.name"_lang }, 4050, Shortcut::None, [&, this] { + TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.settings.name").c_str()); }); + this->getWindowOpenState() = true; }); } diff --git a/plugins/builtin/source/content/views/view_store.cpp b/plugins/builtin/source/content/views/view_store.cpp index 6818aa307..fd34c8de0 100644 --- a/plugins/builtin/source/content/views/view_store.cpp +++ b/plugins/builtin/source/content/views/view_store.cpp @@ -26,13 +26,13 @@ namespace hex::plugin::builtin { using namespace std::literals::chrono_literals; ViewStore::ViewStore() : View("hex.builtin.view.store.name") { - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 3000, [&, this] { - if (ImGui::MenuItem("hex.builtin.view.store.name"_lang)) { - if (this->m_requestStatus == RequestStatus::NotAttempted) - this->refresh(); - TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.store.name").c_str()); }); - this->getWindowOpenState() = true; - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000); + + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.store.name" }, 2050, Shortcut::None, [&, this] { + if (this->m_requestStatus == RequestStatus::NotAttempted) + this->refresh(); + TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.store.name").c_str()); }); + this->getWindowOpenState() = true; }); } diff --git a/plugins/builtin/source/content/views/view_theme_manager.cpp b/plugins/builtin/source/content/views/view_theme_manager.cpp index c6521d7a4..29e18d779 100644 --- a/plugins/builtin/source/content/views/view_theme_manager.cpp +++ b/plugins/builtin/source/content/views/view_theme_manager.cpp @@ -7,11 +7,9 @@ namespace hex::plugin::builtin { ViewThemeManager::ViewThemeManager() : View("hex.builtin.view.theme_manager.name") { - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 1200, [&, this] { - if (ImGui::MenuItem("hex.builtin.view.theme_manager.name"_lang, "")) { - this->m_viewOpen = true; - this->getWindowOpenState() = true; - } + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.theme_manager.name" }, 3000, Shortcut::None, [&, this] { + this->m_viewOpen = true; + this->getWindowOpenState() = true; }); } diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index ca23c3fad..e6af9f402 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -543,7 +543,7 @@ namespace hex::plugin::builtin { } }); - ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1075, [&] { + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file" }, 1200, [] { if (ImGui::BeginMenu("hex.builtin.menu.file.open_recent"_lang, !s_recentProvidersUpdating && !s_recentProviders.empty())) { // Copy to avoid changing list while iteration auto recentProviders = s_recentProviders;