summaryrefslogtreecommitdiff
path: root/Applications/HexEditor/HexEditorWidget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Applications/HexEditor/HexEditorWidget.cpp')
-rw-r--r--Applications/HexEditor/HexEditorWidget.cpp183
1 files changed, 183 insertions, 0 deletions
diff --git a/Applications/HexEditor/HexEditorWidget.cpp b/Applications/HexEditor/HexEditorWidget.cpp
new file mode 100644
index 0000000000..b7993d4856
--- /dev/null
+++ b/Applications/HexEditor/HexEditorWidget.cpp
@@ -0,0 +1,183 @@
+#include "HexEditorWidget.h"
+#include <AK/Optional.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/CFile.h>
+#include <LibDraw/PNGLoader.h>
+#include <LibGUI/GAboutDialog.h>
+#include <LibGUI/GAction.h>
+#include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GButton.h>
+#include <LibGUI/GFilePicker.h>
+#include <LibGUI/GFontDatabase.h>
+#include <LibGUI/GInputBox.h>
+#include <LibGUI/GMenuBar.h>
+#include <LibGUI/GMessageBox.h>
+#include <LibGUI/GStatusBar.h>
+#include <LibGUI/GTextBox.h>
+#include <LibGUI/GTextEditor.h>
+#include <LibGUI/GToolBar.h>
+#include <stdio.h>
+
+HexEditorWidget::HexEditorWidget()
+{
+ set_layout(make<GBoxLayout>(Orientation::Vertical));
+ layout()->set_spacing(0);
+
+ m_editor = HexEditor::construct(this);
+
+ m_editor->on_status_change = [this](int position, HexEditor::EditMode edit_mode, int selection_start, int selection_end) {
+ m_statusbar->set_text(String::format("Offset: %8X, Edit Mode: %s, Selection Start: %d, Selection End: %d, Selected Bytes: %d",
+ position,
+ edit_mode == HexEditor::EditMode::Hex ? "Hex" : "Text",
+ selection_start,
+ selection_end,
+ (selection_end - selection_start) + 1));
+ };
+
+ m_editor->on_change = [this] {
+ bool was_dirty = m_document_dirty;
+ m_document_dirty = true;
+ if (!was_dirty)
+ update_title();
+ };
+
+ m_statusbar = GStatusBar::construct(this);
+
+ m_open_action = GCommonActions::make_open_action([this](auto&) {
+ Optional<String> open_path = GFilePicker::get_open_filepath();
+
+ if (!open_path.has_value())
+ return;
+
+ open_file(open_path.value());
+ });
+
+ m_save_action = GAction::create("Save", { Mod_Ctrl, Key_S }, GraphicsBitmap::load_from_file("/res/icons/16x16/save.png"), [&](const GAction&) {
+ if (!m_path.is_empty()) {
+ if (!m_editor->write_to_file(m_path)) {
+ GMessageBox::show("Unable to save file.\n", "Error", GMessageBox::Type::Error, GMessageBox::InputType::OK, window());
+ } else {
+ m_document_dirty = false;
+ update_title();
+ }
+ return;
+ }
+
+ m_save_as_action->activate();
+ });
+
+ m_save_as_action = GAction::create("Save as...", { Mod_None, Key_F12 }, GraphicsBitmap::load_from_file("/res/icons/16x16/save.png"), [this](const GAction&) {
+ Optional<String> save_path = GFilePicker::get_save_filepath(m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "bin" : m_extension);
+ if (!save_path.has_value())
+ return;
+
+ if (!m_editor->write_to_file(save_path.value())) {
+ GMessageBox::show("Unable to save file.\n", "Error", GMessageBox::Type::Error, GMessageBox::InputType::OK, window());
+ return;
+ }
+
+ m_document_dirty = false;
+ set_path(FileSystemPath(save_path.value()));
+ dbg() << "Wrote document to " << save_path.value();
+ });
+
+ auto menubar = make<GMenuBar>();
+ auto app_menu = make<GMenu>("Hex Editor");
+ app_menu->add_action(*m_open_action);
+ app_menu->add_action(*m_save_action);
+ app_menu->add_action(*m_save_as_action);
+ app_menu->add_separator();
+ app_menu->add_action(GCommonActions::make_quit_action([this](auto&) {
+ if (!request_close())
+ return;
+ GApplication::the().quit(0);
+ }));
+ menubar->add_menu(move(app_menu));
+
+ auto bytes_per_row_menu = make<GMenu>("Bytes Per Row");
+ for (int i = 8; i <= 32; i += 8) {
+ bytes_per_row_menu->add_action(GAction::create(String::number(i), [this, i](auto&) {
+ m_editor->set_bytes_per_row(i);
+ m_editor->update();
+ }));
+ }
+
+ m_goto_action = GAction::create("Go To...", GraphicsBitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](const GAction&) {
+ auto input_box = GInputBox::construct("Enter offset:", "Go To", this);
+ if (input_box->exec() == GInputBox::ExecOK && !input_box->text_value().is_empty()) {
+ auto valid = false;
+ auto new_offset = input_box->text_value().to_int(valid);
+ if (valid) {
+ m_editor->set_position(new_offset);
+ }
+ }
+ });
+
+ auto edit_menu = make<GMenu>("Edit");
+ edit_menu->add_action(*m_goto_action);
+ edit_menu->add_separator();
+ edit_menu->add_action(GAction::create("Copy Hex", [&](const GAction&) {
+ m_editor->copy_selected_hex_to_clipboard();
+ }));
+ edit_menu->add_action(GAction::create("Copy Text", [&](const GAction&) {
+ m_editor->copy_selected_text_to_clipboard();
+ }));
+ menubar->add_menu(move(edit_menu));
+
+ auto view_menu = make<GMenu>("View");
+ view_menu->add_submenu(move(bytes_per_row_menu));
+ menubar->add_menu(move(view_menu));
+
+ auto help_menu = make<GMenu>("Help");
+ help_menu->add_action(GAction::create("About", [&](const GAction&) {
+ GAboutDialog::show("Hex Editor", load_png("/res/icons/32x32/app-hexeditor.png"), window());
+ }));
+ menubar->add_menu(move(help_menu));
+
+ GApplication::the().set_menubar(move(menubar));
+
+ m_editor->set_focus(true);
+}
+
+HexEditorWidget::~HexEditorWidget()
+{
+}
+
+void HexEditorWidget::set_path(const FileSystemPath& file)
+{
+ m_path = file.string();
+ m_name = file.title();
+ m_extension = file.extension();
+ update_title();
+}
+
+void HexEditorWidget::update_title()
+{
+ StringBuilder builder;
+ builder.append("Hex Editor: ");
+ builder.append(m_path);
+ if (m_document_dirty)
+ builder.append(" (*)");
+ window()->set_title(builder.to_string());
+}
+
+void HexEditorWidget::open_file(const String& path)
+{
+ auto file = CFile::construct(path);
+ if (!file->open(CIODevice::ReadOnly)) {
+ GMessageBox::show(String::format("Opening \"%s\" failed: %s", path.characters(), strerror(errno)), "Error", GMessageBox::Type::Error, GMessageBox::InputType::OK, window());
+ return;
+ }
+
+ m_document_dirty = false;
+ 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.
+ set_path(FileSystemPath(path));
+}
+
+bool HexEditorWidget::request_close()
+{
+ if (!m_document_dirty)
+ return true;
+ auto result = GMessageBox::show("The file has been modified. Quit without saving?", "Quit without saving?", GMessageBox::Type::Warning, GMessageBox::InputType::OKCancel, window());
+ return result == GMessageBox::ExecOK;
+}