1
0
mirror of synced 2025-02-14 09:32:35 +01:00

feat: Implement paste behaviour popup when pasting over one-byte regions (#2004)

### Problem description
This PR implements the feature request described in #1995, that
describes a problem with the `Paste` vs `Paste all` commands. Users
could be thrown off by having `Ctrl+V` act as a simple "Paste over
selection", whereas it's generally accepted as a "Paste all".

### Implementation description
<!-- Explain what you did to correct the problem -->
This PR introduces a new setting, called "Paste behaviour" (under the
"Hex Editor" category).
This setting has three values:
- `Paste over selection`: the current implementation for ImHex. Pastes
only over the selection region, in this case pasting only one byte;
- `Paste everything`: allows ImHex's `Paste` to behave like a `Paste
all` when selecting one-byte regions;
- `Ask me next time`: prompts the user for a choice of behaviour
(default value).

*Note: as users generally use `Paste all` when selecting one-byte
regions, calling `Paste` when selecting over two or more bytes is not
affected by this change, and will still behave like the usual `Paste`
command.*

When selecting a one-byte region, and calling the Paste command, users
that have not defined a preferred behaviour in the settings will be
prompted to choose one, using a brand new popup. The popup also allows
the user to cancel, which will not change the settings' value, and will
cancel the paste action altogether.

### Screenshots
The new popup:

![image](https://github.com/user-attachments/assets/2b0fd532-d4e7-4209-9dd7-8a79278692ea)

The new setting:

![image](https://github.com/user-attachments/assets/2644c35e-7332-422e-8fae-ae8ad0507126)

### Additional things
I'm not very good with long descriptions, so I'm open to any suggestions
regarding the text that is included in the popup!
I do think however that we should keep a hint indicating that `Paste
all` is always an option, which could solve the issue altogether for
very new users.

---------

Signed-off-by: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com>
Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
BioTheWolff 2024-12-16 20:35:48 +01:00 committed by GitHub
parent bb99b9a0ef
commit 9375a60cd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 91 additions and 2 deletions

View File

@ -80,6 +80,12 @@ namespace hex::plugin::builtin {
void registerEvents();
void registerMenuItems();
/**
* Method dedicated to handling paste behaviour when using the normal "Paste" option.
* Decides what to do based on user settings, or opens a popup to let them decide.
*/
void processPasteBehaviour(const Region &selection);
ui::HexEditor m_hexEditor;
bool m_shouldOpenPopup = false;

View File

@ -490,6 +490,7 @@
"hex.builtin.setting.hex_editor.char_padding": "Extra character cell padding",
"hex.builtin.setting.hex_editor.highlight_color": "Selection highlight color",
"hex.builtin.setting.hex_editor.pattern_parent_highlighting": "Highlight pattern parents on hover",
"hex.builtin.setting.hex_editor.paste_behaviour": "Single-Byte Paste behaviour",
"hex.builtin.setting.hex_editor.sync_scrolling": "Synchronize editor scroll position",
"hex.builtin.setting.imhex": "ImHex",
"hex.builtin.setting.imhex.recent_files": "Recent Files",
@ -819,6 +820,11 @@
"hex.builtin.view.hex_editor.menu.edit.open_in_new_provider": "Open selection view...",
"hex.builtin.view.hex_editor.menu.edit.paste": "Paste",
"hex.builtin.view.hex_editor.menu.edit.paste_as": "Paste...",
"hex.builtin.view.hex_editor.menu.edit.paste.popup.title": "Choose paste behaviour",
"hex.builtin.view.hex_editor.menu.edit.paste.popup.description": "When pasting values into the Hex Editor View, ImHex will only overwrite the bytes that are currently selected. If only a single byte is selected however, this can feel counter-intuitive. Would you like to paste the entire content of your clipboard if only one byte is selected or should still only the selected bytes be replaced?",
"hex.builtin.view.hex_editor.menu.edit.paste.popup.hint": "Note: If you want to ensure pasting everything at all times, the 'Paste all' command is also available in the Edit Menu!",
"hex.builtin.view.hex_editor.menu.edit.paste.popup.button.everything": "Paste everything",
"hex.builtin.view.hex_editor.menu.edit.paste.popup.button.selection": "Paste only over selection",
"hex.builtin.view.hex_editor.menu.edit.paste_all": "Paste all",
"hex.builtin.view.hex_editor.menu.edit.paste_all_string": "Paste all as string",
"hex.builtin.view.hex_editor.menu.edit.remove": "Remove...",

View File

@ -835,6 +835,12 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.pattern_parent_highlighting", true);
std::vector<std::string> pasteBehaviourNames = { "Ask me next time", "Paste everything", "Paste over selection" };
std::vector<nlohmann::json> pasteBehaviourValues = { "none", "everything", "selection" };
ContentRegistry::Settings::add<Widgets::DropDown>("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.paste_behaviour",
pasteBehaviourNames,
pasteBehaviourValues,
"none");
}
/* Fonts */

View File

@ -483,6 +483,47 @@ namespace hex::plugin::builtin {
std::string m_input;
};
class PopupPasteBehaviour final : public ViewHexEditor::Popup {
public:
explicit PopupPasteBehaviour(const Region &selection, const auto &pasteCallback) : m_selection(), m_pasteCallback(pasteCallback) {
m_selection = Region { .address=selection.getStartAddress(), .size=selection.getSize() };
}
void draw(ViewHexEditor *editor) override {
const auto width = ImGui::GetWindowWidth();
ImGui::TextWrapped("%s", "hex.builtin.view.hex_editor.menu.edit.paste.popup.description"_lang.get());
ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.paste.popup.hint"_lang);
ImGui::Separator();
if (ImGui::Button("hex.builtin.view.hex_editor.menu.edit.paste.popup.button.selection"_lang, ImVec2(width / 4, 0))) {
m_pasteCallback(m_selection, true);
editor->closePopup();
}
ImGui::SameLine();
if (ImGui::Button("hex.builtin.view.hex_editor.menu.edit.paste.popup.button.everything"_lang, ImVec2(width / 4, 0))) {
m_pasteCallback(m_selection, false);
editor->closePopup();
}
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - (width / 6));
if (ImGui::Button("hex.ui.common.cancel"_lang, ImVec2(width / 6, 0))) {
// Cancel the action, without updating settings nor pasting.
editor->closePopup();
}
}
[[nodiscard]] UnlocalizedString getTitle() const override {
return "hex.builtin.view.hex_editor.menu.edit.paste.popup.title"_lang;
}
private:
Region m_selection;
std::function<void(const Region &selection, bool selectionCheck)> m_pasteCallback;
};
/* Hex Editor */
ViewHexEditor::ViewHexEditor() : View::Window("hex.builtin.view.hex_editor.name", ICON_VS_FILE_BINARY) {
@ -730,6 +771,36 @@ namespace hex::plugin::builtin {
provider->write(selection.getStartAddress(), buffer.data(), size);
}
void ViewHexEditor::processPasteBehaviour(const Region &selection) {
if (selection.getSize() > 1) {
// Apply normal "paste over selection" behaviour when pasting over several bytes
pasteBytes(selection, true, false);
return;
}
// Selection is over one byte, we have to check the settings to decide the course of action
auto setting = ContentRegistry::Settings::read<std::string>(
"hex.builtin.setting.hex_editor",
"hex.builtin.setting.hex_editor.paste_behaviour",
"none");
if (setting == "everything")
pasteBytes(selection, false, false);
else if (setting == "selection")
pasteBytes(selection, true, false);
else
this->openPopup<PopupPasteBehaviour>(selection,
[](const Region &selection, const bool selectionCheck) {
ContentRegistry::Settings::write<std::string>(
"hex.builtin.setting.hex_editor",
"hex.builtin.setting.hex_editor.paste_behaviour",
selectionCheck ? "selection" : "everything");
pasteBytes(selection, selectionCheck, false);
});
}
static void copyString(const Region &selection) {
auto provider = ImHexApi::Provider::get();
if (provider == nullptr)
@ -1190,8 +1261,8 @@ namespace hex::plugin::builtin {
/* Paste */
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste" }, ICON_VS_OUTPUT, 1450, CurrentView + CTRLCMD + Keys::V,
[] {
pasteBytes(ImHexApi::HexEditor::getSelection().value_or( ImHexApi::HexEditor::ProviderRegion(Region { 0, 0 }, ImHexApi::Provider::get())), true, false);
[this] {
processPasteBehaviour(ImHexApi::HexEditor::getSelection().value_or( ImHexApi::HexEditor::ProviderRegion(Region { 0, 0 }, ImHexApi::Provider::get())));
},
ImHexApi::HexEditor::isSelectionValid,
this);