HexEditor: Add Value Inspector
This adds a value inspector window to the Hex Editor. This window shows the data at the current cursor position (or selection start if a range is selected) interpreted as a variety of data types. Currently supported values include 8, 16, 32, and 64 bit signed and unsigned values as well as float and double. The inspector can operate in both little endian and big endian modes. This is switched between by options in the View menu.
This commit is contained in:
parent
604557c71b
commit
6a4fdae575
Notes:
sideshowbarker
2024-07-17 14:18:34 +09:00
Author: https://github.com/tslater2006 Commit: https://github.com/SerenityOS/serenity/commit/6a4fdae575 Pull-request: https://github.com/SerenityOS/serenity/pull/12861 Reviewed-by: https://github.com/MacDue Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/kleinesfilmroellchen ✅
4 changed files with 313 additions and 6 deletions
|
@ -2,6 +2,7 @@
|
|||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -10,6 +11,7 @@
|
|||
#include "FindDialog.h"
|
||||
#include "GoToOffsetDialog.h"
|
||||
#include "SearchResultsModel.h"
|
||||
#include "ValueInspectorModel.h"
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <Applications/HexEditor/HexEditorWindowGML.h>
|
||||
|
@ -44,6 +46,17 @@ HexEditorWidget::HexEditorWidget()
|
|||
m_statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
|
||||
m_search_results = *find_descendant_of_type_named<GUI::TableView>("search_results");
|
||||
m_search_results_container = *find_descendant_of_type_named<GUI::Widget>("search_results_container");
|
||||
m_side_panel_container = *find_descendant_of_type_named<GUI::Widget>("side_panel_container");
|
||||
m_value_inspector_container = *find_descendant_of_type_named<GUI::Widget>("value_inspector_container");
|
||||
m_value_inspector = *find_descendant_of_type_named<GUI::TableView>("value_inspector");
|
||||
|
||||
m_value_inspector->on_activation = [this](GUI::ModelIndex const& index) {
|
||||
if (!index.is_valid())
|
||||
return;
|
||||
m_selecting_from_inspector = true;
|
||||
m_editor->set_selection(m_editor->selection_start_offset(), index.data(GUI::ModelRole::Custom).as_i32());
|
||||
m_editor->update();
|
||||
};
|
||||
|
||||
m_editor->on_status_change = [this](int position, HexEditor::EditMode edit_mode, int selection_start, int selection_end) {
|
||||
m_statusbar->set_text(0, String::formatted("Offset: {:#08X}", position));
|
||||
|
@ -57,6 +70,11 @@ HexEditorWidget::HexEditorWidget()
|
|||
m_copy_text_action->set_enabled(has_selection);
|
||||
m_copy_as_c_code_action->set_enabled(has_selection);
|
||||
m_fill_selection_action->set_enabled(has_selection);
|
||||
|
||||
if (m_value_inspector_container->is_visible() && !m_selecting_from_inspector) {
|
||||
update_inspector_values(selection_start);
|
||||
}
|
||||
m_selecting_from_inspector = false;
|
||||
};
|
||||
|
||||
m_editor->on_change = [this] {
|
||||
|
@ -213,6 +231,10 @@ HexEditorWidget::HexEditorWidget()
|
|||
});
|
||||
m_fill_selection_action->set_enabled(false);
|
||||
|
||||
m_layout_value_inspector_action = GUI::Action::create_checkable("&Value Inspector", [&](auto& action) {
|
||||
set_value_inspector_visible(action.is_checked());
|
||||
});
|
||||
|
||||
m_toolbar->add_action(*m_new_action);
|
||||
m_toolbar->add_action(*m_open_action);
|
||||
m_toolbar->add_action(*m_save_action);
|
||||
|
@ -226,6 +248,94 @@ HexEditorWidget::HexEditorWidget()
|
|||
m_editor->set_focus(true);
|
||||
}
|
||||
|
||||
void HexEditorWidget::update_inspector_values(size_t position)
|
||||
{
|
||||
// build out primitive types like u8, i8, u16, etc
|
||||
size_t byte_read_count = 0;
|
||||
u64 unsigned_64_bit_int = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Optional<u8> read_result = m_editor->get_byte(position + i);
|
||||
u8 current_byte = 0;
|
||||
if (!read_result.has_value())
|
||||
break;
|
||||
|
||||
current_byte = read_result.release_value();
|
||||
if (m_value_inspector_little_endian)
|
||||
unsigned_64_bit_int = ((u64)current_byte << (8 * byte_read_count)) + unsigned_64_bit_int;
|
||||
else
|
||||
unsigned_64_bit_int = (unsigned_64_bit_int << 8) + current_byte;
|
||||
|
||||
++byte_read_count;
|
||||
}
|
||||
|
||||
if (!m_value_inspector_little_endian) {
|
||||
// if we didn't read far enough, lets finish shifting the bytes so the code below works
|
||||
size_t bytes_left_to_read = 8 - byte_read_count;
|
||||
unsigned_64_bit_int = (unsigned_64_bit_int << (8 * bytes_left_to_read));
|
||||
}
|
||||
|
||||
// Populate the model
|
||||
NonnullRefPtr<ValueInspectorModel> value_inspector_model = make_ref_counted<ValueInspectorModel>();
|
||||
if (byte_read_count >= 1) {
|
||||
u8 unsigned_byte_value = 0;
|
||||
if (m_value_inspector_little_endian)
|
||||
unsigned_byte_value = (unsigned_64_bit_int & 0xFF);
|
||||
else
|
||||
unsigned_byte_value = (unsigned_64_bit_int >> (64 - 8)) & 0xFF;
|
||||
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedByte, String::number(static_cast<i8>(unsigned_byte_value)));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedByte, String::number(unsigned_byte_value));
|
||||
} else {
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedByte, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedByte, "");
|
||||
}
|
||||
|
||||
if (byte_read_count >= 2) {
|
||||
u16 unsigned_short_value = 0;
|
||||
if (m_value_inspector_little_endian)
|
||||
unsigned_short_value = (unsigned_64_bit_int & 0xFFFF);
|
||||
else
|
||||
unsigned_short_value = (unsigned_64_bit_int >> (64 - 16)) & 0xFFFF;
|
||||
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedShort, String::number(static_cast<i16>(unsigned_short_value)));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedShort, String::number(unsigned_short_value));
|
||||
} else {
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedShort, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedShort, "");
|
||||
}
|
||||
|
||||
if (byte_read_count >= 4) {
|
||||
u32 unsigned_int_value = 0;
|
||||
if (m_value_inspector_little_endian)
|
||||
unsigned_int_value = (unsigned_64_bit_int & 0xFFFFFFFF);
|
||||
else
|
||||
unsigned_int_value = (unsigned_64_bit_int >> 32) & 0xFFFFFFFF;
|
||||
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedInt, String::number(static_cast<i32>(unsigned_int_value)));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedInt, String::number(unsigned_int_value));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::Float, String::number(bit_cast<float>(unsigned_int_value)));
|
||||
} else {
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedInt, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedInt, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::Float, "");
|
||||
}
|
||||
|
||||
if (byte_read_count >= 8) {
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedLong, String::number(static_cast<i64>(unsigned_64_bit_int)));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedLong, String::number(unsigned_64_bit_int));
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::Double, String::number(bit_cast<double>(unsigned_64_bit_int)));
|
||||
} else {
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::SignedLong, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::UnsignedLong, "");
|
||||
value_inspector_model->set_parsed_value(ValueInspectorModel::ValueType::Double, "");
|
||||
}
|
||||
|
||||
// FIXME: Parse as other values like ASCII, UTF8, UTF16, Timestamp etc
|
||||
|
||||
m_value_inspector->set_model(value_inspector_model);
|
||||
m_value_inspector->update();
|
||||
}
|
||||
|
||||
void HexEditorWidget::initialize_menubar(GUI::Window& window)
|
||||
{
|
||||
auto& file_menu = window.add_menu("&File");
|
||||
|
@ -291,6 +401,7 @@ void HexEditorWidget::initialize_menubar(GUI::Window& window)
|
|||
m_toolbar_container->set_visible(show_toolbar);
|
||||
view_menu.add_action(*m_layout_toolbar_action);
|
||||
view_menu.add_action(*m_layout_search_results_action);
|
||||
view_menu.add_action(*m_layout_value_inspector_action);
|
||||
view_menu.add_separator();
|
||||
|
||||
auto bytes_per_row = Config::read_i32("HexEditor", "Layout", "BytesPerRow", 16);
|
||||
|
@ -311,6 +422,25 @@ void HexEditorWidget::initialize_menubar(GUI::Window& window)
|
|||
action->set_checked(true);
|
||||
}
|
||||
|
||||
m_value_inspector_mode_actions.set_exclusive(true);
|
||||
auto& inspector_mode_menu = view_menu.add_submenu("Value Inspector &Mode");
|
||||
auto little_endian_mode = GUI::Action::create_checkable("&Little Endian", [&](auto& action) {
|
||||
m_value_inspector_little_endian = action.is_checked();
|
||||
update_inspector_values(m_editor->selection_start_offset());
|
||||
});
|
||||
m_value_inspector_mode_actions.add_action(little_endian_mode);
|
||||
inspector_mode_menu.add_action(little_endian_mode);
|
||||
|
||||
auto big_endian_mode = GUI::Action::create_checkable("&Big Endian", [this](auto& action) {
|
||||
m_value_inspector_little_endian = !action.is_checked();
|
||||
update_inspector_values(m_editor->selection_start_offset());
|
||||
});
|
||||
m_value_inspector_mode_actions.add_action(big_endian_mode);
|
||||
inspector_mode_menu.add_action(big_endian_mode);
|
||||
|
||||
// Default to little endian mode
|
||||
little_endian_mode->set_checked(true);
|
||||
|
||||
auto& help_menu = window.add_menu("&Help");
|
||||
help_menu.add_action(GUI::CommonActions::make_about_action("Hex Editor", GUI::Icon::default_icon("app-hex-editor"), &window));
|
||||
}
|
||||
|
@ -365,6 +495,21 @@ void HexEditorWidget::set_search_results_visible(bool visible)
|
|||
{
|
||||
m_layout_search_results_action->set_checked(visible);
|
||||
m_search_results_container->set_visible(visible);
|
||||
|
||||
// Ensure side panel container is visible if either search result or value inspector are turned on
|
||||
m_side_panel_container->set_visible(visible || m_value_inspector_container->is_visible());
|
||||
}
|
||||
|
||||
void HexEditorWidget::set_value_inspector_visible(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
update_inspector_values(m_editor->selection_start_offset());
|
||||
|
||||
m_layout_value_inspector_action->set_checked(visible);
|
||||
m_value_inspector_container->set_visible(visible);
|
||||
|
||||
// Ensure side panel container is visible if either search result or value inspector are turned on
|
||||
m_side_panel_container->set_visible(visible || m_search_results_container->is_visible());
|
||||
}
|
||||
|
||||
void HexEditorWidget::drop_event(GUI::DropEvent& event)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -9,6 +10,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "HexEditor.h"
|
||||
#include "ValueInspectorModel.h"
|
||||
#include <AK/Function.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
|
@ -32,7 +34,8 @@ private:
|
|||
void set_path(StringView);
|
||||
void update_title();
|
||||
void set_search_results_visible(bool visible);
|
||||
|
||||
void set_value_inspector_visible(bool visible);
|
||||
void update_inspector_values(size_t position);
|
||||
virtual void drop_event(GUI::DropEvent&) override;
|
||||
|
||||
RefPtr<HexEditor> m_editor;
|
||||
|
@ -54,6 +57,7 @@ private:
|
|||
RefPtr<GUI::Action> m_goto_offset_action;
|
||||
RefPtr<GUI::Action> m_layout_toolbar_action;
|
||||
RefPtr<GUI::Action> m_layout_search_results_action;
|
||||
RefPtr<GUI::Action> m_layout_value_inspector_action;
|
||||
|
||||
RefPtr<GUI::Action> m_copy_hex_action;
|
||||
RefPtr<GUI::Action> m_copy_text_action;
|
||||
|
@ -61,10 +65,17 @@ private:
|
|||
RefPtr<GUI::Action> m_fill_selection_action;
|
||||
|
||||
GUI::ActionGroup m_bytes_per_row_actions;
|
||||
GUI::ActionGroup m_value_inspector_mode_actions;
|
||||
|
||||
RefPtr<GUI::Statusbar> m_statusbar;
|
||||
RefPtr<GUI::Toolbar> m_toolbar;
|
||||
RefPtr<GUI::ToolbarContainer> m_toolbar_container;
|
||||
RefPtr<GUI::TableView> m_search_results;
|
||||
RefPtr<GUI::Widget> m_search_results_container;
|
||||
RefPtr<GUI::Widget> m_side_panel_container;
|
||||
RefPtr<GUI::Widget> m_value_inspector_container;
|
||||
RefPtr<GUI::TableView> m_value_inspector;
|
||||
|
||||
bool m_value_inspector_little_endian { true };
|
||||
bool m_selecting_from_inspector { false };
|
||||
};
|
||||
|
|
|
@ -18,13 +18,29 @@
|
|||
name: "editor"
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
name: "search_results_container"
|
||||
@GUI::VerticalSplitter {
|
||||
name: "side_panel_container"
|
||||
visible: false
|
||||
layout: @GUI::VerticalBoxLayout {}
|
||||
|
||||
@GUI::TableView {
|
||||
name: "search_results"
|
||||
@GUI::Widget {
|
||||
name: "search_results_container"
|
||||
visible: false
|
||||
layout: @GUI::VerticalBoxLayout {}
|
||||
|
||||
@GUI::TableView {
|
||||
name: "search_results"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
name: "value_inspector_container"
|
||||
visible: false
|
||||
layout: @GUI::VerticalBoxLayout {}
|
||||
|
||||
@GUI::TableView {
|
||||
name: "value_inspector"
|
||||
activates_on_selection: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
135
Userland/Applications/HexEditor/ValueInspectorModel.h
Normal file
135
Userland/Applications/HexEditor/ValueInspectorModel.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Hex.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Model.h>
|
||||
|
||||
class ValueInspectorModel final : public GUI::Model {
|
||||
public:
|
||||
enum ValueType {
|
||||
SignedByte,
|
||||
UnsignedByte,
|
||||
SignedShort,
|
||||
UnsignedShort,
|
||||
SignedInt,
|
||||
UnsignedInt,
|
||||
SignedLong,
|
||||
UnsignedLong,
|
||||
Float,
|
||||
Double,
|
||||
__Count
|
||||
};
|
||||
|
||||
enum Column {
|
||||
Type,
|
||||
Value
|
||||
};
|
||||
|
||||
explicit ValueInspectorModel()
|
||||
{
|
||||
for (int i = 0; i < ValueType::__Count; i++)
|
||||
set_parsed_value(static_cast<ValueType>(i), "");
|
||||
}
|
||||
|
||||
void set_parsed_value(ValueType type, String value)
|
||||
{
|
||||
m_values[type] = value;
|
||||
}
|
||||
|
||||
virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override
|
||||
{
|
||||
return m_values.size();
|
||||
}
|
||||
|
||||
virtual int column_count(GUI::ModelIndex const&) const override
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
String column_name(int column) const override
|
||||
{
|
||||
switch (column) {
|
||||
case Column::Type:
|
||||
return "Type";
|
||||
case Column::Value:
|
||||
return "Value";
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
String inspector_value_type_to_string(ValueType type) const
|
||||
{
|
||||
switch (type) {
|
||||
case SignedByte:
|
||||
return "Signed Byte";
|
||||
case UnsignedByte:
|
||||
return "Unsigned Byte";
|
||||
case SignedShort:
|
||||
return "Signed Short";
|
||||
case UnsignedShort:
|
||||
return "Unsigned Short";
|
||||
case SignedInt:
|
||||
return "Signed Int";
|
||||
case UnsignedInt:
|
||||
return "Unsigned Int";
|
||||
case SignedLong:
|
||||
return "Signed Long";
|
||||
case UnsignedLong:
|
||||
return "Unsigned Long";
|
||||
case Float:
|
||||
return "Float";
|
||||
case Double:
|
||||
return "Double";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
virtual GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole role) const override
|
||||
{
|
||||
if (role == GUI::ModelRole::TextAlignment)
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
switch (index.column()) {
|
||||
case Column::Type:
|
||||
return inspector_value_type_to_string(static_cast<ValueType>(index.row()));
|
||||
case Column::Value:
|
||||
return m_values.at(index.row());
|
||||
}
|
||||
}
|
||||
if (role == GUI::ModelRole::Custom) {
|
||||
ValueType selected_type = static_cast<ValueType>(index.row());
|
||||
switch (selected_type) {
|
||||
case SignedByte:
|
||||
case UnsignedByte:
|
||||
return 1;
|
||||
case SignedShort:
|
||||
case UnsignedShort:
|
||||
return 2;
|
||||
case SignedInt:
|
||||
case UnsignedInt:
|
||||
case Float:
|
||||
return 4;
|
||||
case SignedLong:
|
||||
case UnsignedLong:
|
||||
case Double:
|
||||
return 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
Array<String, ValueType::__Count> m_values = {};
|
||||
};
|
Loading…
Add table
Reference in a new issue