HexEditor: Add 'Go to Offset...' dialog

This commit is contained in:
Brendan Coles 2021-05-23 08:36:30 +00:00 committed by Andreas Kling
parent dd6921bfc6
commit efef77a154
Notes: sideshowbarker 2024-07-18 17:30:08 +09:00
7 changed files with 276 additions and 22 deletions

View file

@ -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
)

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "GoToOffsetDialog.h"
#include <AK/String.h>
#include <Applications/HexEditor/GoToOffsetDialogGML.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/ComboBox.h>
#include <LibGUI/ItemListModel.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Statusbar.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/Widget.h>
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<GUI::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<GUI::TextBox>("text_editor");
m_go_button = *main_widget.find_descendant_of_type_named<GUI::Button>("go_button");
m_offset_type_box = *main_widget.find_descendant_of_type_named<GUI::ComboBox>("offset_type");
m_offset_from_box = *main_widget.find_descendant_of_type_named<GUI::ComboBox>("offset_from");
m_statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
m_offset_type.append("Decimal");
m_offset_type.append("Hexadecimal");
m_offset_type_box->set_model(GUI::ItemListModel<String>::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<String>::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()
{
}

View file

@ -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
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Result.h>
#include <AK/Vector.h>
#include <LibGUI/Dialog.h>
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<String> m_offset_type;
Vector<String> m_offset_from;
RefPtr<GUI::TextEditor> m_text_editor;
RefPtr<GUI::Button> m_go_button;
RefPtr<GUI::ComboBox> m_offset_type_box;
RefPtr<GUI::ComboBox> m_offset_from_box;
RefPtr<GUI::Statusbar> m_statusbar;
};

View file

@ -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();

View file

@ -6,6 +6,7 @@
#include "HexEditorWidget.h"
#include "FindDialog.h"
#include "GoToOffsetDialog.h"
#include <AK/Optional.h>
#include <AK/StringBuilder.h>
#include <Applications/HexEditor/HexEditorWindowGML.h>
@ -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);

View file

@ -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<GUI::Action> m_save_action;
RefPtr<GUI::Action> m_save_as_action;
RefPtr<GUI::Action> m_find_action;
RefPtr<GUI::Action> m_goto_decimal_offset_action;
RefPtr<GUI::Action> m_goto_hex_offset_action;
RefPtr<GUI::Action> m_goto_offset_action;
RefPtr<GUI::Action> m_layout_toolbar_action;
GUI::ActionGroup m_bytes_per_row_actions;