diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index 37a418bf4..25b0949ac 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -46,11 +46,27 @@ namespace hex { Callback callback; }; + struct Category { + std::string name; + size_t slot = 0; + + bool operator<(const Category &other) const { + return name < other.name; + } + + operator const std::string &() const { + return name; + } + }; + void load(); void store(); void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue, const Callback &callback); void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue, const Callback &callback); + void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue, const Callback &callback); + + void addCategoryDescrition(const std::string &unlocalizedCategory, const std::string &unlocalizedCategoryDescription); void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 value); void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &value); @@ -60,9 +76,11 @@ namespace hex { std::string read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue); std::vector read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue = {}); - std::map> &getEntries(); + std::map> &getEntries(); + std::map &getCategoryDescriptions(); nlohmann::json getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName); nlohmann::json &getSettingsData(); + std::vector getStringArray(const std::string &unlocalizedCategory, const std::string &unlocalizedName); } /* Command Palette Command Registry. Allows adding of new commands to the command palette */ @@ -369,4 +387,4 @@ namespace hex { } }; -} \ No newline at end of file +} diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 4c85b40f5..75aee8712 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -41,10 +41,23 @@ namespace hex { } } + static auto getCategoryEntry(const std::string &unlocalizedCategory) { + auto &entries = getEntries(); + const size_t curSlot = entries.size(); + auto found = entries.find(Category { unlocalizedCategory }); + + if (found == entries.end()) { + auto [iter, _] = entries.emplace(Category { unlocalizedCategory, curSlot }, std::vector {}); + return iter; + } + + return found; + } + void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue, const Callback &callback) { log::info("Registered new integer setting: [{}]: {}", unlocalizedCategory, unlocalizedName); - getEntries()[unlocalizedCategory.c_str()].emplace_back(Entry { unlocalizedName.c_str(), callback }); + getCategoryEntry(unlocalizedCategory)->second.emplace_back(Entry { unlocalizedName.c_str(), callback }); auto &json = getSettingsData(); @@ -57,7 +70,7 @@ namespace hex { void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue, const Callback &callback) { log::info("Registered new string setting: [{}]: {}", unlocalizedCategory, unlocalizedName); - getEntries()[unlocalizedCategory].emplace_back(Entry { unlocalizedName, callback }); + getCategoryEntry(unlocalizedCategory)->second.emplace_back(Entry { unlocalizedName, callback }); auto &json = getSettingsData(); @@ -67,6 +80,23 @@ namespace hex { json[unlocalizedCategory][unlocalizedName] = std::string(defaultValue); } + void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue, const Callback &callback) { + log::info("Registered new string array setting: [{}]: {}", unlocalizedCategory, unlocalizedName); + + getCategoryEntry(unlocalizedCategory)->second.emplace_back(Entry { unlocalizedName, callback }); + + auto &json = getSettingsData(); + + if (!json.contains(unlocalizedCategory)) + json[unlocalizedCategory] = nlohmann::json::object(); + if (!json[unlocalizedCategory].contains(unlocalizedName) || !json[unlocalizedCategory][unlocalizedName].is_array()) + json[unlocalizedCategory][unlocalizedName] = defaultValue; + } + + void addCategoryDescrition(const std::string &unlocalizedCategory, const std::string &unlocalizedCategoryDescription) { + getCategoryDescriptions()[unlocalizedCategory] = unlocalizedCategoryDescription; + } + void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 value) { auto &json = getSettingsData(); @@ -141,12 +171,18 @@ namespace hex { } - std::map> &getEntries() { - static std::map> entries; + std::map> &getEntries() { + static std::map> entries; return entries; } + std::map &getCategoryDescriptions() { + static std::map descriptions; + + return descriptions; + } + nlohmann::json getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName) { auto &settings = getSettingsData(); @@ -162,6 +198,15 @@ namespace hex { return settings; } + std::vector getStringArray(const std::string &unlocalizedCategory, const std::string &unlocalizedName) { + auto setting = getSetting(unlocalizedCategory, unlocalizedName); + if (setting.is_array()) { + return setting; + } + + return {}; + } + } @@ -496,4 +541,4 @@ namespace hex { } -} \ No newline at end of file +} diff --git a/lib/libimhex/source/helpers/paths.cpp b/lib/libimhex/source/helpers/paths.cpp index 147fd099a..71b38f010 100644 --- a/lib/libimhex/source/helpers/paths.cpp +++ b/lib/libimhex/source/helpers/paths.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -13,8 +13,6 @@ #include #include -#include -#include namespace hex { @@ -38,9 +36,17 @@ namespace hex { std::vector getPath(ImHexPath path, bool listNonExisting) { std::vector result; + const auto exePath = getExecutablePath(); + const std::string settingName { "hex.builtin.setting.folders" }; + auto userDirs = ContentRegistry::Settings::getStringArray(settingName, settingName); + + auto addUserDirs = [&userDirs](auto &paths) { + std::transform(userDirs.begin(), userDirs.end(), std::back_inserter(paths), [](auto &item) { + return std::move(item); + }); + }; #if defined(OS_WINDOWS) - const auto exePath = getExecutablePath(); const auto parentDir = fs::path(exePath).parent_path(); fs::path appDataDir; @@ -57,21 +63,25 @@ namespace hex { switch (path) { case ImHexPath::Patterns: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "patterns").string(); }); break; case ImHexPath::PatternsInclude: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "includes").string(); }); break; case ImHexPath::Magic: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "magic").string(); }); break; case ImHexPath::Python: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "python").string(); }); @@ -82,6 +92,7 @@ namespace hex { }); break; case ImHexPath::Yara: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "yara").string(); }); @@ -94,11 +105,13 @@ namespace hex { }); break; case ImHexPath::Constants: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "constants").string(); }); break; case ImHexPath::Encodings: + addUserDirs(paths); std::transform(paths.begin(), paths.end(), std::back_inserter(result), [](auto &path) { return (path / "encodings").string(); }); @@ -113,7 +126,6 @@ namespace hex { } #elif defined(OS_MACOS) // Get path to special directories - const auto exePath = getExecutablePath(); const fs::path applicationSupportDir(getMacApplicationSupportDirectoryPath()); std::vector paths = { exePath, applicationSupportDir }; @@ -167,28 +179,31 @@ namespace hex { for (auto &dir : dataDirs) dir = dir / "imhex"; - const auto exePath = getExecutablePath(); - if (!exePath.empty()) dataDirs.emplace(dataDirs.begin(), fs::path(exePath.data()).parent_path()); switch (path) { case ImHexPath::Patterns: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "patterns").string(); }); break; case ImHexPath::PatternsInclude: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "includes").string(); }); break; case ImHexPath::Magic: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "magic").string(); }); break; case ImHexPath::Python: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p).string(); }); break; case ImHexPath::Plugins: std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "plugins").string(); }); break; case ImHexPath::Yara: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "yara").string(); }); break; case ImHexPath::Config: @@ -198,9 +213,11 @@ namespace hex { std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "resources").string(); }); break; case ImHexPath::Constants: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "constants").string(); }); break; case ImHexPath::Encodings: + addUserDirs(dataDirs); std::transform(dataDirs.begin(), dataDirs.end(), std::back_inserter(result), [](auto p) { return (p / "encodings").string(); }); break; case ImHexPath::Logs: @@ -221,4 +238,4 @@ namespace hex { return result; } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index 446d6adda..515dd4878 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include @@ -219,6 +221,50 @@ namespace hex::plugin::builtin { return false; }); + + static const std::string dirsSetting { "hex.builtin.setting.folders" }; + + ContentRegistry::Settings::addCategoryDescrition(dirsSetting, "hex.builtin.setting.folders.description"); + + ContentRegistry::Settings::add(dirsSetting, dirsSetting, std::vector {}, [](auto name, nlohmann::json &setting) { + static std::vector folders = setting; + static int currentItemIndex = 0; + + if (!ImGui::BeginListBox("", ImVec2(-38, -FLT_MIN))) { + return false; + } else { + for (size_t n = 0; n < folders.size(); n++) { + const bool isSelected = (currentItemIndex == n); + if (ImGui::Selectable(folders.at(n).c_str(), isSelected)) { currentItemIndex = n; } + if (isSelected) { ImGui::SetItemDefaultFocus(); } + } + ImGui::EndListBox(); + } + ImGui::SameLine(); + ImGui::BeginGroup(); + + if (ImGui::IconButton(ICON_VS_NEW_FOLDER, ImGui::GetCustomColorVec4(ImGuiCustomCol_DescButton), ImVec2(30, 30))) { + hex::openFileBrowser("Select include folder", hex::DialogMode::Folder, {}, [&](fs::path path) { + auto pathStr = path.string(); + + if (std::find(folders.begin(), folders.end(), pathStr) == folders.end()) { + folders.emplace_back(pathStr); + ContentRegistry::Settings::write(dirsSetting, dirsSetting, folders); + } + }); + } + + if (ImGui::IconButton(ICON_VS_REMOVE_CLOSE, ImGui::GetCustomColorVec4(ImGuiCustomCol_DescButton), ImVec2(30, 30))) { + if (!folders.empty()) { + folders.erase(std::next(folders.begin(), currentItemIndex)); + ContentRegistry::Settings::write(dirsSetting, dirsSetting, folders); + } + } + + ImGui::EndGroup(); + + return true; + }); } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index 60f8e6331..35b64d4c5 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -30,13 +30,29 @@ namespace hex::plugin::builtin { if (ImGui::BeginPopupModal(View::toWindowName("hex.builtin.view.settings.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoResize)) { if (ImGui::BeginTabBar("settings")) { - for (auto &[category, entries] : ContentRegistry::Settings::getEntries()) { + auto &entries = ContentRegistry::Settings::getEntries(); + + std::vector::const_iterator> sortedCategories; + + for (auto it = entries.cbegin(); it != entries.cend(); it++) { + sortedCategories.emplace_back(std::move(it)); + } + + std::sort(sortedCategories.begin(), sortedCategories.end(), [](auto &item0, auto &item1) { + return item0->first.slot < item1->first.slot; + }); + + const auto &descriptions = ContentRegistry::Settings::getCategoryDescriptions(); + + for (auto &it : sortedCategories) { + auto &[category, entries] = *it; if (ImGui::BeginTabItem(LangEntry(category))) { - ImGui::TextUnformatted(LangEntry(category)); + const std::string &categoryDesc = descriptions.count(category) ? descriptions.at(category) : category.name; + ImGui::TextUnformatted(LangEntry(categoryDesc)); ImGui::Separator(); for (auto &[name, callback] : entries) { - if (callback(LangEntry(name), ContentRegistry::Settings::getSettingsData()[category][name])) + if (callback(LangEntry(name), ContentRegistry::Settings::getSettingsData()[category.name][name])) EventManager::post(); } @@ -51,4 +67,4 @@ namespace hex::plugin::builtin { this->getWindowOpenState() = false; } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/lang/en_US.cpp b/plugins/builtin/source/lang/en_US.cpp index ed96fbf2c..e6faca782 100644 --- a/plugins/builtin/source/lang/en_US.cpp +++ b/plugins/builtin/source/lang/en_US.cpp @@ -674,6 +674,8 @@ namespace hex::plugin::builtin { { "hex.builtin.setting.hex_editor.grey_zeros", "Grey out zeros" }, { "hex.builtin.setting.hex_editor.uppercase_hex", "Upper case Hex characters" }, { "hex.builtin.setting.hex_editor.extra_info", "Display extra information" }, + { "hex.builtin.setting.folders", "Folders" }, + { "hex.builtin.setting.folders.description", "Specify additional search paths for patterns, scripts, rules and more" }, { "hex.builtin.provider.file.path", "File path" }, { "hex.builtin.provider.file.size", "Size" },