diff options
author | Camisul <1397270-camisul@users.noreply.gitlab.com> | 2021-01-22 23:22:00 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-24 19:23:31 +0100 |
commit | 0678dab7dca0bba07e9370140637cbac64625a42 (patch) | |
tree | 6898bc4da689a10dc04d7c23b13139b0925ac4d3 /Userland/Applications/HexEditor | |
parent | 509e39ac00fb512fcd192e73b7d69564268d87a7 (diff) | |
download | serenity-0678dab7dca0bba07e9370140637cbac64625a42.zip |
HexEditor: Find
Added search submenu with options to find or find again.
Find allows to search for ASII string or sequence of Hex value.
Diffstat (limited to 'Userland/Applications/HexEditor')
-rw-r--r-- | Userland/Applications/HexEditor/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/FindDialog.cpp | 166 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/FindDialog.h | 58 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditor.cpp | 25 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditor.h | 2 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditorWidget.cpp | 37 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditorWidget.h | 6 |
7 files changed, 294 insertions, 1 deletions
diff --git a/Userland/Applications/HexEditor/CMakeLists.txt b/Userland/Applications/HexEditor/CMakeLists.txt index ebe134863f..496bb25a38 100644 --- a/Userland/Applications/HexEditor/CMakeLists.txt +++ b/Userland/Applications/HexEditor/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES HexEditor.cpp HexEditorWidget.cpp + FindDialog.cpp main.cpp ) diff --git a/Userland/Applications/HexEditor/FindDialog.cpp b/Userland/Applications/HexEditor/FindDialog.cpp new file mode 100644 index 0000000000..5c52f5738b --- /dev/null +++ b/Userland/Applications/HexEditor/FindDialog.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FindDialog.h" +#include <AK/Hex.h> +#include <AK/String.h> +#include <AK/Vector.h> +#include <LibGUI/BoxLayout.h> +#include <LibGUI/Button.h> +#include <LibGUI/Label.h> +#include <LibGUI/MessageBox.h> +#include <LibGUI/RadioButton.h> +#include <LibGUI/TextBox.h> +#include <LibGUI/Widget.h> +#include <LibGfx/Font.h> +#include <LibGfx/FontDatabase.h> + +struct Option { + String title; + OptionId opt; + bool enabled; + bool default_action; +}; + +static const Vector<Option> options = { + { "ACII String", OPTION_ASCII_STRING, true, true }, + { "Hex value", OPTION_HEX_VALUE, true, false }, +}; + +int FindDialog::show(GUI::Window* parent_window, String& out_text, ByteBuffer& out_buffer) +{ + auto dialog = FindDialog::construct(); + + if (parent_window) + dialog->set_icon(parent_window->icon()); + + if (!out_text.is_empty() && !out_text.is_null()) + dialog->m_text_editor->set_text(out_text); + + auto result = dialog->exec(); + + if (result != GUI::Dialog::ExecOK) + return result; + + auto processed = dialog->process_input(dialog->text_value(), dialog->selected_option()); + + out_text = dialog->text_value(); + + if (processed.is_error()) { + GUI::MessageBox::show_error(parent_window, processed.error()); + result = GUI::Dialog::ExecAborted; + } else { + out_buffer = move(processed.value()); + } + + dbgln("Find: value={} option={}", dialog->text_value().characters(), (int)dialog->selected_option()); + return result; +} + +Result<ByteBuffer, String> FindDialog::process_input(String text_value, OptionId opt) +{ + dbgln("process_input opt={}", (int)opt); + switch (opt) { + case OPTION_ASCII_STRING: { + if (text_value.is_empty()) + return String("Input is empty"); + + return text_value.to_byte_buffer(); + } + + case OPTION_HEX_VALUE: { + text_value.replace(" ", "", true); + auto decoded = decode_hex(text_value.substring_view(0, text_value.length())); + if (!decoded.has_value()) + return String("Input contains invalid hex values."); + + return decoded.value(); + } + + default: + ASSERT_NOT_REACHED(); + } +} + +FindDialog::FindDialog() + : Dialog(nullptr) +{ + resize(280, 180 + ((static_cast<int>(options.size()) - 3) * 16)); + center_on_screen(); + set_resizable(false); + set_title("Find"); + + auto& main = set_main_widget<GUI::Widget>(); + main.set_layout<GUI::VerticalBoxLayout>(); + main.layout()->set_margins({ 8, 8, 8, 8 }); + main.layout()->set_spacing(8); + main.set_fill_with_background_color(true); + + auto& find_prompt_container = main.add<GUI::Widget>(); + find_prompt_container.set_layout<GUI::HorizontalBoxLayout>(); + + find_prompt_container.add<GUI::Label>("Value to find"); + + m_text_editor = find_prompt_container.add<GUI::TextBox>(); + m_text_editor->set_fixed_height(19); + + for (size_t i = 0; i < options.size(); i++) { + auto action = options[i]; + auto& radio = main.add<GUI::RadioButton>(); + radio.set_enabled(action.enabled); + radio.set_text(action.title); + + radio.on_checked = [this, i](auto) { + m_selected_option = options[i].opt; + }; + + if (action.default_action) { + radio.set_checked(true); + m_selected_option = options[i].opt; + } + } + + auto& button_box = main.add<GUI::Widget>(); + button_box.set_layout<GUI::HorizontalBoxLayout>(); + button_box.layout()->set_spacing(8); + + auto& ok_button = button_box.add<GUI::Button>(); + ok_button.on_click = [this](auto) { + m_text_value = m_text_editor->text(); + done(ExecResult::ExecOK); + }; + ok_button.set_text("OK"); + + auto& cancel_button = button_box.add<GUI::Button>(); + cancel_button.on_click = [this](auto) { + done(ExecResult::ExecCancel); + }; + cancel_button.set_text("Cancel"); +} + +FindDialog::~FindDialog() +{ +} diff --git a/Userland/Applications/HexEditor/FindDialog.h b/Userland/Applications/HexEditor/FindDialog.h new file mode 100644 index 0000000000..4fe1377072 --- /dev/null +++ b/Userland/Applications/HexEditor/FindDialog.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/Result.h> +#include <AK/Vector.h> +#include <LibGUI/Dialog.h> + +enum OptionId { + OPTION_INVALID = -1, + OPTION_ASCII_STRING, + OPTION_HEX_VALUE +}; + +class FindDialog : public GUI::Dialog { + C_OBJECT(FindDialog); + +public: + static int show(GUI::Window* parent_window, String& out_tex, ByteBuffer& out_buffer); + +private: + Result<ByteBuffer, String> process_input(String text_value, OptionId opt); + + String text_value() const { return m_text_value; } + OptionId selected_option() const { return m_selected_option; } + + FindDialog(); + virtual ~FindDialog() override; + + RefPtr<GUI::TextEditor> m_text_editor; + + String m_text_value; + OptionId m_selected_option { OPTION_INVALID }; +}; diff --git a/Userland/Applications/HexEditor/HexEditor.cpp b/Userland/Applications/HexEditor/HexEditor.cpp index edd98b8c56..fbb7ae0146 100644 --- a/Userland/Applications/HexEditor/HexEditor.cpp +++ b/Userland/Applications/HexEditor/HexEditor.cpp @@ -582,3 +582,28 @@ void HexEditor::paint_event(GUI::PaintEvent& event) } } } + +int HexEditor::find_and_highlight(ByteBuffer& needle, int start) +{ + if (m_buffer.is_empty()) + return -1; + + if (needle.is_null()) { + dbgln("needle is null"); + return -1; + } + + auto raw_offset = memmem(m_buffer.data() + start, m_buffer.size(), needle.data(), needle.size()); + if (raw_offset == NULL) + return -1; + + 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); + + auto end_of_match = relative_offset + needle; + set_position(relative_offset); + m_selection_start = relative_offset; + m_selection_end = end_of_match; + + return end_of_match; +} diff --git a/Userland/Applications/HexEditor/HexEditor.h b/Userland/Applications/HexEditor/HexEditor.h index 15d7632495..8887c6d8af 100644 --- a/Userland/Applications/HexEditor/HexEditor.h +++ b/Userland/Applications/HexEditor/HexEditor.h @@ -62,7 +62,7 @@ public: void set_bytes_per_row(int); void set_position(int position); - + int find_and_highlight(ByteBuffer& needle, int start = 0); Function<void(int, EditMode, int, int)> on_status_change; // position, edit mode, selection start, selection end Function<void()> on_change; diff --git a/Userland/Applications/HexEditor/HexEditorWidget.cpp b/Userland/Applications/HexEditor/HexEditorWidget.cpp index e6f6711884..cc196e02d1 100644 --- a/Userland/Applications/HexEditor/HexEditorWidget.cpp +++ b/Userland/Applications/HexEditor/HexEditorWidget.cpp @@ -25,6 +25,7 @@ */ #include "HexEditorWidget.h" +#include "FindDialog.h" #include <AK/Optional.h> #include <AK/StringBuilder.h> #include <LibCore/File.h> @@ -189,6 +190,42 @@ HexEditorWidget::HexEditorWidget() })); } + auto& search_menu = menubar->add_menu("Search"); + search_menu.add_action(GUI::Action::create("Find", { Mod_Ctrl, Key_F }, [&](const GUI::Action&) { + auto old_buffer = m_search_buffer.isolated_copy(); + if (FindDialog::show(window(), m_search_text, m_search_buffer) == GUI::InputBox::ExecOK) { + + 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; + } + })); + + search_menu.add_action(GUI::Action::create("Find", { Mod_None, Key_F3 }, [&](const GUI::Action&) { + if (m_search_text.is_empty() || m_search_buffer.is_empty() || m_search_buffer.is_null()) { + GUI::MessageBox::show(window(), "Nothing to search for", "Not found", GUI::MessageBox::Type::Warning); + return; + } + + auto result = m_editor->find_and_highlight(m_search_buffer, last_found_index()); + if (!result) { + GUI::MessageBox::show(window(), String::formatted("No more matches for \"{}\" found in this file", m_search_text), "Not found", GUI::MessageBox::Type::Warning); + return; + } + m_editor->update(); + m_last_found_index = result; + })); + auto& help_menu = menubar->add_menu("Help"); help_menu.add_action(GUI::CommonActions::make_about_action("Hex Editor", GUI::Icon::default_icon("Hex Editor"), window())); diff --git a/Userland/Applications/HexEditor/HexEditorWidget.h b/Userland/Applications/HexEditor/HexEditorWidget.h index 55152518ab..76183bd5e8 100644 --- a/Userland/Applications/HexEditor/HexEditorWidget.h +++ b/Userland/Applications/HexEditor/HexEditorWidget.h @@ -52,6 +52,12 @@ private: String m_path; String m_name; String m_extension; + + String m_search_text; + ByteBuffer m_search_buffer; + int last_found_index() const { return m_last_found_index == -1 ? 0 : m_last_found_index; } + int m_last_found_index { -1 }; + RefPtr<GUI::Action> m_new_action; RefPtr<GUI::Action> m_open_action; RefPtr<GUI::Action> m_save_action; |