sys: Completely revamped main menu item system

This commit is contained in:
WerWolv
2023-03-20 13:11:43 +01:00
parent 677c989664
commit 39e8d557e8
24 changed files with 1019 additions and 669 deletions

View File

@@ -277,9 +277,11 @@ namespace hex {
namespace impl {
using DrawCallback = std::function<void()>;
using LayoutFunction = std::function<void(u32)>;
using ClickCallback = std::function<void()>;
using DrawCallback = std::function<void()>;
using MenuCallback = std::function<void()>;
using EnabledCallback = std::function<bool()>;
using LayoutFunction = std::function<void(u32)>;
using ClickCallback = std::function<void()>;
struct Layout {
std::string unlocalizedName;
@@ -291,8 +293,10 @@ namespace hex {
};
struct MenuItem {
std::string unlocalizedName;
DrawCallback callback;
std::vector<std::string> 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<std::string> &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; });
void addMenuItemSubMenu(std::vector<std::string> unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; });
void addMenuItemSeparator(std::vector<std::string> unlocalizedMainMenuNames, u32 priority);
void addWelcomeScreenEntry(const impl::DrawCallback &function);
void addFooterItem(const impl::DrawCallback &function);

View File

@@ -6,6 +6,7 @@
#include <functional>
#include <map>
#include <set>
#include <string>
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<Keys>(0x1000'0000));
constexpr static auto ALT = Key(static_cast<Keys>(0x2000'0000));
constexpr static auto SHIFT = Key(static_cast<Keys>(0x4000'0000));
constexpr static auto SUPER = Key(static_cast<Keys>(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<Keys>(0x1000'0000));
constexpr static auto ALT = Key(static_cast<Keys>(0x2000'0000));
constexpr static auto SHIFT = Key(static_cast<Keys>(0x4000'0000));
constexpr static auto SUPER = Key(static_cast<Keys>(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<void()> &callback);

View File

@@ -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<std::string> &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<std::string> 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<std::string> unlocalizedMainMenuNames, u32 priority) {
unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue);
getMenuItems().insert({
priority, { unlocalizedMainMenuNames, {}, []{}, []{ return true; } }
});
}

View File

@@ -272,6 +272,27 @@ namespace hex {
}
}
static void createNestedMenu(std::span<const std::string> menuItems, const Shortcut &shortcut, const std::function<void()> &callback, const std::function<bool()> &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<std::string> 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<EventFrameBegin>();
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "ヘルプ",

View File

@@ -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": "도움말",

View File

@@ -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",

View File

@@ -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": "帮助",

View File

@@ -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": "幫助",

View File

@@ -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<u64, u8> 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<u8> bytes(3000);
for (u64 address = 0; address < provider->getActualSize(); address += 3000) {
bytes.resize(std::min<u64>(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<EventProviderOpened>(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<EventProviderOpened>(newProvider);
/* Open File */
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, 1100, CTRLCMD + Keys::O, [] {
EventManager::post<RequestOpenWindow>("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<RequestOpenWindow>("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<u64, u8> 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<u8> bytes(3000);
for (u64 address = 0; address < provider->getActualSize(); address += 3000) {
bytes.resize(std::min<u64>(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();

View File

@@ -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");
});
}

View File

@@ -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();
});
}

View File

@@ -124,6 +124,8 @@ namespace hex::plugin::builtin {
if (!formatWriteFunction.empty()) {
editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector<u8> {
pattern->setValue(value);
return { };
};
}

View File

@@ -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) {

View File

@@ -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<RequestOpenPopup>("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<u8> buffer(customEncoding.getLongestSequence(), 0x00);
std::string string;
u64 offset = selection.getStartAddress();
while (offset < selection.getEndAddress()) {
provider->read(offset, buffer.data(), std::min<size_t>(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<std::fs::path> 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<PopupFind>();
},
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<PopupGoto>();
},
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<PopupSelect>();
},
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<std::fs::path> 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<PopupFind>();
}
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<PopupGoto>();
}
/* 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<PopupSelect>();
}
});
// 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<u8> buffer(customEncoding->getLongestSequence(), 0x00);
std::string string;
u64 offset = selection->getStartAddress();
while (offset < selection->getEndAddress()) {
provider->read(offset, buffer.data(), std::min<size_t>(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<PopupBaseAddress>(provider->getBaseAddress());
}
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.resize"_lang, nullptr, false, providerValid && provider->isResizable())) {
this->openPopup<PopupResize>(provider->getActualSize());
}
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.insert"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) {
this->openPopup<PopupInsert>(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<PopupRemove>(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<ViewProvider*>(newProvider); viewProvider != nullptr) {
viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider());
if (viewProvider->open())
EventManager::post<EventProviderOpened>(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<PopupBaseAddress>(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<PopupResize>(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<PopupInsert>(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<PopupRemove>(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<ViewProvider*>(newProvider); viewProvider != nullptr) {
viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider());
if (viewProvider->open())
EventManager::post<EventProviderOpened>(viewProvider);
}
},
[] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid(); });
}
}

View File

@@ -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<std::fs::path> paths;
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.load_pattern"_lang, nullptr, false, providerValid)) {
std::vector<std::fs::path> 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<std::pair<const char *, size_t>, 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<std::pair<const char *, size_t>, 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<char>(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<char>(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() {

View File

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

View File

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

View File

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

View File

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