mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 13:27:35 +00:00
HexEditor: Add 'Find All' option to Find Dialog to find all matches
This commit is contained in:
parent
d7797c8bf8
commit
6aa766f8ca
9 changed files with 238 additions and 30 deletions
|
@ -29,7 +29,7 @@ static const Vector<Option> options = {
|
||||||
{ "Hex value", OPTION_HEX_VALUE, true, false },
|
{ "Hex value", OPTION_HEX_VALUE, true, false },
|
||||||
};
|
};
|
||||||
|
|
||||||
int FindDialog::show(GUI::Window* parent_window, String& out_text, ByteBuffer& out_buffer)
|
int FindDialog::show(GUI::Window* parent_window, String& out_text, ByteBuffer& out_buffer, bool& find_all)
|
||||||
{
|
{
|
||||||
auto dialog = FindDialog::construct();
|
auto dialog = FindDialog::construct();
|
||||||
|
|
||||||
|
@ -39,12 +39,16 @@ int FindDialog::show(GUI::Window* parent_window, String& out_text, ByteBuffer& o
|
||||||
if (!out_text.is_empty() && !out_text.is_null())
|
if (!out_text.is_empty() && !out_text.is_null())
|
||||||
dialog->m_text_editor->set_text(out_text);
|
dialog->m_text_editor->set_text(out_text);
|
||||||
|
|
||||||
|
dialog->m_find_button->set_enabled(!dialog->m_text_editor->text().is_empty());
|
||||||
|
dialog->m_find_all_button->set_enabled(!dialog->m_text_editor->text().is_empty());
|
||||||
|
|
||||||
auto result = dialog->exec();
|
auto result = dialog->exec();
|
||||||
|
|
||||||
if (result != GUI::Dialog::ExecOK)
|
if (result != GUI::Dialog::ExecOK)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
auto processed = dialog->process_input(dialog->text_value(), dialog->selected_option());
|
auto selected_option = dialog->selected_option();
|
||||||
|
auto processed = dialog->process_input(dialog->text_value(), selected_option);
|
||||||
|
|
||||||
out_text = dialog->text_value();
|
out_text = dialog->text_value();
|
||||||
|
|
||||||
|
@ -55,7 +59,9 @@ int FindDialog::show(GUI::Window* parent_window, String& out_text, ByteBuffer& o
|
||||||
out_buffer = move(processed.value());
|
out_buffer = move(processed.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln("Find: value={} option={}", dialog->text_value().characters(), (int)dialog->selected_option());
|
find_all = dialog->find_all();
|
||||||
|
|
||||||
|
dbgln("Find: value={} option={} find_all={}", out_text.characters(), (int)selected_option, find_all);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +103,8 @@ FindDialog::FindDialog()
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
|
||||||
m_text_editor = *main_widget.find_descendant_of_type_named<GUI::TextBox>("text_editor");
|
m_text_editor = *main_widget.find_descendant_of_type_named<GUI::TextBox>("text_editor");
|
||||||
m_ok_button = *main_widget.find_descendant_of_type_named<GUI::Button>("ok_button");
|
m_find_button = *main_widget.find_descendant_of_type_named<GUI::Button>("find_button");
|
||||||
|
m_find_all_button = *main_widget.find_descendant_of_type_named<GUI::Button>("find_all_button");
|
||||||
m_cancel_button = *main_widget.find_descendant_of_type_named<GUI::Button>("cancel_button");
|
m_cancel_button = *main_widget.find_descendant_of_type_named<GUI::Button>("cancel_button");
|
||||||
|
|
||||||
auto& radio_container = *main_widget.find_descendant_of_type_named<GUI::Widget>("radio_container");
|
auto& radio_container = *main_widget.find_descendant_of_type_named<GUI::Widget>("radio_container");
|
||||||
|
@ -117,13 +124,26 @@ FindDialog::FindDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_text_editor->on_return_pressed = [this] {
|
m_text_editor->on_change = [this]() {
|
||||||
m_ok_button->click();
|
m_find_button->set_enabled(!m_text_editor->text().is_empty());
|
||||||
|
m_find_all_button->set_enabled(!m_text_editor->text().is_empty());
|
||||||
};
|
};
|
||||||
|
|
||||||
m_ok_button->on_click = [this](auto) {
|
m_text_editor->on_return_pressed = [this] {
|
||||||
m_text_value = m_text_editor->text();
|
m_find_button->click();
|
||||||
done(ExecResult::ExecOK);
|
};
|
||||||
|
|
||||||
|
m_find_button->on_click = [this](auto) {
|
||||||
|
auto text = m_text_editor->text();
|
||||||
|
if (!text.is_empty()) {
|
||||||
|
m_text_value = text;
|
||||||
|
done(ExecResult::ExecOK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
m_find_all_button->on_click = [this](auto) {
|
||||||
|
m_find_all = true;
|
||||||
|
m_find_button->click();
|
||||||
};
|
};
|
||||||
|
|
||||||
m_cancel_button->on_click = [this](auto) {
|
m_cancel_button->on_click = [this](auto) {
|
||||||
|
|
|
@ -34,8 +34,13 @@
|
||||||
layout: @GUI::HorizontalBoxLayout
|
layout: @GUI::HorizontalBoxLayout
|
||||||
|
|
||||||
@GUI::Button {
|
@GUI::Button {
|
||||||
name: "ok_button"
|
name: "find_button"
|
||||||
text: "OK"
|
text: "Find"
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::Button {
|
||||||
|
name: "find_all_button"
|
||||||
|
text: "Find All"
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Button {
|
@GUI::Button {
|
||||||
|
|
|
@ -20,21 +20,24 @@ class FindDialog : public GUI::Dialog {
|
||||||
C_OBJECT(FindDialog);
|
C_OBJECT(FindDialog);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static int show(GUI::Window* parent_window, String& out_tex, ByteBuffer& out_buffer);
|
static int show(GUI::Window* parent_window, String& out_tex, ByteBuffer& out_buffer, bool& find_all);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Result<ByteBuffer, String> process_input(String text_value, OptionId opt);
|
Result<ByteBuffer, String> process_input(String text_value, OptionId opt);
|
||||||
|
|
||||||
String text_value() const { return m_text_value; }
|
String text_value() const { return m_text_value; }
|
||||||
OptionId selected_option() const { return m_selected_option; }
|
OptionId selected_option() const { return m_selected_option; }
|
||||||
|
bool find_all() const { return m_find_all; }
|
||||||
|
|
||||||
FindDialog();
|
FindDialog();
|
||||||
virtual ~FindDialog() override;
|
virtual ~FindDialog() override;
|
||||||
|
|
||||||
RefPtr<GUI::TextEditor> m_text_editor;
|
RefPtr<GUI::TextEditor> m_text_editor;
|
||||||
RefPtr<GUI::Button> m_ok_button;
|
RefPtr<GUI::Button> m_find_button;
|
||||||
|
RefPtr<GUI::Button> m_find_all_button;
|
||||||
RefPtr<GUI::Button> m_cancel_button;
|
RefPtr<GUI::Button> m_cancel_button;
|
||||||
|
|
||||||
|
bool m_find_all { false };
|
||||||
String m_text_value;
|
String m_text_value;
|
||||||
OptionId m_selected_option { OPTION_INVALID };
|
OptionId m_selected_option { OPTION_INVALID };
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "HexEditor.h"
|
#include "HexEditor.h"
|
||||||
|
#include "SearchResultsModel.h"
|
||||||
#include <AK/Debug.h>
|
#include <AK/Debug.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <LibGUI/Action.h>
|
#include <LibGUI/Action.h>
|
||||||
|
@ -572,6 +573,13 @@ void HexEditor::highlight(int start, int end)
|
||||||
}
|
}
|
||||||
|
|
||||||
int HexEditor::find_and_highlight(ByteBuffer& needle, int start)
|
int HexEditor::find_and_highlight(ByteBuffer& needle, int start)
|
||||||
|
{
|
||||||
|
auto end_of_match = find(needle, start);
|
||||||
|
highlight(end_of_match - needle.size(), end_of_match);
|
||||||
|
return end_of_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HexEditor::find(ByteBuffer& needle, int start)
|
||||||
{
|
{
|
||||||
if (m_buffer.is_empty())
|
if (m_buffer.is_empty())
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -581,10 +589,37 @@ int HexEditor::find_and_highlight(ByteBuffer& needle, int start)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int relative_offset = static_cast<const u8*>(raw_offset) - m_buffer.data();
|
int relative_offset = static_cast<const u8*>(raw_offset) - m_buffer.data();
|
||||||
dbgln("find_and_highlight: start={} raw_offset={} relative_offset={}", start, raw_offset, relative_offset);
|
dbgln("find: start={} raw_offset={} relative_offset={}", start, raw_offset, relative_offset);
|
||||||
|
|
||||||
auto end_of_match = relative_offset + needle.size();
|
auto end_of_match = relative_offset + needle.size();
|
||||||
highlight(relative_offset, end_of_match);
|
|
||||||
|
|
||||||
return end_of_match;
|
return end_of_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<Match> HexEditor::find_all(ByteBuffer& needle, int start)
|
||||||
|
{
|
||||||
|
if (m_buffer.is_empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
Vector<Match> matches;
|
||||||
|
|
||||||
|
size_t i = start;
|
||||||
|
while (i < m_buffer.size()) {
|
||||||
|
auto raw_offset = memmem(m_buffer.data() + i, m_buffer.size() - i, needle.data(), needle.size());
|
||||||
|
if (raw_offset == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int relative_offset = static_cast<const u8*>(raw_offset) - m_buffer.data();
|
||||||
|
dbgln("find_all: needle={} start={} raw_offset={} relative_offset={}", needle.data(), i, raw_offset, relative_offset);
|
||||||
|
matches.append({ relative_offset, String::formatted("{}", StringView { needle }.to_string().characters()) });
|
||||||
|
i = relative_offset + needle.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.is_empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto first_match = matches.at(0);
|
||||||
|
highlight(first_match.offset, first_match.offset + first_match.value.length());
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "SearchResultsModel.h"
|
||||||
#include <AK/ByteBuffer.h>
|
#include <AK/ByteBuffer.h>
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
|
@ -46,7 +47,9 @@ public:
|
||||||
|
|
||||||
void set_position(int position);
|
void set_position(int position);
|
||||||
void highlight(int start, int end);
|
void highlight(int start, int end);
|
||||||
|
int find(ByteBuffer& needle, int start = 0);
|
||||||
int find_and_highlight(ByteBuffer& needle, int start = 0);
|
int find_and_highlight(ByteBuffer& needle, int start = 0);
|
||||||
|
Vector<Match> find_all(ByteBuffer& needle, int start = 0);
|
||||||
Function<void(int, EditMode, int, int)> on_status_change; // position, edit mode, selection start, selection end
|
Function<void(int, EditMode, int, int)> on_status_change; // position, edit mode, selection start, selection end
|
||||||
Function<void()> on_change;
|
Function<void()> on_change;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "HexEditorWidget.h"
|
#include "HexEditorWidget.h"
|
||||||
#include "FindDialog.h"
|
#include "FindDialog.h"
|
||||||
#include "GoToOffsetDialog.h"
|
#include "GoToOffsetDialog.h"
|
||||||
|
#include "SearchResultsModel.h"
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <Applications/HexEditor/HexEditorWindowGML.h>
|
#include <Applications/HexEditor/HexEditorWindowGML.h>
|
||||||
|
@ -20,7 +21,9 @@
|
||||||
#include <LibGUI/Menu.h>
|
#include <LibGUI/Menu.h>
|
||||||
#include <LibGUI/Menubar.h>
|
#include <LibGUI/Menubar.h>
|
||||||
#include <LibGUI/MessageBox.h>
|
#include <LibGUI/MessageBox.h>
|
||||||
|
#include <LibGUI/Model.h>
|
||||||
#include <LibGUI/Statusbar.h>
|
#include <LibGUI/Statusbar.h>
|
||||||
|
#include <LibGUI/TableView.h>
|
||||||
#include <LibGUI/TextBox.h>
|
#include <LibGUI/TextBox.h>
|
||||||
#include <LibGUI/TextEditor.h>
|
#include <LibGUI/TextEditor.h>
|
||||||
#include <LibGUI/Toolbar.h>
|
#include <LibGUI/Toolbar.h>
|
||||||
|
@ -40,6 +43,8 @@ HexEditorWidget::HexEditorWidget()
|
||||||
m_toolbar_container = *find_descendant_of_type_named<GUI::ToolbarContainer>("toolbar_container");
|
m_toolbar_container = *find_descendant_of_type_named<GUI::ToolbarContainer>("toolbar_container");
|
||||||
m_editor = *find_descendant_of_type_named<HexEditor>("editor");
|
m_editor = *find_descendant_of_type_named<HexEditor>("editor");
|
||||||
m_statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
|
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_editor->on_status_change = [this](int position, HexEditor::EditMode edit_mode, int selection_start, int selection_end) {
|
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));
|
m_statusbar->set_text(0, String::formatted("Offset: {:#08X}", position));
|
||||||
|
@ -56,6 +61,16 @@ HexEditorWidget::HexEditorWidget()
|
||||||
update_title();
|
update_title();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
m_search_results->set_activates_on_selection(true);
|
||||||
|
m_search_results->on_activation = [this](const GUI::ModelIndex& index) {
|
||||||
|
if (!index.is_valid())
|
||||||
|
return;
|
||||||
|
auto offset = index.data(GUI::ModelRole::Custom).to_i32();
|
||||||
|
m_last_found_index = offset;
|
||||||
|
m_editor->set_position(offset);
|
||||||
|
m_editor->update();
|
||||||
|
};
|
||||||
|
|
||||||
m_new_action = GUI::Action::create("New", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [this](const GUI::Action&) {
|
m_new_action = GUI::Action::create("New", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [this](const GUI::Action&) {
|
||||||
if (m_document_dirty) {
|
if (m_document_dirty) {
|
||||||
if (GUI::MessageBox::show(window(), "Save changes to current file first?", "Warning", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel) != GUI::Dialog::ExecResult::ExecOK)
|
if (GUI::MessageBox::show(window(), "Save changes to current file first?", "Warning", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel) != GUI::Dialog::ExecResult::ExecOK)
|
||||||
|
@ -117,22 +132,38 @@ HexEditorWidget::HexEditorWidget()
|
||||||
|
|
||||||
m_find_action = GUI::Action::create("&Find", { Mod_Ctrl, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [&](const GUI::Action&) {
|
m_find_action = GUI::Action::create("&Find", { Mod_Ctrl, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [&](const GUI::Action&) {
|
||||||
auto old_buffer = m_search_buffer;
|
auto old_buffer = m_search_buffer;
|
||||||
if (FindDialog::show(window(), m_search_text, m_search_buffer) == GUI::InputBox::ExecOK) {
|
bool find_all = false;
|
||||||
|
if (FindDialog::show(window(), m_search_text, m_search_buffer, find_all) == GUI::InputBox::ExecOK) {
|
||||||
|
if (find_all) {
|
||||||
|
auto matches = m_editor->find_all(m_search_buffer, 0);
|
||||||
|
m_search_results->set_model(*new SearchResultsModel(move(matches)));
|
||||||
|
m_search_results->update();
|
||||||
|
|
||||||
bool same_buffers = false;
|
if (matches.is_empty()) {
|
||||||
if (old_buffer.size() == m_search_buffer.size()) {
|
GUI::MessageBox::show(window(), String::formatted("Pattern \"{}\" not found in this file", m_search_text), "Not found", GUI::MessageBox::Type::Warning);
|
||||||
if (memcmp(old_buffer.data(), m_search_buffer.data(), old_buffer.size()) == 0)
|
return;
|
||||||
same_buffers = true;
|
}
|
||||||
|
|
||||||
|
GUI::MessageBox::show(window(), String::formatted("Found {} matches for \"{}\" in this file", matches.size(), m_search_text), String::formatted("{} matches", matches.size()), GUI::MessageBox::Type::Warning);
|
||||||
|
set_search_results_visible(true);
|
||||||
|
} else {
|
||||||
|
bool same_buffers = false;
|
||||||
|
if (old_buffer.size() == m_search_buffer.size()) {
|
||||||
|
if (memcmp(old_buffer.data(), m_search_buffer.data(), old_buffer.size()) == 0)
|
||||||
|
same_buffers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = m_editor->find_and_highlight(m_search_buffer, same_buffers ? last_found_index() : 0);
|
||||||
|
|
||||||
|
if (result == -1) {
|
||||||
|
GUI::MessageBox::show(window(), String::formatted("Pattern \"{}\" not found in this file", m_search_text), "Not found", GUI::MessageBox::Type::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_last_found_index = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = m_editor->find_and_highlight(m_search_buffer, same_buffers ? last_found_index() : 0);
|
|
||||||
|
|
||||||
if (result == -1) {
|
|
||||||
GUI::MessageBox::show(window(), String::formatted("Pattern \"{}\" not found in this file", m_search_text), "Not found", GUI::MessageBox::Type::Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_editor->update();
|
m_editor->update();
|
||||||
m_last_found_index = result;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,6 +187,10 @@ HexEditorWidget::HexEditorWidget()
|
||||||
m_config->sync();
|
m_config->sync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_layout_search_results_action = GUI::Action::create_checkable("&Search Results", [&](auto& action) {
|
||||||
|
set_search_results_visible(action.is_checked());
|
||||||
|
});
|
||||||
|
|
||||||
m_toolbar->add_action(*m_new_action);
|
m_toolbar->add_action(*m_new_action);
|
||||||
m_toolbar->add_action(*m_open_action);
|
m_toolbar->add_action(*m_open_action);
|
||||||
m_toolbar->add_action(*m_save_action);
|
m_toolbar->add_action(*m_save_action);
|
||||||
|
@ -230,8 +265,9 @@ void HexEditorWidget::initialize_menubar(GUI::Menubar& menubar)
|
||||||
auto show_toolbar = m_config->read_bool_entry("Layout", "ShowToolbar", true);
|
auto show_toolbar = m_config->read_bool_entry("Layout", "ShowToolbar", true);
|
||||||
m_layout_toolbar_action->set_checked(show_toolbar);
|
m_layout_toolbar_action->set_checked(show_toolbar);
|
||||||
m_toolbar_container->set_visible(show_toolbar);
|
m_toolbar_container->set_visible(show_toolbar);
|
||||||
|
|
||||||
view_menu.add_action(*m_layout_toolbar_action);
|
view_menu.add_action(*m_layout_toolbar_action);
|
||||||
|
view_menu.add_action(*m_layout_search_results_action);
|
||||||
|
view_menu.add_separator();
|
||||||
|
|
||||||
auto bytes_per_row = m_config->read_num_entry("Layout", "BytesPerRow", 16);
|
auto bytes_per_row = m_config->read_num_entry("Layout", "BytesPerRow", 16);
|
||||||
m_editor->set_bytes_per_row(bytes_per_row);
|
m_editor->set_bytes_per_row(bytes_per_row);
|
||||||
|
@ -294,3 +330,9 @@ bool HexEditorWidget::request_close()
|
||||||
auto result = GUI::MessageBox::show(window(), "The file has been modified. Quit without saving?", "Quit without saving?", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel);
|
auto result = GUI::MessageBox::show(window(), "The file has been modified. Quit without saving?", "Quit without saving?", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel);
|
||||||
return result == GUI::MessageBox::ExecOK;
|
return result == GUI::MessageBox::ExecOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HexEditorWidget::set_search_results_visible(bool visible)
|
||||||
|
{
|
||||||
|
m_layout_search_results_action->set_checked(visible);
|
||||||
|
m_search_results_container->set_visible(visible);
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ private:
|
||||||
HexEditorWidget();
|
HexEditorWidget();
|
||||||
void set_path(const LexicalPath& file);
|
void set_path(const LexicalPath& file);
|
||||||
void update_title();
|
void update_title();
|
||||||
|
void set_search_results_visible(bool visible);
|
||||||
|
|
||||||
RefPtr<Core::ConfigFile> m_config;
|
RefPtr<Core::ConfigFile> m_config;
|
||||||
|
|
||||||
|
@ -50,12 +51,15 @@ private:
|
||||||
RefPtr<GUI::Action> m_find_action;
|
RefPtr<GUI::Action> m_find_action;
|
||||||
RefPtr<GUI::Action> m_goto_offset_action;
|
RefPtr<GUI::Action> m_goto_offset_action;
|
||||||
RefPtr<GUI::Action> m_layout_toolbar_action;
|
RefPtr<GUI::Action> m_layout_toolbar_action;
|
||||||
|
RefPtr<GUI::Action> m_layout_search_results_action;
|
||||||
|
|
||||||
GUI::ActionGroup m_bytes_per_row_actions;
|
GUI::ActionGroup m_bytes_per_row_actions;
|
||||||
|
|
||||||
RefPtr<GUI::Statusbar> m_statusbar;
|
RefPtr<GUI::Statusbar> m_statusbar;
|
||||||
RefPtr<GUI::Toolbar> m_toolbar;
|
RefPtr<GUI::Toolbar> m_toolbar;
|
||||||
RefPtr<GUI::ToolbarContainer> m_toolbar_container;
|
RefPtr<GUI::ToolbarContainer> m_toolbar_container;
|
||||||
|
RefPtr<GUI::TableView> m_search_results;
|
||||||
|
RefPtr<GUI::Widget> m_search_results_container;
|
||||||
|
|
||||||
bool m_document_dirty { false };
|
bool m_document_dirty { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,8 +14,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HexEditor::HexEditor {
|
@GUI::HorizontalSplitter {
|
||||||
name: "editor"
|
@HexEditor::HexEditor {
|
||||||
|
name: "editor"
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::Widget {
|
||||||
|
name: "search_results_container"
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
layout: @GUI::VerticalBoxLayout {
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::TableView {
|
||||||
|
name: "search_results"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Statusbar {
|
@GUI::Statusbar {
|
||||||
|
|
82
Userland/Applications/HexEditor/SearchResultsModel.h
Normal file
82
Userland/Applications/HexEditor/SearchResultsModel.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, the SerenityOS developers.
|
||||||
|
*
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
struct Match {
|
||||||
|
int offset;
|
||||||
|
String value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SearchResultsModel final : public GUI::Model {
|
||||||
|
public:
|
||||||
|
enum Column {
|
||||||
|
Offset,
|
||||||
|
Value
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit SearchResultsModel(const Vector<Match>&& matches)
|
||||||
|
: m_matches(move(matches))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override
|
||||||
|
{
|
||||||
|
return m_matches.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int column_count(const GUI::ModelIndex&) const override
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
String column_name(int column) const override
|
||||||
|
{
|
||||||
|
switch (column) {
|
||||||
|
case Column::Offset:
|
||||||
|
return "Offset";
|
||||||
|
case Column::Value:
|
||||||
|
return "Value";
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override
|
||||||
|
{
|
||||||
|
if (role == GUI::ModelRole::TextAlignment)
|
||||||
|
return Gfx::TextAlignment::CenterLeft;
|
||||||
|
if (role == GUI::ModelRole::Custom) {
|
||||||
|
auto& match = m_matches.at(index.row());
|
||||||
|
return match.offset;
|
||||||
|
}
|
||||||
|
if (role == GUI::ModelRole::Display) {
|
||||||
|
auto& match = m_matches.at(index.row());
|
||||||
|
switch (index.column()) {
|
||||||
|
case Column::Offset:
|
||||||
|
return String::formatted("{:#08X}", match.offset);
|
||||||
|
case Column::Value: {
|
||||||
|
Utf8View utf8_view(match.value);
|
||||||
|
if (!utf8_view.validate())
|
||||||
|
return {};
|
||||||
|
return StringView(match.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void update() override { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector<Match> m_matches;
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue