mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-28 15:57:03 -05:00
Currently setting the time interval to auto save the project has no effect other than creating one initial save if the input file is unsaved. When a file is created and not saved it remains in a dirty state which prevented setting the state of the autosaved project to needing to be saved. Fixed by decoupling the state of the provider from the state of the autosave. When a provider is detected as being dirty it always makes the autosave as being needed once the time interval has elapsed. feat: Implemented the menus on the main menu bar that will be available when the text editor has focus. It allows you to load and save patterns using open and save and will tack changes if files on disk are modified externally. It also only opens the file chooser the first time you save a pattern file and subsequent changes save to the same file. If you want to save into another file and have the new file be tracked you can use Save As. Finally, export doesn't track the file on disk at all. this feature uses the same changes tracker class used elsewhere in imHex. fix: Changed the defaults of various shortcuts that were using Alt + a key to avoid possible problems with some keyboards. Shouldn't affect end users as their shortcuts are loaded from internal file but those who complain about the Alt key misbehaving will be asked to reset the keys to the new defaults. In addition, all globally accessible shortcuts were added the Allow while typing flag so that they can be used in any field that accepts text. New menu entries were added for debugging to make the pattern editor and the hex editor menus more like each other. Finally, the call to RegisterMainMenuEntries() when initializing views was moved to occur after the call to registerViews() so that menus are not repeated when set for different views.
652 lines
31 KiB
C++
652 lines
31 KiB
C++
#include <hex/api/content_registry.hpp>
|
|
|
|
#include <imgui.h>
|
|
#include <implot.h>
|
|
|
|
#include <hex/ui/view.hpp>
|
|
#include <hex/api/shortcut_manager.hpp>
|
|
#include <hex/api/project_file_manager.hpp>
|
|
#include <hex/api/layout_manager.hpp>
|
|
#include <hex/api/achievement_manager.hpp>
|
|
#include <hex/api/events/requests_gui.hpp>
|
|
#include <hex/api/tutorial_manager.hpp>
|
|
|
|
#include <hex/helpers/crypto.hpp>
|
|
#include <hex/helpers/patches.hpp>
|
|
#include <hex/helpers/debugging.hpp>
|
|
|
|
#include <content/global_actions.hpp>
|
|
#include <toasts/toast_notification.hpp>
|
|
#include <popups/popup_text_input.hpp>
|
|
#include <hex/api/workspace_manager.hpp>
|
|
#include <hex/api/events/events_interaction.hpp>
|
|
|
|
#include <wolv/io/file.hpp>
|
|
#include <wolv/literals.hpp>
|
|
|
|
#include <romfs/romfs.hpp>
|
|
#include <hex/helpers/menu_items.hpp>
|
|
|
|
#include <GLFW/glfw3.h>
|
|
|
|
using namespace std::literals::string_literals;
|
|
using namespace wolv::literals;
|
|
|
|
namespace hex::plugin::builtin {
|
|
|
|
namespace {
|
|
|
|
bool noRunningTasks() {
|
|
return TaskManager::getRunningTaskCount() == 0;
|
|
}
|
|
|
|
bool noRunningTaskAndValidProvider() {
|
|
return noRunningTasks() && ImHexApi::Provider::isValid();
|
|
}
|
|
|
|
bool noRunningTaskAndWritableProvider() {
|
|
return noRunningTasks() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable();
|
|
}
|
|
|
|
}
|
|
|
|
namespace {
|
|
|
|
void handleIPSError(IPSError error) {
|
|
TaskManager::doLater([error]{
|
|
switch (error) {
|
|
case IPSError::InvalidPatchHeader:
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang);
|
|
break;
|
|
case IPSError::AddressOutOfRange:
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang);
|
|
break;
|
|
case IPSError::PatchTooLarge:
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang);
|
|
break;
|
|
case IPSError::InvalidPatchFormat:
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang);
|
|
break;
|
|
case IPSError::MissingEOF:
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
// Import
|
|
namespace {
|
|
|
|
void importIPSPatch() {
|
|
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
|
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readVector();
|
|
auto patch = Patches::fromIPSPatch(patchData);
|
|
if (!patch.has_value()) {
|
|
handleIPSError(patch.error());
|
|
return;
|
|
}
|
|
|
|
task.setMaxValue(patch->get().size());
|
|
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
for (auto &[address, value] : patch->get()) {
|
|
provider->write(address, &value, sizeof(value));
|
|
task.increment();
|
|
}
|
|
|
|
provider->getUndoStack().groupOperations(patch->get().size(), "hex.builtin.undo_operation.patches");
|
|
});
|
|
});
|
|
}
|
|
|
|
void importIPS32Patch() {
|
|
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
|
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readVector();
|
|
auto patch = Patches::fromIPS32Patch(patchData);
|
|
if (!patch.has_value()) {
|
|
handleIPSError(patch.error());
|
|
return;
|
|
}
|
|
|
|
task.setMaxValue(patch->get().size());
|
|
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
for (auto &[address, value] : patch->get()) {
|
|
provider->write(address, &value, sizeof(value));
|
|
task.increment();
|
|
}
|
|
|
|
provider->getUndoStack().groupOperations(patch->get().size(), "hex.builtin.undo_operation.patches");
|
|
});
|
|
});
|
|
}
|
|
|
|
void importModifiedFile() {
|
|
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
|
auto provider = ImHexApi::Provider::get();
|
|
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readVector();
|
|
|
|
if (patchData.size() != provider->getActualSize()) {
|
|
ui::ToastError::open("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());
|
|
|
|
for (auto &[address, value] : patches) {
|
|
provider->write(address, &value, sizeof(value));
|
|
task.increment();
|
|
}
|
|
|
|
provider->getUndoStack().groupOperations(patches.size(), "hex.builtin.undo_operation.patches");
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
// Export
|
|
namespace {
|
|
|
|
void exportBase64() {
|
|
fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &) {
|
|
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
|
if (!outputFile.isValid()) {
|
|
TaskManager::doLater([] {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.error.create_file"_lang);
|
|
});
|
|
return;
|
|
}
|
|
|
|
auto provider = ImHexApi::Provider::get();
|
|
std::vector<u8> bytes(5_MiB);
|
|
for (u64 address = 0; address < provider->getActualSize(); address += bytes.size()) {
|
|
bytes.resize(std::min<u64>(bytes.size(), provider->getActualSize() - address));
|
|
provider->read(provider->getBaseAddress() + address, bytes.data(), bytes.size());
|
|
|
|
outputFile.writeVector(crypt::encode64(bytes));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void exportSelectionToFile() {
|
|
fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
|
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
|
if (!outputFile.isValid()) {
|
|
TaskManager::doLater([] {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.error.create_file"_lang);
|
|
});
|
|
return;
|
|
}
|
|
|
|
auto provider = ImHexApi::Provider::get();
|
|
std::vector<u8> bytes(5_MiB);
|
|
|
|
auto selection = ImHexApi::HexEditor::getSelection();
|
|
for (u64 address = selection->getStartAddress(); address <= selection->getEndAddress(); address += bytes.size()) {
|
|
bytes.resize(std::min<u64>(bytes.size(), selection->getEndAddress() - address + 1));
|
|
provider->read(address, bytes.data(), bytes.size());
|
|
|
|
outputFile.writeVector(bytes);
|
|
task.update();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void drawExportLanguageMenu() {
|
|
for (const auto &formatter : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) {
|
|
if (menu::menuItem(Lang(formatter.unlocalizedName), Shortcut::None, false, ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->getActualSize() > 0)) {
|
|
fs::openFileBrowser(fs::DialogMode::Save, {}, [&formatter](const auto &path) {
|
|
TaskManager::createTask("hex.builtin.task.exporting_data", TaskManager::NoProgress, [&formatter, path](auto&){
|
|
auto provider = ImHexApi::Provider::get();
|
|
auto selection = ImHexApi::HexEditor::getSelection()
|
|
.value_or(
|
|
ImHexApi::HexEditor::ProviderRegion {
|
|
{ provider->getBaseAddress(), provider->getSize() },
|
|
provider
|
|
});
|
|
|
|
auto result = formatter.callback(provider, selection.getStartAddress(), selection.getSize(), false);
|
|
|
|
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
|
if (!file.isValid()) {
|
|
TaskManager::doLater([] {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.as_language.popup.export_error"_lang);
|
|
});
|
|
return;
|
|
}
|
|
|
|
file.writeString(result);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void exportReport() {
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [](auto &) {
|
|
std::string data;
|
|
|
|
for (const auto &provider : ImHexApi::Provider::getProviders()) {
|
|
data += hex::format("# {}", provider->getName());
|
|
data += "\n\n";
|
|
|
|
for (const auto &generator : ContentRegistry::Reports::impl::getGenerators()) {
|
|
data += generator.callback(provider);
|
|
data += "\n\n";
|
|
}
|
|
data += "\n\n";
|
|
}
|
|
|
|
TaskManager::doLater([data] {
|
|
fs::openFileBrowser(fs::DialogMode::Save, { { "Markdown File", "md" }}, [&data](const auto &path) {
|
|
auto file = wolv::io::File(path, wolv::io::File::Mode::Create);
|
|
if (!file.isValid()) {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.report.popup.export_error"_lang);
|
|
return;
|
|
}
|
|
|
|
file.writeString(data);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void exportIPSPatch() {
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
auto patches = Patches::fromProvider(provider);
|
|
if (!patches.has_value()) {
|
|
handleIPSError(patches.error());
|
|
return;
|
|
}
|
|
|
|
// 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->get().contains(0x00454F45) && patches->get().contains(0x00454F46)) {
|
|
u8 value = 0;
|
|
provider->read(0x00454F45, &value, sizeof(u8));
|
|
patches->get().at(0x00454F45) = value;
|
|
}
|
|
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
|
auto data = patches->toIPSPatch();
|
|
|
|
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()) {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
|
return;
|
|
}
|
|
|
|
if (data.has_value()) {
|
|
const auto& bytes = data.value();
|
|
file.writeVector(bytes);
|
|
EventPatchCreated::post(bytes.data(), bytes.size(), PatchKind::IPS);
|
|
} else {
|
|
handleIPSError(data.error());
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void exportIPS32Patch() {
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
auto patches = Patches::fromProvider(provider);
|
|
if (!patches.has_value()) {
|
|
handleIPSError(patches.error());
|
|
return;
|
|
}
|
|
|
|
// 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->get().contains(0x45454F45) && patches->get().contains(0x45454F46)) {
|
|
u8 value = 0;
|
|
provider->read(0x45454F45, &value, sizeof(u8));
|
|
patches->get().at(0x45454F45) = value;
|
|
}
|
|
|
|
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
|
auto data = patches->toIPS32Patch();
|
|
|
|
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()) {
|
|
ui::ToastError::open("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
|
return;
|
|
}
|
|
|
|
if (data.has_value()) {
|
|
const std::vector<u8>& bytes = data.value();
|
|
file.writeVector(bytes);
|
|
EventPatchCreated::post(bytes.data(), bytes.size(), PatchKind::IPS32);
|
|
} else {
|
|
handleIPSError(data.error());
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief returns true if there is a currently selected provider, and it is possibl to dump data from it
|
|
*/
|
|
bool isProviderDumpable() {
|
|
auto provider = ImHexApi::Provider::get();
|
|
return ImHexApi::Provider::isValid() && provider->isDumpable();
|
|
}
|
|
|
|
static void createFileMenu() {
|
|
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.file", 1000);
|
|
|
|
/* Create File */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.create_file" }, ICON_VS_FILE, 1050, CTRLCMD + Keys::N + AllowWhileTyping, [] {
|
|
auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
|
|
if (newProvider != nullptr && !newProvider->open())
|
|
hex::ImHexApi::Provider::remove(newProvider);
|
|
else
|
|
EventProviderOpened::post(newProvider);
|
|
}, noRunningTasks);
|
|
|
|
/* Open File */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, ICON_VS_FOLDER_OPENED, 1100, CTRLCMD + Keys::O + AllowWhileTyping, [] {
|
|
RequestOpenWindow::post("Open File");
|
|
}, noRunningTasks, ContentRegistry::Views::getViewByName("hex.builtin.view.hex_editor.name"));
|
|
|
|
/* Open Other */
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, ICON_VS_TELESCOPE, 1150, [] {
|
|
for (const auto &unlocalizedProviderName : ContentRegistry::Provider::impl::getEntries()) {
|
|
if (menu::menuItem(Lang(unlocalizedProviderName)))
|
|
ImHexApi::Provider::createProvider(unlocalizedProviderName);
|
|
}
|
|
}, noRunningTasks);
|
|
|
|
/* Reload Provider */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.reload_provider"}, ICON_VS_REFRESH, 1250, CTRLCMD + Keys::R + AllowWhileTyping, [] {
|
|
auto provider = ImHexApi::Provider::get();
|
|
|
|
provider->close();
|
|
if (!provider->open())
|
|
ImHexApi::Provider::remove(provider, true);
|
|
|
|
EventDataChanged::post(provider);
|
|
}, noRunningTaskAndValidProvider);
|
|
|
|
|
|
/* Project open / save */
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.project" }, ICON_VS_NOTEBOOK, 1400, []{}, noRunningTasks);
|
|
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.open" }, ICON_VS_ROOT_FOLDER_OPENED, 1410,
|
|
CTRL + ALT + Keys::O + AllowWhileTyping,
|
|
openProject, noRunningTasks);
|
|
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save" }, ICON_VS_SAVE, 1450,
|
|
CTRL + ALT + Keys::S + AllowWhileTyping,
|
|
saveProject, [&] { return noRunningTaskAndValidProvider() && ProjectFile::hasPath(); });
|
|
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save_as" }, ICON_VS_SAVE_AS, 1500,
|
|
ALT + SHIFT + Keys::S + AllowWhileTyping,
|
|
saveProjectAs, noRunningTaskAndValidProvider);
|
|
|
|
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 2000);
|
|
|
|
/* Import */
|
|
{
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.import" }, ICON_VS_SIGN_IN, 5140, []{}, noRunningTaskAndValidProvider);
|
|
|
|
/* IPS */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips"}, ICON_VS_GIT_PULL_REQUEST_NEW_CHANGES, 5150,
|
|
Shortcut::None,
|
|
importIPSPatch,
|
|
noRunningTaskAndWritableProvider);
|
|
|
|
/* IPS32 */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips32"}, ICON_VS_GIT_PULL_REQUEST_NEW_CHANGES, 5200,
|
|
Shortcut::None,
|
|
importIPS32Patch,
|
|
noRunningTaskAndWritableProvider);
|
|
|
|
/* Modified File */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.modified_file" }, ICON_VS_FILES, 5300,
|
|
Shortcut::None,
|
|
importModifiedFile,
|
|
noRunningTaskAndWritableProvider);
|
|
}
|
|
|
|
/* Export */
|
|
/* Only make them accessible if the current provider is dumpable */
|
|
{
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, ICON_VS_SIGN_OUT, 6000, []{}, isProviderDumpable);
|
|
|
|
/* Selection to File */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.selection_to_file" }, ICON_VS_FILE_BINARY, 6010,
|
|
Shortcut::None,
|
|
exportSelectionToFile,
|
|
ImHexApi::HexEditor::isSelectionValid);
|
|
|
|
/* Base 64 */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.base64" }, ICON_VS_NOTE, 6020,
|
|
Shortcut::None,
|
|
exportBase64,
|
|
isProviderDumpable);
|
|
|
|
/* Language */
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.as_language" }, ICON_VS_CODE, 6030,
|
|
drawExportLanguageMenu,
|
|
isProviderDumpable);
|
|
|
|
/* Report */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.report" }, ICON_VS_MARKDOWN, 6040,
|
|
Shortcut::None,
|
|
exportReport,
|
|
ImHexApi::Provider::isValid);
|
|
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6050);
|
|
|
|
/* IPS */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips" }, ICON_VS_GIT_PULL_REQUEST_NEW_CHANGES, 6100,
|
|
Shortcut::None,
|
|
exportIPSPatch,
|
|
isProviderDumpable);
|
|
|
|
/* IPS32 */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips32" }, ICON_VS_GIT_PULL_REQUEST_NEW_CHANGES, 6150,
|
|
Shortcut::None,
|
|
exportIPS32Patch,
|
|
isProviderDumpable);
|
|
}
|
|
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 10000);
|
|
|
|
/* Close Provider */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.close"}, ICON_VS_CHROME_CLOSE, 10050, CTRLCMD + Keys::W + AllowWhileTyping, [] {
|
|
ImHexApi::Provider::remove(ImHexApi::Provider::get());
|
|
}, noRunningTaskAndValidProvider);
|
|
|
|
/* Quit ImHex */
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.quit"}, ICON_VS_CLOSE_ALL, 10100, ALT + Keys::F4 + AllowWhileTyping, [] {
|
|
ImHexApi::System::closeImHex();
|
|
});
|
|
}
|
|
|
|
static void createEditMenu() {
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.edit", 2000);
|
|
}
|
|
|
|
static void createViewMenu() {
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.view", 3000);
|
|
|
|
#if !defined(OS_WEB)
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.always_on_top" }, ICON_VS_PINNED, 1000, Keys::F10 + AllowWhileTyping, [] {
|
|
static bool state = false;
|
|
|
|
state = !state;
|
|
glfwSetWindowAttrib(ImHexApi::System::getMainWindowHandle(), GLFW_FLOATING, state);
|
|
}, []{ return true; }, []{ return glfwGetWindowAttrib(ImHexApi::System::getMainWindowHandle(), GLFW_FLOATING); });
|
|
#endif
|
|
|
|
#if !defined(OS_MACOS) && !defined(OS_WEB)
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.view", "hex.builtin.menu.view.fullscreen" }, ICON_VS_SCREEN_FULL, 2000, Keys::F11 + AllowWhileTyping, [] {
|
|
static bool state = false;
|
|
static ImVec2 position, size;
|
|
|
|
state = !state;
|
|
|
|
|
|
const auto window = ImHexApi::System::getMainWindowHandle();
|
|
if (state) {
|
|
position = ImHexApi::System::getMainWindowPosition();
|
|
size = ImHexApi::System::getMainWindowSize();
|
|
|
|
const auto monitor = glfwGetPrimaryMonitor();
|
|
const auto videoMode = glfwGetVideoMode(monitor);
|
|
|
|
glfwSetWindowMonitor(window, monitor, 0, 0, videoMode->width, videoMode->height, videoMode->refreshRate);
|
|
} else {
|
|
glfwSetWindowMonitor(window, nullptr, position.x, position.y, size.x, size.y, 0);
|
|
glfwSetWindowAttrib(window, GLFW_DECORATED, ImHexApi::System::isBorderlessWindowModeEnabled() ? GLFW_FALSE : GLFW_TRUE);
|
|
}
|
|
|
|
}, []{ return true; }, []{ return glfwGetWindowMonitor(ImHexApi::System::getMainWindowHandle()) != nullptr; });
|
|
#endif
|
|
|
|
#if !defined(OS_WEB)
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.view" }, 3000);
|
|
#endif
|
|
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.view" }, 4000, [] {
|
|
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
|
|
if (view->hasViewMenuItemEntry()) {
|
|
auto &state = view->getWindowOpenState();
|
|
|
|
if (menu::menuItemEx(Lang(view->getUnlocalizedName()), view->getIcon(), Shortcut::None, state, ImHexApi::Provider::isValid() && !LayoutManager::isLayoutLocked()))
|
|
state = !state;
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
static void createLayoutMenu() {
|
|
LayoutManager::reload();
|
|
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, ICON_VS_LAYOUT, 1050, []{}, ImHexApi::Provider::isValid);
|
|
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout", "hex.builtin.menu.workspace.layout.save" }, 1100, Shortcut::None, [] {
|
|
ui::PopupTextInput::open("hex.builtin.popup.save_layout.title", "hex.builtin.popup.save_layout.desc", [](const std::string &name) {
|
|
LayoutManager::save(name);
|
|
});
|
|
}, ImHexApi::Provider::isValid);
|
|
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, ICON_VS_LAYOUT, 1150, [] {
|
|
bool locked = LayoutManager::isLayoutLocked();
|
|
if (menu::menuItemEx("hex.builtin.menu.workspace.layout.lock"_lang, ICON_VS_LOCK, Shortcut::None, locked, ImHexApi::Provider::isValid())) {
|
|
LayoutManager::lockLayout(!locked);
|
|
ContentRegistry::Settings::write<bool>("hex.builtin.setting.interface", "hex.builtin.setting.interface.layout_locked", !locked);
|
|
}
|
|
});
|
|
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, 1200);
|
|
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, 2000, [] {
|
|
for (const auto &path : romfs::list("layouts")) {
|
|
if (menu::menuItem(wolv::util::capitalizeString(path.stem().string()).c_str(), Shortcut::None, false, ImHexApi::Provider::isValid())) {
|
|
LayoutManager::loadFromString(std::string(romfs::get(path).string()));
|
|
}
|
|
}
|
|
|
|
bool shiftPressed = ImGui::GetIO().KeyShift;
|
|
for (auto &[name, path] : LayoutManager::getLayouts()) {
|
|
if (menu::menuItem(hex::format("{}{}", name, shiftPressed ? " " ICON_VS_CHROME_CLOSE : "").c_str(), Shortcut::None, false, ImHexApi::Provider::isValid())) {
|
|
if (shiftPressed) {
|
|
LayoutManager::removeLayout(name);
|
|
break;
|
|
} else {
|
|
LayoutManager::load(path);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static void createWorkspaceMenu() {
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.workspace", 4000);
|
|
|
|
createLayoutMenu();
|
|
|
|
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.workspace" }, 3000);
|
|
|
|
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.create" }, ICON_VS_ADD, 3100, Shortcut::None, [] {
|
|
ui::PopupTextInput::open("hex.builtin.popup.create_workspace.title", "hex.builtin.popup.create_workspace.desc", [](const std::string &name) {
|
|
WorkspaceManager::createWorkspace(name);
|
|
});
|
|
}, ImHexApi::Provider::isValid);
|
|
|
|
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace" }, 3200, [] {
|
|
const auto &workspaces = WorkspaceManager::getWorkspaces();
|
|
|
|
bool shiftPressed = ImGui::GetIO().KeyShift;
|
|
for (auto it = workspaces.begin(); it != workspaces.end(); ++it) {
|
|
const auto &[name, workspace] = *it;
|
|
|
|
bool canRemove = shiftPressed && !workspace.builtin;
|
|
if (menu::menuItem(hex::format("{}{}", name, canRemove ? " " ICON_VS_CHROME_CLOSE : "").c_str(), Shortcut::None, it == WorkspaceManager::getCurrentWorkspace(), ImHexApi::Provider::isValid())) {
|
|
if (canRemove) {
|
|
WorkspaceManager::removeWorkspace(name);
|
|
break;
|
|
} else {
|
|
WorkspaceManager::switchWorkspace(name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static void createExtrasMenu() {
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.extras", 5000);
|
|
}
|
|
|
|
static void createHelpMenu() {
|
|
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.help", 6000);
|
|
}
|
|
|
|
|
|
void registerMainMenuEntries() {
|
|
createFileMenu();
|
|
createEditMenu();
|
|
createViewMenu();
|
|
createWorkspaceMenu();
|
|
createExtrasMenu();
|
|
createHelpMenu();
|
|
}
|
|
|
|
} |