diff --git a/Userland/Applications/HexEditor/CMakeLists.txt b/Userland/Applications/HexEditor/CMakeLists.txt index 8e20117420e..ab58a31109c 100644 --- a/Userland/Applications/HexEditor/CMakeLists.txt +++ b/Userland/Applications/HexEditor/CMakeLists.txt @@ -1,10 +1,13 @@ compile_gml(HexEditorWindow.gml HexEditorWindowGML.h hex_editor_window_gml) +compile_gml(GoToOffsetDialog.gml GoToOffsetDialogGML.h go_to_offset_dialog_gml) set(SOURCES HexEditor.cpp HexEditorWidget.cpp FindDialog.cpp + GoToOffsetDialog.cpp main.cpp + GoToOffsetDialogGML.h HexEditorWindowGML.h ) diff --git a/Userland/Applications/HexEditor/GoToOffsetDialog.cpp b/Userland/Applications/HexEditor/GoToOffsetDialog.cpp new file mode 100644 index 00000000000..a74a60adc2a --- /dev/null +++ b/Userland/Applications/HexEditor/GoToOffsetDialog.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GoToOffsetDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int GoToOffsetDialog::show(GUI::Window* parent_window, int& history_offset, int& out_offset, int selection_offset, int buffer_size) +{ + auto dialog = GoToOffsetDialog::construct(); + dialog->m_selection_offset = selection_offset; + dialog->m_buffer_size = buffer_size; + + if (parent_window) + dialog->set_icon(parent_window->icon()); + + if (history_offset) + dialog->m_text_editor->set_text(String::formatted("{}", history_offset)); + + auto result = dialog->exec(); + + if (result != GUI::Dialog::ExecOK) + return result; + + auto input_offset = dialog->process_input(); + history_offset = move(input_offset); + + auto new_offset = dialog->calculate_new_offset(input_offset); + dbgln("Go to offset: value={}", new_offset); + out_offset = move(new_offset); + + return GUI::Dialog::ExecOK; +} + +int GoToOffsetDialog::process_input() +{ + auto input_offset = m_text_editor->text().trim_whitespace(); + int offset; + auto type = m_offset_type_box->text().trim_whitespace(); + if (type == "Decimal") { + offset = String::formatted("{}", input_offset).to_int().value_or(0); + } else if (type == "Hexadecimal") { + offset = strtol(String::formatted("{}", input_offset).characters(), nullptr, 16); + } else { + VERIFY_NOT_REACHED(); + } + return offset; +} + +int GoToOffsetDialog::calculate_new_offset(int input_offset) +{ + int new_offset; + auto from = m_offset_from_box->text().trim_whitespace(); + if (from == "Start") { + new_offset = input_offset; + } else if (from == "End") { + new_offset = m_buffer_size - input_offset; + } else if (from == "Here") { + new_offset = input_offset + m_selection_offset; + } else { + VERIFY_NOT_REACHED(); + } + + if (new_offset > m_buffer_size) + new_offset = m_buffer_size; + if (new_offset < 0) + new_offset = 0; + + return new_offset; +} + +void GoToOffsetDialog::update_statusbar() +{ + auto new_offset = calculate_new_offset(process_input()); + m_statusbar->set_text(0, String::formatted("HEX: {:#08X}", new_offset)); + m_statusbar->set_text(1, String::formatted("DEC: {}", new_offset)); +} + +GoToOffsetDialog::GoToOffsetDialog() + : Dialog(nullptr) +{ + resize(300, 80); + center_on_screen(); + set_resizable(false); + set_title("Go to Offset"); + + auto& main_widget = set_main_widget(); + if (!main_widget.load_from_gml(go_to_offset_dialog_gml)) + VERIFY_NOT_REACHED(); + + m_text_editor = *main_widget.find_descendant_of_type_named("text_editor"); + m_go_button = *main_widget.find_descendant_of_type_named("go_button"); + m_offset_type_box = *main_widget.find_descendant_of_type_named("offset_type"); + m_offset_from_box = *main_widget.find_descendant_of_type_named("offset_from"); + m_statusbar = *main_widget.find_descendant_of_type_named("statusbar"); + + m_offset_type.append("Decimal"); + m_offset_type.append("Hexadecimal"); + m_offset_type_box->set_model(GUI::ItemListModel::create(m_offset_type)); + m_offset_type_box->set_selected_index(0); + m_offset_type_box->set_only_allow_values_from_model(true); + + m_offset_from.append("Start"); + m_offset_from.append("Here"); + m_offset_from.append("End"); + m_offset_from_box->set_model(GUI::ItemListModel::create(m_offset_from)); + m_offset_from_box->set_selected_index(0); + m_offset_from_box->set_only_allow_values_from_model(true); + + m_text_editor->on_return_pressed = [this] { + m_go_button->click(); + }; + + m_go_button->on_click = [this](auto) { + done(ExecResult::ExecOK); + }; + + m_text_editor->on_change = [this]() { + auto text = m_text_editor->text(); + if (text.starts_with("0x")) { + text.replace("0x", ""); + m_offset_type_box->set_selected_index(1); + m_text_editor->set_text(text); + } + update_statusbar(); + }; + + m_offset_type_box->on_change = [this]() { + update_statusbar(); + }; + + m_offset_from_box->on_change = [this]() { + update_statusbar(); + }; + + update_statusbar(); +} + +GoToOffsetDialog::~GoToOffsetDialog() +{ +} diff --git a/Userland/Applications/HexEditor/GoToOffsetDialog.gml b/Userland/Applications/HexEditor/GoToOffsetDialog.gml new file mode 100644 index 00000000000..3234f5dc206 --- /dev/null +++ b/Userland/Applications/HexEditor/GoToOffsetDialog.gml @@ -0,0 +1,63 @@ +@GUI::Widget { + name: "main" + fixed_width: 300 + fixed_height: 80 + fill_with_background_color: true + + layout: @GUI::VerticalBoxLayout { + spacing: 2 + margins: [0, 0, 0, 0] + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 2 + margins: [2, 2, 2, 2] + } + + @GUI::Label { + text: "Offset" + text_alignment: "CenterLeft" + fixed_width: 50 + } + + @GUI::TextBox { + name: "text_editor" + fixed_width: 100 + } + + @GUI::ComboBox { + name: "offset_type" + fixed_width: 100 + } + + @GUI::Button { + name: "go_button" + text: "Go" + fixed_width: 40 + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 2 + margins: [2, 2, 2, 2] + } + + @GUI::Label { + text: "From" + text_alignment: "CenterLeft" + fixed_width: 50 + } + + @GUI::ComboBox { + name: "offset_from" + fixed_width: 100 + } + } + + @GUI::Statusbar { + name: "statusbar" + label_count: 2 + } +} diff --git a/Userland/Applications/HexEditor/GoToOffsetDialog.h b/Userland/Applications/HexEditor/GoToOffsetDialog.h new file mode 100644 index 00000000000..358b04bf6db --- /dev/null +++ b/Userland/Applications/HexEditor/GoToOffsetDialog.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +class GoToOffsetDialog : public GUI::Dialog { + C_OBJECT(GoToOffsetDialog); + +public: + static int show(GUI::Window* parent_window, int& history_offset, int& out_offset, int selection_offset, int end); + +private: + GoToOffsetDialog(); + virtual ~GoToOffsetDialog() override; + void update_statusbar(); + int process_input(); + int calculate_new_offset(int offset); + int m_selection_offset { 0 }; + int m_buffer_size { 0 }; + Vector m_offset_type; + Vector m_offset_from; + + RefPtr m_text_editor; + RefPtr m_go_button; + RefPtr m_offset_type_box; + RefPtr m_offset_from_box; + RefPtr m_statusbar; +}; diff --git a/Userland/Applications/HexEditor/HexEditor.h b/Userland/Applications/HexEditor/HexEditor.h index 4406a3a849e..70da18a0f30 100644 --- a/Userland/Applications/HexEditor/HexEditor.h +++ b/Userland/Applications/HexEditor/HexEditor.h @@ -29,12 +29,14 @@ public: bool is_readonly() const { return m_readonly; } void set_readonly(bool); + int buffer_size() const { return m_buffer.size(); } void set_buffer(const ByteBuffer&); void fill_selection(u8 fill_byte); bool write_to_file(const String& path); void select_all(); bool has_selection() const { return !(m_selection_start == -1 || m_selection_end == -1 || (m_selection_end - m_selection_start) < 0 || m_buffer.is_empty()); } + int selection_start_offset() const { return m_selection_start; } bool copy_selected_text_to_clipboard(); bool copy_selected_hex_to_clipboard(); bool copy_selected_hex_to_clipboard_as_c_code(); diff --git a/Userland/Applications/HexEditor/HexEditorWidget.cpp b/Userland/Applications/HexEditor/HexEditorWidget.cpp index 9c6c6057da9..ba99de42005 100644 --- a/Userland/Applications/HexEditor/HexEditorWidget.cpp +++ b/Userland/Applications/HexEditor/HexEditorWidget.cpp @@ -6,6 +6,7 @@ #include "HexEditorWidget.h" #include "FindDialog.h" +#include "GoToOffsetDialog.h" #include #include #include @@ -131,6 +132,20 @@ HexEditorWidget::HexEditorWidget() } }); + m_goto_offset_action = GUI::Action::create("&Go to Offset ...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-to.png"), [this](const GUI::Action&) { + int new_offset; + auto result = GoToOffsetDialog::show( + window(), + m_goto_history, + new_offset, + m_editor->selection_start_offset(), + m_editor->buffer_size()); + if (result == GUI::InputBox::ExecOK) { + m_editor->highlight(new_offset, new_offset); + m_editor->update(); + } + }); + m_layout_toolbar_action = GUI::Action::create_checkable("&Toolbar", [&](auto& action) { m_toolbar_container->set_visible(action.is_checked()); }); @@ -140,6 +155,7 @@ HexEditorWidget::HexEditorWidget() m_toolbar->add_action(*m_save_action); m_toolbar->add_separator(); m_toolbar->add_action(*m_find_action); + m_toolbar->add_action(*m_goto_offset_action); m_editor->set_focus(true); } @@ -162,23 +178,6 @@ void HexEditorWidget::initialize_menubar(GUI::Menubar& menubar) GUI::Application::the()->quit(); })); - m_goto_decimal_offset_action = GUI::Action::create("&Go to Offset (Decimal)...", { Mod_Ctrl | Mod_Shift, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](const GUI::Action&) { - String value; - if (GUI::InputBox::show(window(), value, "Enter decimal offset:", "Go to Offset") == GUI::InputBox::ExecOK && !value.is_empty()) { - auto new_offset = value.to_int(); - if (new_offset.has_value()) - m_editor->set_position(new_offset.value()); - } - }); - - m_goto_hex_offset_action = GUI::Action::create("Go to &Offset (Hex)...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](const GUI::Action&) { - String value; - if (GUI::InputBox::show(window(), value, "Enter hexadecimal offset:", "Go to Offset") == GUI::InputBox::ExecOK && !value.is_empty()) { - auto new_offset = strtol(value.characters(), nullptr, 16); - m_editor->set_position(new_offset); - } - }); - auto& edit_menu = menubar.add_menu("&Edit"); edit_menu.add_action(GUI::CommonActions::make_select_all_action([this](auto&) { m_editor->select_all(); @@ -192,9 +191,6 @@ void HexEditorWidget::initialize_menubar(GUI::Menubar& menubar) } })); edit_menu.add_separator(); - edit_menu.add_action(*m_goto_decimal_offset_action); - edit_menu.add_action(*m_goto_hex_offset_action); - edit_menu.add_separator(); edit_menu.add_action(GUI::Action::create("Copy &Hex", { Mod_Ctrl, Key_C }, [&](const GUI::Action&) { m_editor->copy_selected_hex_to_clipboard(); })); @@ -220,6 +216,8 @@ void HexEditorWidget::initialize_menubar(GUI::Menubar& menubar) m_editor->update(); m_last_found_index = result; })); + edit_menu.add_separator(); + edit_menu.add_action(*m_goto_offset_action); auto& view_menu = menubar.add_menu("&View"); view_menu.add_action(*m_layout_toolbar_action); diff --git a/Userland/Applications/HexEditor/HexEditorWidget.h b/Userland/Applications/HexEditor/HexEditorWidget.h index 24ebc3cddda..6d0b8d1ee97 100644 --- a/Userland/Applications/HexEditor/HexEditorWidget.h +++ b/Userland/Applications/HexEditor/HexEditorWidget.h @@ -35,6 +35,7 @@ private: String m_name; String m_extension; + int m_goto_history { 0 }; String m_search_text; ByteBuffer m_search_buffer; int last_found_index() const { return m_last_found_index == -1 ? 0 : m_last_found_index; } @@ -45,8 +46,7 @@ private: RefPtr m_save_action; RefPtr m_save_as_action; RefPtr m_find_action; - RefPtr m_goto_decimal_offset_action; - RefPtr m_goto_hex_offset_action; + RefPtr m_goto_offset_action; RefPtr m_layout_toolbar_action; GUI::ActionGroup m_bytes_per_row_actions;