Serenity Operating System
at hosted 252 lines 11 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "HexEditorWidget.h" 28#include <AK/Optional.h> 29#include <AK/StringBuilder.h> 30#include <LibCore/File.h> 31#include <LibGUI/AboutDialog.h> 32#include <LibGUI/Action.h> 33#include <LibGUI/BoxLayout.h> 34#include <LibGUI/Button.h> 35#include <LibGUI/FilePicker.h> 36#include <LibGUI/FontDatabase.h> 37#include <LibGUI/InputBox.h> 38#include <LibGUI/Menu.h> 39#include <LibGUI/MenuBar.h> 40#include <LibGUI/MessageBox.h> 41#include <LibGUI/StatusBar.h> 42#include <LibGUI/TextBox.h> 43#include <LibGUI/TextEditor.h> 44#include <LibGUI/ToolBar.h> 45#include <stdio.h> 46#include <string.h> 47 48HexEditorWidget::HexEditorWidget() 49{ 50 set_layout<GUI::VerticalBoxLayout>(); 51 layout()->set_spacing(0); 52 53 m_editor = add<HexEditor>(); 54 55 m_editor->on_status_change = [this](int position, HexEditor::EditMode edit_mode, int selection_start, int selection_end) { 56 m_statusbar->set_text(0, String::format("Offset: %8X", position)); 57 m_statusbar->set_text(1, String::format("Edit Mode: %s", edit_mode == HexEditor::EditMode::Hex ? "Hex" : "Text")); 58 m_statusbar->set_text(2, String::format("Selection Start: %d", selection_start)); 59 m_statusbar->set_text(3, String::format("Selection End: %d", selection_end)); 60 m_statusbar->set_text(4, String::format("Selected Bytes: %d", (selection_end - selection_start) + 1)); 61 }; 62 63 m_editor->on_change = [this] { 64 bool was_dirty = m_document_dirty; 65 m_document_dirty = true; 66 if (!was_dirty) 67 update_title(); 68 }; 69 70 m_statusbar = add<GUI::StatusBar>(5); 71 72 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&) { 73 if (m_document_dirty) { 74 auto save_document_first_box = GUI::MessageBox::construct("Save Document First?", "Warning", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel, window()); 75 auto save_document_first_result = save_document_first_box->exec(); 76 77 if (save_document_first_result != GUI::Dialog::ExecResult::ExecOK) 78 return; 79 m_save_action->activate(); 80 } 81 82 auto input_box = GUI::InputBox::construct("Enter new file size:", "New file size", window()); 83 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 84 auto valid = false; 85 auto file_size = input_box->text_value().to_int(valid); 86 if (valid && file_size > 0) { 87 m_document_dirty = false; 88 m_editor->set_buffer(ByteBuffer::create_zeroed(file_size)); 89 set_path(FileSystemPath()); 90 update_title(); 91 } else { 92 GUI::MessageBox::show("Invalid file size entered.", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 93 } 94 } 95 }); 96 97 m_open_action = GUI::CommonActions::make_open_action([this](auto&) { 98 Optional<String> open_path = GUI::FilePicker::get_open_filepath(); 99 100 if (!open_path.has_value()) 101 return; 102 103 open_file(open_path.value()); 104 }); 105 106 m_save_action = GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [&](const GUI::Action&) { 107 if (!m_path.is_empty()) { 108 if (!m_editor->write_to_file(m_path)) { 109 GUI::MessageBox::show("Unable to save file.\n", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 110 } else { 111 m_document_dirty = false; 112 update_title(); 113 } 114 return; 115 } 116 117 m_save_as_action->activate(); 118 }); 119 120 m_save_as_action = GUI::Action::create("Save as...", { Mod_Ctrl | Mod_Shift, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [this](const GUI::Action&) { 121 Optional<String> save_path = GUI::FilePicker::get_save_filepath(m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "bin" : m_extension); 122 if (!save_path.has_value()) 123 return; 124 125 if (!m_editor->write_to_file(save_path.value())) { 126 GUI::MessageBox::show("Unable to save file.\n", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 127 return; 128 } 129 130 m_document_dirty = false; 131 set_path(FileSystemPath(save_path.value())); 132 dbg() << "Wrote document to " << save_path.value(); 133 }); 134 135 auto menubar = make<GUI::MenuBar>(); 136 auto& app_menu = menubar->add_menu("Hex Editor"); 137 app_menu.add_action(*m_new_action); 138 app_menu.add_action(*m_open_action); 139 app_menu.add_action(*m_save_action); 140 app_menu.add_action(*m_save_as_action); 141 app_menu.add_separator(); 142 app_menu.add_action(GUI::CommonActions::make_quit_action([this](auto&) { 143 if (!request_close()) 144 return; 145 GUI::Application::the().quit(0); 146 })); 147 148 auto bytes_per_row_menu = GUI::Menu::construct("Bytes Per Row"); 149 for (int i = 8; i <= 32; i += 8) { 150 bytes_per_row_menu->add_action(GUI::Action::create(String::number(i), [this, i](auto&) { 151 m_editor->set_bytes_per_row(i); 152 m_editor->update(); 153 })); 154 } 155 156 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&) { 157 auto input_box = GUI::InputBox::construct("Enter Decimal offset:", "Go To", window()); 158 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 159 auto valid = false; 160 auto new_offset = input_box->text_value().to_int(valid); 161 if (valid) { 162 m_editor->set_position(new_offset); 163 } 164 } 165 }); 166 167 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&) { 168 auto input_box = GUI::InputBox::construct("Enter Hex offset:", "Go To", window()); 169 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 170 auto new_offset = strtol(input_box->text_value().characters(), nullptr, 16); 171 m_editor->set_position(new_offset); 172 } 173 }); 174 175 auto& edit_menu = menubar->add_menu("Edit"); 176 edit_menu.add_action(GUI::Action::create("Fill selection...", { Mod_Ctrl, Key_B }, [&](const GUI::Action&) { 177 auto input_box = GUI::InputBox::construct("Fill byte (hex):", "Fill Selection", window()); 178 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 179 auto fill_byte = strtol(input_box->text_value().characters(), nullptr, 16); 180 m_editor->fill_selection(fill_byte); 181 } 182 })); 183 edit_menu.add_separator(); 184 edit_menu.add_action(*m_goto_decimal_offset_action); 185 edit_menu.add_action(*m_goto_hex_offset_action); 186 edit_menu.add_separator(); 187 edit_menu.add_action(GUI::Action::create("Copy Hex", { Mod_Ctrl, Key_C }, [&](const GUI::Action&) { 188 m_editor->copy_selected_hex_to_clipboard(); 189 })); 190 edit_menu.add_action(GUI::Action::create("Copy Text", { Mod_Ctrl | Mod_Shift, Key_C }, [&](const GUI::Action&) { 191 m_editor->copy_selected_text_to_clipboard(); 192 })); 193 edit_menu.add_separator(); 194 edit_menu.add_action(GUI::Action::create("Copy As C Code", { Mod_Alt | Mod_Shift, Key_C }, [&](const GUI::Action&) { 195 m_editor->copy_selected_hex_to_clipboard_as_c_code(); 196 })); 197 198 auto& view_menu = menubar->add_menu("View"); 199 view_menu.add_submenu(move(bytes_per_row_menu)); 200 201 auto& help_menu = menubar->add_menu("Help"); 202 help_menu.add_action(GUI::Action::create("About", [&](auto&) { 203 GUI::AboutDialog::show("Hex Editor", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hexeditor.png"), window()); 204 })); 205 206 GUI::Application::the().set_menubar(move(menubar)); 207 208 m_editor->set_focus(true); 209} 210 211HexEditorWidget::~HexEditorWidget() 212{ 213} 214 215void HexEditorWidget::set_path(const FileSystemPath& file) 216{ 217 m_path = file.string(); 218 m_name = file.title(); 219 m_extension = file.extension(); 220 update_title(); 221} 222 223void HexEditorWidget::update_title() 224{ 225 StringBuilder builder; 226 builder.append(m_path); 227 if (m_document_dirty) 228 builder.append(" (*)"); 229 builder.append(" - Hex Editor"); 230 window()->set_title(builder.to_string()); 231} 232 233void HexEditorWidget::open_file(const String& path) 234{ 235 auto file = Core::File::construct(path); 236 if (!file->open(Core::IODevice::ReadOnly)) { 237 GUI::MessageBox::show(String::format("Opening \"%s\" failed: %s", path.characters(), strerror(errno)), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 238 return; 239 } 240 241 m_document_dirty = false; 242 m_editor->set_buffer(file->read_all()); // FIXME: On really huge files, this is never going to work. Should really create a framework to fetch data from the file on-demand. 243 set_path(FileSystemPath(path)); 244} 245 246bool HexEditorWidget::request_close() 247{ 248 if (!m_document_dirty) 249 return true; 250 auto result = GUI::MessageBox::show("The file has been modified. Quit without saving?", "Quit without saving?", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel, window()); 251 return result == GUI::MessageBox::ExecOK; 252}