feat: Initial work rework current project system

This commit is contained in:
WerWolv 2025-02-10 21:44:14 +01:00
parent b3d208e6e6
commit efe0928fc6
14 changed files with 464 additions and 49 deletions

View File

@ -22,6 +22,9 @@ set(LIBIMHEX_SOURCES
source/data_processor/link.cpp source/data_processor/link.cpp
source/data_processor/node.cpp source/data_processor/node.cpp
source/project/project.cpp
source/project/project_manager.cpp
source/helpers/utils.cpp source/helpers/utils.cpp
source/helpers/utils_linux.cpp source/helpers/utils_linux.cpp
source/helpers/fs.cpp source/helpers/fs.cpp

View File

@ -0,0 +1,57 @@
#pragma once
#include <hex.hpp>
#include <hex/api/localization_manager.hpp>
#include <memory>
#include <list>
#include <string>
namespace hex::proj {
class Content {
public:
explicit Content(UnlocalizedString type, std::string name)
: m_type(std::move(type)), m_name(std::move(name)) { }
void setData(std::string data) { m_data = std::move(data); }
const std::string& getData() const { return m_data; }
const std::string& getName() const { return m_name; }
void setName(std::string name) { m_name = std::move(name); }
const UnlocalizedString& getType() const { return m_type; }
bool isOpen() const { return m_open; }
void setOpen(bool open) { m_open = open; }
bool isEmpty() const { return m_data.empty(); }
private:
UnlocalizedString m_type;
std::string m_name;
std::string m_data;
bool m_open = false;
};
class Project {
public:
explicit Project(std::string name) : m_name(std::move(name)) {}
Project(const Project &) = delete;
Project(Project &&) = delete;
~Project();
Project &operator=(const Project &) = delete;
Project &operator=(Project &&) = delete;
const std::string &getName() const { return m_name; }
void addContent(UnlocalizedString type);
const std::list<std::unique_ptr<Content>>& getContents() const { return m_contents; }
private:
std::string m_name;
std::list<std::unique_ptr<Content>> m_contents;
};
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <hex.hpp>
#include <hex/project/project.hpp>
#include <list>
#include <string>
#include <filesystem>
#include <functional>
namespace hex::proj {
class ProjectManager {
ProjectManager() = default;
public:
static void createProject(std::string name);
static void loadProject(const std::filesystem::path &path);
static void removeProject(const Project &project);
using LoadFunction = std::function<void(const Content&)>;
using StoreFunction = std::function<void(Content&)>;
struct ContentHandler {
UnlocalizedString type;
bool allowMultiple = false;
LoadFunction load;
StoreFunction store;
};
static void registerContentHandler(ContentHandler handler);
static const std::list<ContentHandler>& getContentHandlers();
static const ContentHandler* getContentHandler(const UnlocalizedString &typeName);
static const std::list<std::unique_ptr<Project>> &getProjects();
static void storeContent(Content &content);
static void loadContent(Content &content);
static Content* getLoadedContent(const UnlocalizedString &type);
};
}

View File

@ -0,0 +1,28 @@
#include <hex/project/project.hpp>
#include <hex/project/project_manager.hpp>
namespace hex::proj {
Project::~Project() {
for (auto &content : m_contents) {
ProjectManager::storeContent(*content);
}
}
void Project::addContent(UnlocalizedString type) {
const auto &content = m_contents.emplace_back(
std::make_unique<Content>(
type,
fmt::format("Unnamed {}", Lang(type))
)
);
const auto loadedContent = ProjectManager::getLoadedContent(type);
ProjectManager::storeContent(loadedContent == nullptr ? *content : *loadedContent);
ProjectManager::loadContent(*content);
}
}

View File

@ -0,0 +1,89 @@
#include <hex/helpers/auto_reset.hpp>
#include <hex/project/project_manager.hpp>
namespace hex::proj {
static AutoReset<std::list<std::unique_ptr<Project>>> s_projects;
static AutoReset<std::list<ProjectManager::ContentHandler>> s_contentHandlers;
void ProjectManager::createProject(std::string name) {
s_projects->emplace_back(std::make_unique<Project>(std::move(name)));
}
void ProjectManager::loadProject(const std::filesystem::path &path) {
std::ignore = path;
}
void ProjectManager::removeProject(const Project &projectToClose) {
std::erase_if(*s_projects, [&](const std::unique_ptr<Project> &project) {
return project.get() == &projectToClose;
});
}
const std::list<std::unique_ptr<Project>> &ProjectManager::getProjects() {
return *s_projects;
}
const std::list<ProjectManager::ContentHandler> &ProjectManager::getContentHandlers() {
return *s_contentHandlers;
}
void ProjectManager::registerContentHandler(ContentHandler handler) {
s_contentHandlers->emplace_back(std::move(handler));
}
const ProjectManager::ContentHandler* ProjectManager::getContentHandler(const UnlocalizedString &typeName) {
for (const auto &handler : getContentHandlers()) {
if (handler.type == typeName) {
return &handler;
}
}
return nullptr;
}
void ProjectManager::storeContent(Content &content) {
const auto *handler = getContentHandler(content.getType());
if (handler != nullptr) {
handler->store(content);
}
}
void ProjectManager::loadContent(Content &content) {
const auto *handler = getContentHandler(content.getType());
if (handler != nullptr) {
if (!handler->allowMultiple) {
for (const auto &project : getProjects()) {
for (const auto &projectContent : project->getContents()) {
if (projectContent->isOpen() && projectContent->getType() == content.getType()) {
storeContent(*projectContent);
projectContent->setOpen(false);
}
}
}
}
if (handler->type == content.getType()) {
handler->load(content);
content.setOpen(true);
}
}
}
Content* ProjectManager::getLoadedContent(const UnlocalizedString& type) {
for (const auto &project : getProjects()) {
for (const auto &projectContent : project->getContents()) {
if (projectContent->isOpen() && projectContent->getType() == type) {
return projectContent.get();
}
}
}
return nullptr;
}
}

View File

@ -51,6 +51,8 @@ add_imhex_plugin(
source/content/helpers/demangle.cpp source/content/helpers/demangle.cpp
source/content/sidebar/project_explorer.cpp
source/content/data_processor_nodes/basic_nodes.cpp source/content/data_processor_nodes/basic_nodes.cpp
source/content/data_processor_nodes/control_nodes.cpp source/content/data_processor_nodes/control_nodes.cpp
source/content/data_processor_nodes/decode_nodes.cpp source/content/data_processor_nodes/decode_nodes.cpp

View File

@ -32,8 +32,8 @@ namespace hex::plugin::builtin {
private: private:
std::string m_currFilter; std::string m_currFilter;
PerProvider<std::list<Bookmark>> m_bookmarks; std::list<Bookmark> m_bookmarks;
PerProvider<u64> m_currBookmarkId; u64 m_currBookmarkId;
}; };
} }

View File

@ -56,7 +56,7 @@ namespace hex::plugin::builtin {
void reloadCustomNodes(); void reloadCustomNodes();
void updateNodePositions(); void updateNodePositions();
std::vector<Workspace*> &getWorkspaceStack() { return *m_workspaceStack; } std::vector<Workspace*> &getWorkspaceStack() { return m_workspaceStack; }
private: private:
void drawContextMenus(ViewDataProcessor::Workspace &workspace); void drawContextMenus(ViewDataProcessor::Workspace &workspace);
@ -76,8 +76,8 @@ namespace hex::plugin::builtin {
std::vector<CustomNode> m_customNodes; std::vector<CustomNode> m_customNodes;
PerProvider<Workspace> m_mainWorkspace; Workspace m_mainWorkspace;
PerProvider<std::vector<Workspace*>> m_workspaceStack; std::vector<Workspace*> m_workspaceStack;
TaskHolder m_evaluationTask; TaskHolder m_evaluationTask;
}; };

View File

@ -399,6 +399,10 @@
"hex.builtin.popup.save_layout.title": "Save Layout", "hex.builtin.popup.save_layout.title": "Save Layout",
"hex.builtin.popup.save_layout.desc": "Enter a name under which to save the current layout.", "hex.builtin.popup.save_layout.desc": "Enter a name under which to save the current layout.",
"hex.builtin.popup.waiting_for_tasks.desc": "There are still tasks running in the background.\nImHex will close after they are finished.", "hex.builtin.popup.waiting_for_tasks.desc": "There are still tasks running in the background.\nImHex will close after they are finished.",
"hex.builtin.project.content_type.pattern": "Pattern Source Code",
"hex.builtin.project.content_type.bookmarks": "Bookmarks",
"hex.builtin.project.content_type.data_processor": "Data Processor Nodes",
"hex.builtin.project.free_items": "Free Items",
"hex.builtin.provider.rename": "Rename", "hex.builtin.provider.rename": "Rename",
"hex.builtin.provider.rename.desc": "Enter a name for this provider.", "hex.builtin.provider.rename.desc": "Enter a name for this provider.",
"hex.builtin.provider.tooltip.show_more": "Hold SHIFT for more information", "hex.builtin.provider.tooltip.show_more": "Hold SHIFT for more information",

View File

@ -0,0 +1,145 @@
#include <imgui.h>
#include <imgui_internal.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <fonts/vscode_icons.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/task_manager.hpp>
#include <hex/project/project.hpp>
#include <hex/project/project_manager.hpp>
#include <hex/helpers/utils.hpp>
namespace hex::plugin::builtin {
namespace {
void drawContent(proj::Content &content) {
static proj::Content *rightClickedContent = nullptr;
static proj::Content *renamingContent = nullptr;
static std::string renameText;
ImGui::TableNextColumn();
if (renamingContent != &content) {
ImGui::Selectable(content.getName().c_str(), content.isOpen(), ImGuiSelectableFlags_SpanAllColumns);
} else {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
if (ImGui::InputText("##ContentName", renameText)) {
renamingContent->setName(renameText);
}
ImGui::SetKeyboardFocusHere(-1);
ImGui::PopStyleVar();
if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) {
renamingContent->setName(renameText);
renamingContent = nullptr;
}
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
renamingContent = nullptr;
}
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
proj::ProjectManager::loadContent(content);
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
rightClickedContent = &content;
ImGui::OpenPopup("ContentContextMenu");
}
} else {
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && renamingContent == &content)
renamingContent = nullptr;
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(Lang(content.getType()));
if (rightClickedContent == &content) {
if (ImGui::BeginPopup("ContentContextMenu")) {
if (ImGui::MenuItemEx("Open", ICON_VS_OPEN_PREVIEW)) {
proj::ProjectManager::loadContent(content);
}
if (ImGui::MenuItemEx("Rename", ICON_VS_DIFF_RENAMED)) {
renamingContent = &content;
renameText = content.getName();
}
ImGui::EndPopup();
}
}
}
void drawProject(proj::Project &project) {
static proj::Project *rightClickedProject = nullptr;
bool open = ImGui::TreeNodeEx(project.getName().c_str(), ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAllColumns);
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
rightClickedProject = &project;
ImGui::OpenPopup("ProjectContextMenu");
}
if (rightClickedProject == &project) {
if (ImGui::BeginPopup("ProjectContextMenu")) {
if (ImGui::BeginMenuEx("Add", ICON_VS_FILE_ADD)) {
for (const auto &handler : proj::ProjectManager::getContentHandlers()) {
if (ImGui::MenuItem(Lang(handler.type))) {
rightClickedProject->addContent(handler.type);
}
}
ImGui::EndMenu();
}
if (ImGui::MenuItemEx("Close", ICON_VS_CLOSE)) {
TaskManager::doLater([project = rightClickedProject] {
proj::ProjectManager::removeProject(*project);
});
}
ImGui::EndPopup();
}
}
if (open) {
for (const auto &content : project.getContents()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(content.get());
drawContent(*content);
ImGui::PopID();
}
ImGui::TreePop();
}
}
}
void registerProjectExplorer() {
ContentRegistry::Interface::addSidebarItem(ICON_VS_PROJECT, [] {
if (ImGui::BeginTable("Projects", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) {
ImGui::TableSetupColumn("##Icon", ImGuiTableColumnFlags_WidthFixed, 20_scaled);
ImGui::TableSetupColumn("##Name", ImGuiTableColumnFlags_WidthStretch, 20_scaled);
ImGui::TableSetupColumn("##Type", ImGuiTableColumnFlags_WidthFixed, 100_scaled);
for (auto &project : proj::ProjectManager::getProjects()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::PushID(project.get());
drawProject(*project);
ImGui::PopID();
ImGui::NewLine();
}
ImGui::EndTable();
}
});
proj::ProjectManager::createProject("Project 1");
proj::ProjectManager::createProject("Project 2");
proj::ProjectManager::createProject("Free Items");
}
}

View File

@ -12,6 +12,7 @@
#include <content/providers/view_provider.hpp> #include <content/providers/view_provider.hpp>
#include <fonts/vscode_icons.hpp> #include <fonts/vscode_icons.hpp>
#include <hex/project/project_manager.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -45,16 +46,16 @@ namespace hex::plugin::builtin {
bookmarkId bookmarkId
}; };
m_bookmarks->emplace_back(std::move(bookmark), true); m_bookmarks.emplace_back(std::move(bookmark), true);
ImHexApi::Provider::markDirty(); ImHexApi::Provider::markDirty();
EventBookmarkCreated::post(m_bookmarks->back().entry); EventBookmarkCreated::post(m_bookmarks.back().entry);
EventHighlightingChanged::post(); EventHighlightingChanged::post();
}); });
RequestRemoveBookmark::subscribe([this](u64 id) { RequestRemoveBookmark::subscribe([this](u64 id) {
std::erase_if(m_bookmarks.get(), [id](const auto &bookmark) { std::erase_if(m_bookmarks, [id](const auto &bookmark) {
return bookmark.entry.id == id; return bookmark.entry.id == id;
}); });
}); });
@ -65,7 +66,7 @@ namespace hex::plugin::builtin {
// Check all bookmarks for potential overlaps with the current address // Check all bookmarks for potential overlaps with the current address
std::optional<ImColor> color; std::optional<ImColor> color;
for (const auto &bookmark : *m_bookmarks) { for (const auto &bookmark : m_bookmarks) {
if (!bookmark.highlightVisible) if (!bookmark.highlightVisible)
continue; continue;
@ -82,7 +83,7 @@ namespace hex::plugin::builtin {
std::ignore = data; std::ignore = data;
// Loop over all bookmarks // Loop over all bookmarks
for (const auto &[bookmark, highlightVisible] : *m_bookmarks) { for (const auto &[bookmark, highlightVisible] : m_bookmarks) {
if (!highlightVisible) if (!highlightVisible)
continue; continue;
@ -158,7 +159,7 @@ namespace hex::plugin::builtin {
return true; return true;
auto data = nlohmann::json::parse(fileContent.begin(), fileContent.end()); auto data = nlohmann::json::parse(fileContent.begin(), fileContent.end());
m_bookmarks.get(provider).clear(); m_bookmarks.clear();
return this->importBookmarks(provider, data); return this->importBookmarks(provider, data);
}, },
.store = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) -> bool { .store = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) -> bool {
@ -170,11 +171,26 @@ namespace hex::plugin::builtin {
return result; return result;
} }
}); });
proj::ProjectManager::registerContentHandler({
.type = "hex.builtin.project.content_type.bookmarks",
.load = [this](const proj::Content &content) {
m_bookmarks.clear();
const auto data = content.isEmpty() ? nlohmann::json() : nlohmann::json::parse(content.getData());
this->importBookmarks(ImHexApi::Provider::get(), data);
EventHighlightingChanged::post();
},
.store = [this](proj::Content &content) {
nlohmann::json data;
this->exportBookmarks(ImHexApi::Provider::get(), data);
content.setData(data.dump(4));
}
});
ContentRegistry::Reports::addReportProvider([this](prv::Provider *provider) -> std::string { ContentRegistry::Reports::addReportProvider([this](prv::Provider *provider) -> std::string {
std::string result; std::string result;
const auto &bookmarks = m_bookmarks.get(provider); const auto &bookmarks = m_bookmarks;
if (bookmarks.empty()) if (bookmarks.empty())
return ""; return "";
@ -252,7 +268,7 @@ namespace hex::plugin::builtin {
void ViewBookmarks::drawDropTarget(std::list<Bookmark>::iterator it, float height) { void ViewBookmarks::drawDropTarget(std::list<Bookmark>::iterator it, float height) {
height = std::max(height, 1.0F); height = std::max(height, 1.0F);
if (it != m_bookmarks->begin()) { if (it != m_bookmarks.begin()) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - height); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - height);
} else { } else {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + height); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + height);
@ -261,7 +277,7 @@ namespace hex::plugin::builtin {
ImGui::InvisibleButton("##DropTarget", ImVec2(ImGui::GetContentRegionAvail().x, height * 2.0F)); ImGui::InvisibleButton("##DropTarget", ImVec2(ImGui::GetContentRegionAvail().x, height * 2.0F));
const auto dropTarget = ImRect(ImGui::GetItemRectMin(), ImVec2(ImGui::GetItemRectMax().x, ImGui::GetItemRectMin().y + 2_scaled)); const auto dropTarget = ImRect(ImGui::GetItemRectMin(), ImVec2(ImGui::GetItemRectMax().x, ImGui::GetItemRectMin().y + 2_scaled));
if (it == m_bookmarks->begin()) { if (it == m_bookmarks.begin()) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - height); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - height);
} }
@ -274,13 +290,13 @@ namespace hex::plugin::builtin {
u64 droppedBookmarkId = *static_cast<const u64*>(payload->Data); u64 droppedBookmarkId = *static_cast<const u64*>(payload->Data);
// Find the correct bookmark with that id // Find the correct bookmark with that id
auto droppedIter = std::ranges::find_if(m_bookmarks->begin(), m_bookmarks->end(), [droppedBookmarkId](const auto &bookmarkItem) { auto droppedIter = std::ranges::find_if(m_bookmarks.begin(), m_bookmarks.end(), [droppedBookmarkId](const auto &bookmarkItem) {
return bookmarkItem.entry.id == droppedBookmarkId; return bookmarkItem.entry.id == droppedBookmarkId;
}); });
// Swap the two bookmarks // Swap the two bookmarks
if (droppedIter != m_bookmarks->end()) { if (droppedIter != m_bookmarks.end()) {
m_bookmarks->splice(it, m_bookmarks, droppedIter); m_bookmarks.splice(it, m_bookmarks, droppedIter);
EventHighlightingChanged::post(); EventHighlightingChanged::post();
} }
@ -298,18 +314,18 @@ namespace hex::plugin::builtin {
ImGui::PopItemWidth(); ImGui::PopItemWidth();
if (ImGui::BeginChild("##bookmarks")) { if (ImGui::BeginChild("##bookmarks")) {
if (m_bookmarks->empty()) { if (m_bookmarks.empty()) {
ImGuiExt::TextOverlay("hex.builtin.view.bookmarks.no_bookmarks"_lang, ImGui::GetWindowPos() + ImGui::GetWindowSize() / 2, ImGui::GetWindowWidth() * 0.7); ImGuiExt::TextOverlay("hex.builtin.view.bookmarks.no_bookmarks"_lang, ImGui::GetWindowPos() + ImGui::GetWindowSize() / 2, ImGui::GetWindowWidth() * 0.7);
} }
auto bookmarkToRemove = m_bookmarks->end(); auto bookmarkToRemove = m_bookmarks.end();
const auto defaultItemSpacing = ImGui::GetStyle().ItemSpacing.y; const auto defaultItemSpacing = ImGui::GetStyle().ItemSpacing.y;
ImGui::Dummy({ ImGui::GetContentRegionAvail().x, 0 }); ImGui::Dummy({ ImGui::GetContentRegionAvail().x, 0 });
drawDropTarget(m_bookmarks->begin(), defaultItemSpacing); drawDropTarget(m_bookmarks.begin(), defaultItemSpacing);
// Draw all bookmarks // Draw all bookmarks
for (auto it = m_bookmarks->begin(); it != m_bookmarks->end(); ++it) { for (auto it = m_bookmarks.begin(); it != m_bookmarks.end(); ++it) {
auto &[bookmark, highlightVisible] = *it; auto &[bookmark, highlightVisible] = *it;
auto &[region, name, comment, color, locked, bookmarkId] = bookmark; auto &[region, name, comment, color, locked, bookmarkId] = bookmark;
@ -529,15 +545,15 @@ namespace hex::plugin::builtin {
} }
// Remove the bookmark that was marked for removal // Remove the bookmark that was marked for removal
if (bookmarkToRemove != m_bookmarks->end()) { if (bookmarkToRemove != m_bookmarks.end()) {
m_bookmarks->erase(bookmarkToRemove); m_bookmarks.erase(bookmarkToRemove);
EventHighlightingChanged::post(); EventHighlightingChanged::post();
} }
} }
ImGui::EndChild(); ImGui::EndChild();
} }
bool ViewBookmarks::importBookmarks(prv::Provider *provider, const nlohmann::json &json) { bool ViewBookmarks::importBookmarks(prv::Provider *, const nlohmann::json &json) {
if (!json.contains("bookmarks")) if (!json.contains("bookmarks"))
return false; return false;
@ -549,30 +565,32 @@ namespace hex::plugin::builtin {
if (!region.contains("address") || !region.contains("size")) if (!region.contains("address") || !region.contains("size"))
continue; continue;
m_bookmarks.get(provider).push_back({ m_bookmarks.push_back({
{ {
.region = { region["address"], region["size"] }, .region = { region["address"], region["size"] },
.name = bookmark["name"], .name = bookmark["name"],
.comment = bookmark["comment"], .comment = bookmark["comment"],
.color = bookmark["color"], .color = bookmark["color"],
.locked = bookmark["locked"], .locked = bookmark["locked"],
.id = bookmark.contains("id") ? bookmark["id"].get<u64>() : m_currBookmarkId.get(provider), .id = bookmark.contains("id") ? bookmark["id"].get<u64>() : m_currBookmarkId,
}, },
bookmark.contains("highlightVisible") ? bookmark["highlightVisible"].get<bool>() : true, bookmark.contains("highlightVisible") ? bookmark["highlightVisible"].get<bool>() : true,
}); });
if (bookmark.contains("id")) if (bookmark.contains("id"))
m_currBookmarkId.get(provider) = std::max<u64>(m_currBookmarkId.get(provider), bookmark["id"].get<i64>() + 1); m_currBookmarkId = std::max<u64>(m_currBookmarkId, bookmark["id"].get<i64>() + 1);
else else
m_currBookmarkId.get(provider) += 1; m_currBookmarkId += 1;
} }
EventHighlightingChanged::post();
return true; return true;
} }
bool ViewBookmarks::exportBookmarks(prv::Provider *provider, nlohmann::json &json) { bool ViewBookmarks::exportBookmarks(prv::Provider *, nlohmann::json &json) {
json["bookmarks"] = nlohmann::json::array(); json["bookmarks"] = nlohmann::json::array();
size_t index = 0; size_t index = 0;
for (const auto &[bookmark, highlightVisible] : m_bookmarks.get(provider)) { for (const auto &[bookmark, highlightVisible] : m_bookmarks) {
json["bookmarks"][index] = { json["bookmarks"][index] = {
{ "name", bookmark.name }, { "name", bookmark.name },
{ "comment", bookmark.comment }, { "comment", bookmark.comment },
@ -627,7 +645,7 @@ namespace hex::plugin::builtin {
wolv::io::File(path, wolv::io::File::Mode::Create).writeString(json.dump(4)); wolv::io::File(path, wolv::io::File::Mode::Create).writeString(json.dump(4));
}); });
}, [this]{ }, [this]{
return ImHexApi::Provider::isValid() && !m_bookmarks->empty(); return ImHexApi::Provider::isValid() && !m_bookmarks.empty();
}); });
} }

View File

@ -14,6 +14,7 @@
#include <imnodes.h> #include <imnodes.h>
#include <imnodes_internal.h> #include <imnodes_internal.h>
#include <hex/project/project_manager.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <wolv/io/file.hpp> #include <wolv/io/file.hpp>
@ -361,28 +362,39 @@ namespace hex::plugin::builtin {
ProjectFile::registerPerProviderHandler({ ProjectFile::registerPerProviderHandler({
.basePath = "data_processor.json", .basePath = "data_processor.json",
.required = false, .required = false,
.load = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) { .load = [this](prv::Provider *, const std::fs::path &basePath, const Tar &tar) {
std::string save = tar.readString(basePath); std::string save = tar.readString(basePath);
ViewDataProcessor::loadNodes(m_mainWorkspace.get(provider), nlohmann::json::parse(save)); this->loadNodes(m_mainWorkspace, nlohmann::json::parse(save));
m_updateNodePositions = true; m_updateNodePositions = true;
return true; return true;
}, },
.store = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) { .store = [this](prv::Provider *, const std::fs::path &basePath, const Tar &tar) {
tar.writeString(basePath, ViewDataProcessor::saveNodes(m_mainWorkspace.get(provider)).dump(4)); tar.writeString(basePath, this->saveNodes(m_mainWorkspace).dump(4));
return true; return true;
} }
}); });
proj::ProjectManager::registerContentHandler({
.type = "hex.builtin.project.content_type.data_processor",
.load = [this](const proj::Content &content) {
const nlohmann::json data = content.isEmpty() ? nlohmann::json() : nlohmann::json::parse(content.getData());
this->loadNodes(m_mainWorkspace, data);
m_updateNodePositions = true;
},
.store = [this](proj::Content &content) {
content.setData(this->saveNodes(m_mainWorkspace).dump(4));
}
});
EventProviderOpened::subscribe(this, [this](auto *provider) { EventProviderOpened::subscribe(this, [this](auto *) {
m_mainWorkspace.get(provider) = { }; m_mainWorkspace = { };
m_workspaceStack.get(provider).push_back(&m_mainWorkspace.get(provider)); m_workspaceStack.push_back(&m_mainWorkspace);
}); });
EventProviderChanged::subscribe(this, [this](const auto *, const auto *) { EventProviderChanged::subscribe(this, [this](const auto *, const auto *) {
for (auto *workspace : *m_workspaceStack) { for (auto *workspace : m_workspaceStack) {
for (auto &node : workspace->nodes) { for (auto &node : workspace->nodes) {
node->setCurrentOverlay(nullptr); node->setCurrentOverlay(nullptr);
} }
@ -399,7 +411,7 @@ namespace hex::plugin::builtin {
[&](const std::fs::path &path) { [&](const std::fs::path &path) {
wolv::io::File file(path, wolv::io::File::Mode::Read); wolv::io::File file(path, wolv::io::File::Mode::Read);
if (file.isValid()) { if (file.isValid()) {
ViewDataProcessor::loadNodes(*m_mainWorkspace, nlohmann::json::parse(file.readString())); ViewDataProcessor::loadNodes(m_mainWorkspace, nlohmann::json::parse(file.readString()));
m_updateNodePositions = true; m_updateNodePositions = true;
} }
}); });
@ -411,17 +423,17 @@ namespace hex::plugin::builtin {
[&, this](const std::fs::path &path) { [&, this](const std::fs::path &path) {
wolv::io::File file(path, wolv::io::File::Mode::Create); wolv::io::File file(path, wolv::io::File::Mode::Create);
if (file.isValid()) if (file.isValid())
file.writeString(ViewDataProcessor::saveNodes(*m_mainWorkspace).dump(4)); file.writeString(ViewDataProcessor::saveNodes(m_mainWorkspace).dump(4));
}); });
}, [this]{ }, [this]{
return !m_workspaceStack->empty() && !m_workspaceStack->back()->nodes.empty() && ImHexApi::Provider::isValid(); return !m_workspaceStack.empty() && !m_workspaceStack.back()->nodes.empty() && ImHexApi::Provider::isValid();
}); });
ContentRegistry::FileHandler::add({ ".hexnode" }, [this](const auto &path) { ContentRegistry::FileHandler::add({ ".hexnode" }, [this](const auto &path) {
wolv::io::File file(path, wolv::io::File::Mode::Read); wolv::io::File file(path, wolv::io::File::Mode::Read);
if (!file.isValid()) return false; if (!file.isValid()) return false;
ViewDataProcessor::loadNodes(*m_mainWorkspace, file.readString()); ViewDataProcessor::loadNodes(m_mainWorkspace, file.readString());
m_updateNodePositions = true; m_updateNodePositions = true;
return true; return true;
@ -884,7 +896,7 @@ namespace hex::plugin::builtin {
} }
void ViewDataProcessor::drawContent() { void ViewDataProcessor::drawContent() {
auto &workspace = *m_workspaceStack->back(); auto &workspace = *m_workspaceStack.back();
ImGui::BeginDisabled(m_evaluationTask.isRunning()); ImGui::BeginDisabled(m_evaluationTask.isRunning());
@ -962,7 +974,7 @@ namespace hex::plugin::builtin {
ImGuiExt::TextFormattedCentered("{}", "hex.builtin.view.data_processor.help_text"_lang); ImGuiExt::TextFormattedCentered("{}", "hex.builtin.view.data_processor.help_text"_lang);
// Draw a close button if there is more than one workspace on the stack // Draw a close button if there is more than one workspace on the stack
if (m_workspaceStack->size() > 1) { if (m_workspaceStack.size() > 1) {
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 1.5F, ImGui::GetTextLineHeightWithSpacing() * 0.2F)); ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 1.5F, ImGui::GetTextLineHeightWithSpacing() * 0.2F));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 4.0F)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 4.0F));
if (ImGuiExt::DimmedIconButton(ICON_VS_CLOSE, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) { if (ImGuiExt::DimmedIconButton(ICON_VS_CLOSE, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) {
@ -1079,7 +1091,7 @@ namespace hex::plugin::builtin {
// Remove the top-most workspace from the stack if requested // Remove the top-most workspace from the stack if requested
if (popWorkspace) { if (popWorkspace) {
m_workspaceStack->pop_back(); m_workspaceStack.pop_back();
m_updateNodePositions = true; m_updateNodePositions = true;
} }
} }

View File

@ -36,6 +36,8 @@
#include <content/global_actions.hpp> #include <content/global_actions.hpp>
#include <fonts/fonts.hpp> #include <fonts/fonts.hpp>
#include <hex/project/project.hpp>
#include <hex/project/project_manager.hpp>
#include <ui/menu_items.hpp> #include <ui/menu_items.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -333,9 +335,9 @@ namespace hex::plugin::builtin {
const auto availableSize = g.CurrentWindow->Size; const auto availableSize = g.CurrentWindow->Size;
const auto windowPosition = ImGui::GetCursorScreenPos(); const auto windowPosition = ImGui::GetCursorScreenPos();
auto textEditorSize = availableSize; auto textEditorSize = availableSize;
textEditorSize.y *= 3.5 / 5.0; textEditorSize.y *= 3.5F / 5.0F;
textEditorSize.y -= ImGui::GetTextLineHeightWithSpacing(); textEditorSize.y -= ImGui::GetTextLineHeightWithSpacing();
textEditorSize.y = std::clamp(textEditorSize.y + height,200.0F, availableSize.y-200.0F); textEditorSize.y = std::clamp(textEditorSize.y + height, 200.0F, availableSize.y - 200.0F);
if (g.NavWindow != nullptr) { if (g.NavWindow != nullptr) {
std::string name = g.NavWindow->Name; std::string name = g.NavWindow->Name;
@ -1425,7 +1427,7 @@ namespace hex::plugin::builtin {
const auto &currScope = evaluator->getScope(-m_debuggerScopeIndex); const auto &currScope = evaluator->getScope(-m_debuggerScopeIndex);
if (ImGui::BeginCombo("##scope", displayValue(currScope.parent, m_debuggerScopeIndex).c_str())) { if (ImGui::BeginCombo("##scope", displayValue(currScope.parent, m_debuggerScopeIndex).c_str())) {
for (size_t i = 0; i < evaluator->getScopeCount(); i++) { for (size_t i = 0; i < evaluator->getScopeCount(); i++) {
auto &scope = evaluator->getScope(-i); auto &scope = evaluator->getScope(-i32(i));
if (ImGui::Selectable(displayValue(scope.parent, i).c_str(), i == size_t(m_debuggerScopeIndex))) { if (ImGui::Selectable(displayValue(scope.parent, i).c_str(), i == size_t(m_debuggerScopeIndex))) {
m_debuggerScopeIndex = i; m_debuggerScopeIndex = i;
@ -2299,6 +2301,16 @@ namespace hex::plugin::builtin {
} }
}); });
proj::ProjectManager::registerContentHandler({
.type = "hex.builtin.project.content_type.pattern",
.load = [this](const proj::Content &content) {
m_textEditor.SetText(content.getData());
},
.store = [this](proj::Content &content) {
content.setData(wolv::util::trim(m_textEditor.GetText()));
}
});
ShortcutManager::addShortcut(this, CTRLCMD + Keys::G + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.goto_line", [this] { ShortcutManager::addShortcut(this, CTRLCMD + Keys::G + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.goto_line", [this] {
m_openGotoLinePopUp = true; m_openGotoLinePopUp = true;
}); });

View File

@ -45,6 +45,7 @@ namespace hex::plugin::builtin {
void registerReportGenerators(); void registerReportGenerators();
void registerTutorials(); void registerTutorials();
void registerDataInformationSections(); void registerDataInformationSections();
void registerProjectExplorer();
void loadWorkspaces(); void loadWorkspaces();
void addWindowDecoration(); void addWindowDecoration();
@ -140,6 +141,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
registerReportGenerators(); registerReportGenerators();
registerTutorials(); registerTutorials();
registerDataInformationSections(); registerDataInformationSections();
registerProjectExplorer();
loadWorkspaces(); loadWorkspaces();
addWindowDecoration(); addWindowDecoration();
createWelcomeScreen(); createWelcomeScreen();