From 92803c1536db5ed919ac1f2f78db43c062b3a434 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Fri, 29 Mar 2024 13:22:21 +0100 Subject: [PATCH] feat: Added simple insert mode to hex editor --- lib/libimhex/source/providers/undo/stack.cpp | 1 + plugins/builtin/romfs/lang/en_US.json | 1 + .../source/content/views/view_hex_editor.cpp | 22 +++++- plugins/ui/include/ui/hex_editor.hpp | 28 ++++++++ plugins/ui/source/ui/hex_editor.cpp | 71 +++++++++++++++---- 5 files changed, 107 insertions(+), 16 deletions(-) diff --git a/lib/libimhex/source/providers/undo/stack.cpp b/lib/libimhex/source/providers/undo/stack.cpp index efbda320c..a7213068f 100644 --- a/lib/libimhex/source/providers/undo/stack.cpp +++ b/lib/libimhex/source/providers/undo/stack.cpp @@ -79,6 +79,7 @@ namespace hex::prv::undo { for (u32 i = 0; i < count; i += 1) { i64 index = startIndex + i; + m_undoStack[index]->undo(m_provider); operation->addOperation(std::move(m_undoStack[index])); } diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index b876d71a3..dd837ae3a 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -776,6 +776,7 @@ "hex.builtin.view.hex_editor.menu.edit.cut": "Cut", "hex.builtin.view.hex_editor.menu.edit.fill": "Fill...", "hex.builtin.view.hex_editor.menu.edit.insert": "Insert...", + "hex.builtin.view.hex_editor.menu.edit.insert_mode": "Insert Mode", "hex.builtin.view.hex_editor.menu.edit.jump_to": "Jump to", "hex.builtin.view.hex_editor.menu.edit.jump_to.curr_pattern": "Current Pattern", "hex.builtin.view.hex_editor.menu.edit.open_in_new_provider": "Open selection view...", diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 85e4c4e85..dfa68137a 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -1117,7 +1117,7 @@ namespace hex::plugin::builtin { /* Remove */ ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.remove" }, ICON_VS_CLEAR_ALL, 1800, Shortcut::None, [this] { - auto selection = ImHexApi::HexEditor::getSelection(); + auto selection = ImHexApi::HexEditor::getSelection(); this->openPopup(selection->getStartAddress(), selection->getSize()); }, @@ -1126,12 +1126,30 @@ namespace hex::plugin::builtin { /* Fill */ ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.fill" }, ICON_VS_PAINTCAN, 1810, Shortcut::None, [this] { - auto selection = ImHexApi::HexEditor::getSelection(); + auto selection = ImHexApi::HexEditor::getSelection(); this->openPopup(selection->getStartAddress(), selection->getSize()); }, [] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable(); }); + /* Toggle Overwrite/Insert mode */ + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.insert_mode" }, ICON_VS_PENCIL, 1820, Shortcut::None, + [this] { + if (m_hexEditor.getMode() == ui::HexEditor::Mode::Insert) + m_hexEditor.setMode(ui::HexEditor::Mode::Overwrite); + else + m_hexEditor.setMode(ui::HexEditor::Mode::Insert); + }, + [] { + return ImHexApi::HexEditor::isSelectionValid() && + ImHexApi::Provider::isValid() && + ImHexApi::Provider::get()->isWritable() && + ImHexApi::Provider::get()->isResizable(); + }, + [this] { + return m_hexEditor.getMode() == ui::HexEditor::Mode::Insert; + }); + /* Jump to */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.jump_to" }, ICON_VS_DEBUG_STEP_OUT, 1850, [] { diff --git a/plugins/ui/include/ui/hex_editor.hpp b/plugins/ui/include/ui/hex_editor.hpp index 370e1eb95..1329ab723 100644 --- a/plugins/ui/include/ui/hex_editor.hpp +++ b/plugins/ui/include/ui/hex_editor.hpp @@ -156,6 +156,11 @@ namespace hex::ui { EventRegionSelected::post(ImHexApi::HexEditor::ProviderRegion{ { selection.address, selection.size }, m_provider }); m_shouldModifyValue = true; } + + if (m_mode == Mode::Insert) { + m_selectionStart = m_selectionEnd; + m_cursorBlinkTimer = -0.3F; + } } [[nodiscard]] Region getSelection() const { @@ -294,6 +299,26 @@ namespace hex::ui { m_editingAddress = std::nullopt; } + enum class Mode { Overwrite, Insert }; + void setMode(Mode mode) { + if (mode == Mode::Insert) { + // Don't enter insert mode if the provider doesn't support resizing the underlying data + if (!m_provider->isResizable()) + return; + + // Get rid of any selection in insert mode + m_selectionStart = m_selectionEnd; + m_cursorPosition = m_selectionEnd; + m_selectionChanged = true; + } + + m_mode = mode; + } + + [[nodiscard]] Mode getMode() const { + return m_mode; + } + private: prv::Provider *m_provider = nullptr; @@ -346,6 +371,9 @@ namespace hex::ui { static void defaultTooltipCallback(u64, const u8 *, size_t) { } std::function(u64, const u8 *, size_t)> m_foregroundColorCallback = defaultColorCallback, m_backgroundColorCallback = defaultColorCallback; std::function m_tooltipCallback = defaultTooltipCallback; + + Mode m_mode = Mode::Overwrite; + float m_cursorBlinkTimer = -0.3F; }; } diff --git a/plugins/ui/source/ui/hex_editor.cpp b/plugins/ui/source/ui/hex_editor.cpp index 5725d5957..035553d8f 100644 --- a/plugins/ui/source/ui/hex_editor.cpp +++ b/plugins/ui/source/ui/hex_editor.cpp @@ -91,6 +91,9 @@ namespace hex::ui { } std::optional HexEditor::applySelectionColor(u64 byteAddress, std::optional color) { + if (m_mode == Mode::Insert) + return color.value_or(0); + if (isSelectionValid()) { auto selection = getSelection(); @@ -287,7 +290,13 @@ namespace hex::ui { m_enteredEditingMode = true; m_editingBytes.resize(size); - std::memcpy(m_editingBytes.data(), data, size); + if (m_mode == Mode::Overwrite) + std::memcpy(m_editingBytes.data(), data, size); + else if (m_mode == Mode::Insert) { + std::memset(m_editingBytes.data(), 0x00, size); + m_provider->insert(address, size); + } + m_editingCellType = cellType; } } @@ -342,9 +351,20 @@ namespace hex::ui { if (nextEditingAddress >= m_provider->getBaseAddress() + m_provider->getCurrentPageAddress() + m_provider->getSize()) m_editingAddress = std::nullopt; - else + else { m_editingAddress = nextEditingAddress; + + if (m_mode == Mode::Insert) { + std::memset(m_editingBytes.data(), 0x00, size); + m_provider->getUndoStack().groupOperations(2, "hex.builtin.undo_operation.insert"); + m_provider->insert(nextEditingAddress, size); + } + } } else { + if (m_mode == Mode::Insert) { + m_provider->undo(); + } + m_editingAddress = std::nullopt; } @@ -379,21 +399,34 @@ namespace hex::ui { const color_t SelectionFrameColor = ImGui::GetColorU32(ImGuiCol_Text); - // Draw vertical line at the left of first byte and the start of the line - if (x == 0 || byteAddress == selection.getStartAddress()) - drawList->AddLine(cellPos, cellPos + ImVec2(0, cellSize.y), ImColor(SelectionFrameColor), 1_scaled); + switch (m_mode) { + case Mode::Overwrite: { + // Draw vertical line at the left of first byte and the start of the line + if (x == 0 || byteAddress == selection.getStartAddress()) + drawList->AddLine(cellPos, cellPos + ImVec2(0, cellSize.y), ImColor(SelectionFrameColor), 1_scaled); - // Draw vertical line at the right of the last byte and the end of the line - if (x == u16((m_bytesPerRow / bytesPerCell) - 1) || (byteAddress + bytesPerCell) > selection.getEndAddress()) - drawList->AddLine(cellPos + ImVec2(cellSize.x, -1), cellPos + cellSize, ImColor(SelectionFrameColor), 1_scaled); + // Draw vertical line at the right of the last byte and the end of the line + if (x == u16((m_bytesPerRow / bytesPerCell) - 1) || (byteAddress + bytesPerCell) > selection.getEndAddress()) + drawList->AddLine(cellPos + ImVec2(cellSize.x, -1), cellPos + cellSize, ImColor(SelectionFrameColor), 1_scaled); - // Draw horizontal line at the top of the bytes - if (y == 0 || (byteAddress - m_bytesPerRow) < selection.getStartAddress()) - drawList->AddLine(cellPos, cellPos + ImVec2(cellSize.x + 1, 0), ImColor(SelectionFrameColor), 1_scaled); + // Draw horizontal line at the top of the bytes + if (y == 0 || (byteAddress - m_bytesPerRow) < selection.getStartAddress()) + drawList->AddLine(cellPos, cellPos + ImVec2(cellSize.x + 1, 0), ImColor(SelectionFrameColor), 1_scaled); - // Draw horizontal line at the bottom of the bytes - if ((byteAddress + m_bytesPerRow) > selection.getEndAddress()) - drawList->AddLine(cellPos + ImVec2(0, cellSize.y), cellPos + cellSize + ImVec2(1, 0), ImColor(SelectionFrameColor), 1_scaled); + // Draw horizontal line at the bottom of the bytes + if ((byteAddress + m_bytesPerRow) > selection.getEndAddress()) + drawList->AddLine(cellPos + ImVec2(0, cellSize.y), cellPos + cellSize + ImVec2(1, 0), ImColor(SelectionFrameColor), 1_scaled); + + break; + } + case Mode::Insert: { + bool cursorVisible = (!ImGui::GetIO().ConfigInputTextCursorBlink) || (m_cursorBlinkTimer <= 0.0f) || std::fmod(m_cursorBlinkTimer, 1.20f) <= 0.80f; + if (cursorVisible && byteAddress == selection.getStartAddress()) { + // Draw vertical line at the left of first byte and the start of the line + drawList->AddLine(cellPos, cellPos + ImVec2(0, cellSize.y), ImColor(SelectionFrameColor), 1_scaled); + } + } + } } void HexEditor::drawEditor(const ImVec2 &size) { @@ -422,6 +455,9 @@ namespace hex::ui { ImGuiExt::TextFormattedCentered("{}", "hex.ui.hex_editor.no_bytes"_lang); } + if (!m_editingAddress.has_value() && ImGui::IsKeyPressed(ImGuiKey_Escape)) + m_mode = Mode::Overwrite; + Region hoveredCell = Region::Invalid(); if (ImGui::BeginChild("Hex View", size, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { this->drawScrollbar(CharacterSize); @@ -898,6 +934,11 @@ namespace hex::ui { // Human-readable units ImGuiExt::DimmedIconToggle(ICON_VS_SYMBOL_NUMERIC, &m_showHumanReadableUnits); ImGuiExt::InfoTooltip("hex.ui.hex_editor.human_readable_units_footer"_lang); + + ImGui::SameLine(0, 10_scaled); + if (m_mode == Mode::Insert) { + ImGui::TextUnformatted("[ INSERT ]"); + } } // Collapse button @@ -1045,6 +1086,8 @@ namespace hex::ui { this->drawFooter(footerSize); m_selectionChanged = false; + + m_cursorBlinkTimer += ImGui::GetIO().DeltaTime; } }