Serenity Operating System
at portability 255 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 47HexEditorWidget::HexEditorWidget() 48{ 49 set_layout(make<GUI::VerticalBoxLayout>()); 50 layout()->set_spacing(0); 51 52 m_editor = add<HexEditor>(); 53 54 m_editor->on_status_change = [this](int position, HexEditor::EditMode edit_mode, int selection_start, int selection_end) { 55 m_statusbar->set_text(0, String::format("Offset: %8X", position)); 56 m_statusbar->set_text(1, String::format("Edit Mode: %s", edit_mode == HexEditor::EditMode::Hex ? "Hex" : "Text")); 57 m_statusbar->set_text(2, String::format("Selection Start: %d", selection_start)); 58 m_statusbar->set_text(3, String::format("Selection End: %d", selection_end)); 59 m_statusbar->set_text(4, String::format("Selected Bytes: %d", (selection_end - selection_start) + 1)); 60 }; 61 62 m_editor->on_change = [this] { 63 bool was_dirty = m_document_dirty; 64 m_document_dirty = true; 65 if (!was_dirty) 66 update_title(); 67 }; 68 69 m_statusbar = add<GUI::StatusBar>(5); 70 71 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&) { 72 if (m_document_dirty) { 73 auto save_document_first_box = GUI::MessageBox::construct("Save Document First?", "Warning", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel, window()); 74 auto save_document_first_result = save_document_first_box->exec(); 75 76 if (save_document_first_result != GUI::Dialog::ExecResult::ExecOK) 77 return; 78 m_save_action->activate(); 79 } 80 81 auto input_box = GUI::InputBox::construct("Enter new file size:", "New file size", this); 82 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 83 auto valid = false; 84 auto file_size = input_box->text_value().to_int(valid); 85 if (valid && file_size > 0) { 86 m_document_dirty = false; 87 m_editor->set_buffer(ByteBuffer::create_zeroed(file_size)); 88 set_path(FileSystemPath()); 89 update_title(); 90 } else { 91 GUI::MessageBox::show("Invalid file size entered.", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 92 } 93 } 94 }); 95 96 m_open_action = GUI::CommonActions::make_open_action([this](auto&) { 97 Optional<String> open_path = GUI::FilePicker::get_open_filepath(); 98 99 if (!open_path.has_value()) 100 return; 101 102 open_file(open_path.value()); 103 }); 104 105 m_save_action = GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [&](const GUI::Action&) { 106 if (!m_path.is_empty()) { 107 if (!m_editor->write_to_file(m_path)) { 108 GUI::MessageBox::show("Unable to save file.\n", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 109 } else { 110 m_document_dirty = false; 111 update_title(); 112 } 113 return; 114 } 115 116 m_save_as_action->activate(); 117 }); 118 119 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&) { 120 Optional<String> save_path = GUI::FilePicker::get_save_filepath(m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "bin" : m_extension); 121 if (!save_path.has_value()) 122 return; 123 124 if (!m_editor->write_to_file(save_path.value())) { 125 GUI::MessageBox::show("Unable to save file.\n", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 126 return; 127 } 128 129 m_document_dirty = false; 130 set_path(FileSystemPath(save_path.value())); 131 dbg() << "Wrote document to " << save_path.value(); 132 }); 133 134 auto menubar = make<GUI::MenuBar>(); 135 auto app_menu = GUI::Menu::construct("Hex Editor"); 136 app_menu->add_action(*m_new_action); 137 app_menu->add_action(*m_open_action); 138 app_menu->add_action(*m_save_action); 139 app_menu->add_action(*m_save_as_action); 140 app_menu->add_separator(); 141 app_menu->add_action(GUI::CommonActions::make_quit_action([this](auto&) { 142 if (!request_close()) 143 return; 144 GUI::Application::the().quit(0); 145 })); 146 menubar->add_menu(move(app_menu)); 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", this); 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", this); 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 = GUI::Menu::construct("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", this); 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 menubar->add_menu(move(edit_menu)); 198 199 auto view_menu = GUI::Menu::construct("View"); 200 view_menu->add_submenu(move(bytes_per_row_menu)); 201 menubar->add_menu(move(view_menu)); 202 203 auto help_menu = GUI::Menu::construct("Help"); 204 help_menu->add_action(GUI::Action::create("About", [&](const GUI::Action&) { 205 GUI::AboutDialog::show("Hex Editor", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hexeditor.png"), window()); 206 })); 207 menubar->add_menu(move(help_menu)); 208 209 GUI::Application::the().set_menubar(move(menubar)); 210 211 m_editor->set_focus(true); 212} 213 214HexEditorWidget::~HexEditorWidget() 215{ 216} 217 218void HexEditorWidget::set_path(const FileSystemPath& file) 219{ 220 m_path = file.string(); 221 m_name = file.title(); 222 m_extension = file.extension(); 223 update_title(); 224} 225 226void HexEditorWidget::update_title() 227{ 228 StringBuilder builder; 229 builder.append("Hex Editor: "); 230 builder.append(m_path); 231 if (m_document_dirty) 232 builder.append(" (*)"); 233 window()->set_title(builder.to_string()); 234} 235 236void HexEditorWidget::open_file(const String& path) 237{ 238 auto file = Core::File::construct(path); 239 if (!file->open(Core::IODevice::ReadOnly)) { 240 GUI::MessageBox::show(String::format("Opening \"%s\" failed: %s", path.characters(), strerror(errno)), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window()); 241 return; 242 } 243 244 m_document_dirty = false; 245 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. 246 set_path(FileSystemPath(path)); 247} 248 249bool HexEditorWidget::request_close() 250{ 251 if (!m_document_dirty) 252 return true; 253 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()); 254 return result == GUI::MessageBox::ExecOK; 255}