mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +00:00
HexEditor: Add annotations system
Allow the user to highlight sections of the edited document, giving them arbitrary background colors. These annotations can be created from a selection, or by manually specifying the start and end offsets. Annotations can be edited or deleted by right-clicking them. Any color can be used for the background. Dark colors automatically make the text white for easier readability. When creating a new annotation, we use whatever color the user last picked as this is slightly more likely to be the one they want. Icons contributed by Cubic Love. Co-authored-by: Cubic Love <7754483+cubiclove@users.noreply.github.com>
This commit is contained in:
parent
1168e46c1d
commit
cbd28c9110
Notes:
sideshowbarker
2024-07-17 03:03:44 +09:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/SerenityOS/serenity/commit/cbd28c9110 Pull-request: https://github.com/SerenityOS/serenity/pull/22691 Reviewed-by: https://github.com/DanShaders Reviewed-by: https://github.com/LucasChollet ✅
13 changed files with 378 additions and 4 deletions
BIN
Base/res/icons/16x16/annotation-add.png
Normal file
BIN
Base/res/icons/16x16/annotation-add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 B |
BIN
Base/res/icons/16x16/annotation-remove.png
Normal file
BIN
Base/res/icons/16x16/annotation-remove.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 321 B |
BIN
Base/res/icons/16x16/annotation.png
Normal file
BIN
Base/res/icons/16x16/annotation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 319 B |
|
@ -4,11 +4,14 @@ serenity_component(
|
|||
TARGETS HexEditor
|
||||
)
|
||||
|
||||
compile_gml(HexEditorWidget.gml HexEditorWidgetGML.cpp)
|
||||
compile_gml(GoToOffsetWidget.gml GoToOffsetWidgetGML.cpp)
|
||||
compile_gml(EditAnnotationWidget.gml EditAnnotationWidgetGML.cpp)
|
||||
compile_gml(FindWidget.gml FindWidgetGML.cpp)
|
||||
compile_gml(GoToOffsetWidget.gml GoToOffsetWidgetGML.cpp)
|
||||
compile_gml(HexEditorWidget.gml HexEditorWidgetGML.cpp)
|
||||
|
||||
set(SOURCES
|
||||
EditAnnotationDialog.cpp
|
||||
EditAnnotationWidgetGML.cpp
|
||||
FindDialog.cpp
|
||||
FindWidgetGML.cpp
|
||||
GoToOffsetDialog.cpp
|
||||
|
|
109
Userland/Applications/HexEditor/EditAnnotationDialog.cpp
Normal file
109
Userland/Applications/HexEditor/EditAnnotationDialog.cpp
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "EditAnnotationDialog.h"
|
||||
#include <LibGUI/MessageBox.h>
|
||||
|
||||
static Gfx::Color s_most_recent_color { Color::from_argb(0xfffce94f) };
|
||||
|
||||
GUI::Dialog::ExecResult EditAnnotationDialog::show_create_dialog(GUI::Window* parent_window, HexDocument& document, Selection selection)
|
||||
{
|
||||
auto dialog_or_error = EditAnnotationDialog::try_create(parent_window, document, selection);
|
||||
if (dialog_or_error.is_error()) {
|
||||
GUI::MessageBox::show(parent_window, MUST(String::formatted("{}", dialog_or_error.error())), "Error while opening Create Annotation dialog"sv, GUI::MessageBox::Type::Error);
|
||||
return ExecResult::Aborted;
|
||||
}
|
||||
|
||||
auto dialog = dialog_or_error.release_value();
|
||||
return dialog->exec();
|
||||
}
|
||||
|
||||
GUI::Dialog::ExecResult EditAnnotationDialog::show_edit_dialog(GUI::Window* parent_window, HexDocument& document, Annotation& annotation)
|
||||
{
|
||||
auto dialog_or_error = EditAnnotationDialog::try_create(parent_window, document, &annotation);
|
||||
if (dialog_or_error.is_error()) {
|
||||
GUI::MessageBox::show(parent_window, MUST(String::formatted("{}", dialog_or_error.error())), "Error while opening Edit Annotation dialog"sv, GUI::MessageBox::Type::Error);
|
||||
return ExecResult::Aborted;
|
||||
}
|
||||
|
||||
auto dialog = dialog_or_error.release_value();
|
||||
return dialog->exec();
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<EditAnnotationDialog>> EditAnnotationDialog::try_create(GUI::Window* parent_window, HexDocument& hex_document, Variant<Annotation*, Selection> selection_or_annotation)
|
||||
{
|
||||
auto widget = TRY(HexEditor::EditAnnotationWidget::try_create());
|
||||
return adopt_nonnull_ref_or_enomem(new (nothrow) EditAnnotationDialog(parent_window, move(widget), hex_document, move(selection_or_annotation)));
|
||||
}
|
||||
|
||||
EditAnnotationDialog::EditAnnotationDialog(GUI::Window* parent_window, NonnullRefPtr<HexEditor::EditAnnotationWidget> widget, HexDocument& hex_document, Variant<Annotation*, Selection> selection_or_annotation)
|
||||
: GUI::Dialog(parent_window)
|
||||
, m_document(hex_document.make_weak_ptr())
|
||||
{
|
||||
resize(260, 140);
|
||||
set_resizable(false);
|
||||
set_main_widget(widget);
|
||||
|
||||
m_start_offset = find_descendant_of_type_named<GUI::NumericInput>("start_offset");
|
||||
m_end_offset = find_descendant_of_type_named<GUI::NumericInput>("end_offset");
|
||||
m_background_color = find_descendant_of_type_named<GUI::ColorInput>("background_color");
|
||||
m_save_button = find_descendant_of_type_named<GUI::DialogButton>("save_button");
|
||||
m_cancel_button = find_descendant_of_type_named<GUI::DialogButton>("cancel_button");
|
||||
|
||||
// FIXME: This could be specified in GML, but the GML doesn't like property setters that aren't `set_FOO()`.
|
||||
m_background_color->set_color_has_alpha_channel(false);
|
||||
|
||||
// NOTE: The NumericInput stores an i64, so not all size_t values can fit. But I don't think we'll be
|
||||
// hex-editing files larger than 9000 petabytes for the foreseeable future!
|
||||
VERIFY(hex_document.size() <= NumericLimits<i64>::max());
|
||||
m_start_offset->set_min(0);
|
||||
m_start_offset->set_max(hex_document.size() - 1);
|
||||
m_end_offset->set_min(0);
|
||||
m_end_offset->set_max(hex_document.size() - 1);
|
||||
|
||||
selection_or_annotation.visit(
|
||||
[this](Annotation*& annotation) {
|
||||
m_annotation = *annotation;
|
||||
set_title("Edit Annotation"sv);
|
||||
set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation.png"sv).release_value_but_fixme_should_propagate_errors());
|
||||
VERIFY(m_annotation->start_offset <= NumericLimits<i64>::max());
|
||||
VERIFY(m_annotation->end_offset <= NumericLimits<i64>::max());
|
||||
m_start_offset->set_value(m_annotation->start_offset);
|
||||
m_end_offset->set_value(m_annotation->end_offset);
|
||||
m_background_color->set_color(m_annotation->background_color);
|
||||
},
|
||||
[this](Selection& selection) {
|
||||
set_title("Add Annotation"sv);
|
||||
set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation-add.png"sv).release_value_but_fixme_should_propagate_errors());
|
||||
// Selection start is inclusive, and end is exclusive.
|
||||
// Therefore, if the selection isn't empty, we need to subtract 1 from the end offset.
|
||||
m_start_offset->set_value(selection.start);
|
||||
m_end_offset->set_value(selection.is_empty() ? selection.end : selection.end - 1);
|
||||
// Default to the most recently used annotation color.
|
||||
m_background_color->set_color(s_most_recent_color);
|
||||
});
|
||||
|
||||
m_save_button->on_click = [this](auto) {
|
||||
auto start_offset = static_cast<size_t>(m_start_offset->value());
|
||||
auto end_offset = static_cast<size_t>(m_end_offset->value());
|
||||
Annotation result {
|
||||
.start_offset = min(start_offset, end_offset),
|
||||
.end_offset = max(start_offset, end_offset),
|
||||
.background_color = m_background_color->color(),
|
||||
};
|
||||
if (m_annotation.has_value()) {
|
||||
*m_annotation = move(result);
|
||||
} else {
|
||||
if (m_document)
|
||||
m_document->add_annotation(result);
|
||||
}
|
||||
s_most_recent_color = m_background_color->color();
|
||||
done(ExecResult::OK);
|
||||
};
|
||||
m_cancel_button->on_click = [this](auto) {
|
||||
done(ExecResult::Cancel);
|
||||
};
|
||||
}
|
37
Userland/Applications/HexEditor/EditAnnotationDialog.h
Normal file
37
Userland/Applications/HexEditor/EditAnnotationDialog.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EditAnnotationWidget.h"
|
||||
#include "HexDocument.h"
|
||||
#include "Selection.h"
|
||||
#include <LibGUI/Button.h>
|
||||
#include <LibGUI/ColorInput.h>
|
||||
#include <LibGUI/Dialog.h>
|
||||
#include <LibGUI/NumericInput.h>
|
||||
|
||||
class EditAnnotationDialog : public GUI::Dialog {
|
||||
C_OBJECT_ABSTRACT(EditAnnotationDialog)
|
||||
|
||||
public:
|
||||
static ExecResult show_create_dialog(GUI::Window* parent_window, HexDocument&, Selection);
|
||||
static ExecResult show_edit_dialog(GUI::Window* parent_window, HexDocument&, Annotation&);
|
||||
static ErrorOr<NonnullRefPtr<EditAnnotationDialog>> try_create(GUI::Window* parent_window, HexDocument&, Variant<Annotation*, Selection>);
|
||||
|
||||
private:
|
||||
EditAnnotationDialog(GUI::Window* parent_window, NonnullRefPtr<HexEditor::EditAnnotationWidget>, HexDocument&, Variant<Annotation*, Selection>);
|
||||
virtual ~EditAnnotationDialog() override = default;
|
||||
|
||||
WeakPtr<HexDocument> m_document;
|
||||
Optional<Annotation&> m_annotation;
|
||||
|
||||
RefPtr<GUI::NumericInput> m_start_offset;
|
||||
RefPtr<GUI::NumericInput> m_end_offset;
|
||||
RefPtr<GUI::ColorInput> m_background_color;
|
||||
RefPtr<GUI::Button> m_save_button;
|
||||
RefPtr<GUI::Button> m_cancel_button;
|
||||
};
|
75
Userland/Applications/HexEditor/EditAnnotationWidget.gml
Normal file
75
Userland/Applications/HexEditor/EditAnnotationWidget.gml
Normal file
|
@ -0,0 +1,75 @@
|
|||
@HexEditor::EditAnnotationWidget {
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
fill_with_background_color: true
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
preferred_height: "fit"
|
||||
|
||||
@GUI::Label {
|
||||
text: "Start offset:"
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::NumericInput {
|
||||
name: "start_offset"
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
preferred_height: "fit"
|
||||
|
||||
@GUI::Label {
|
||||
text: "End offset:"
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::NumericInput {
|
||||
name: "end_offset"
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
preferred_height: "fit"
|
||||
|
||||
@GUI::Label {
|
||||
text: "Color:"
|
||||
text_alignment: "CenterLeft"
|
||||
}
|
||||
|
||||
@GUI::ColorInput {
|
||||
name: "background_color"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
margins: [4]
|
||||
}
|
||||
preferred_height: "fit"
|
||||
|
||||
@GUI::Layout::Spacer {}
|
||||
|
||||
@GUI::DialogButton {
|
||||
name: "save_button"
|
||||
text: "Save"
|
||||
}
|
||||
|
||||
@GUI::DialogButton {
|
||||
name: "cancel_button"
|
||||
text: "Cancel"
|
||||
}
|
||||
}
|
||||
}
|
23
Userland/Applications/HexEditor/EditAnnotationWidget.h
Normal file
23
Userland/Applications/HexEditor/EditAnnotationWidget.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace HexEditor {
|
||||
|
||||
class EditAnnotationWidget : public GUI::Widget {
|
||||
C_OBJECT_ABSTRACT(GoToOffsetWidget)
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<EditAnnotationWidget>> try_create();
|
||||
virtual ~EditAnnotationWidget() override = default;
|
||||
|
||||
private:
|
||||
EditAnnotationWidget() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -24,6 +25,34 @@ bool HexDocument::is_dirty() const
|
|||
return m_changes.size() > 0;
|
||||
}
|
||||
|
||||
void HexDocument::add_annotation(Annotation annotation)
|
||||
{
|
||||
m_annotations.append(move(annotation));
|
||||
}
|
||||
|
||||
void HexDocument::delete_annotation(Annotation const& annotation)
|
||||
{
|
||||
m_annotations.remove_first_matching([&](auto& other) {
|
||||
return other == annotation;
|
||||
});
|
||||
}
|
||||
|
||||
Optional<Annotation&> HexDocument::closest_annotation_at(size_t position)
|
||||
{
|
||||
// FIXME: If we end up with a lot of annotations, we'll need to store them and query them in a smarter way.
|
||||
Optional<Annotation&> result;
|
||||
for (auto& annotation : m_annotations) {
|
||||
if (annotation.start_offset <= position && position <= annotation.end_offset) {
|
||||
// If multiple annotations cover this position, use whichever starts latest. This would be the innermost one
|
||||
// if they overlap fully rather than partially.
|
||||
if (!result.has_value() || result->start_offset < annotation.start_offset)
|
||||
result = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HexDocumentMemory::HexDocumentMemory(ByteBuffer&& buffer)
|
||||
: m_buffer(move(buffer))
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -14,9 +15,18 @@
|
|||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibGUI/Command.h>
|
||||
#include <LibGfx/Color.h>
|
||||
|
||||
constexpr Duration COMMAND_COMMIT_TIME = Duration::from_milliseconds(400);
|
||||
|
||||
struct Annotation {
|
||||
size_t start_offset { 0 };
|
||||
size_t end_offset { 0 };
|
||||
Gfx::Color background_color { Color::from_argb(0xfffce94f) };
|
||||
|
||||
bool operator==(Annotation const& other) const = default;
|
||||
};
|
||||
|
||||
class HexDocument : public Weakable<HexDocument> {
|
||||
public:
|
||||
enum class Type {
|
||||
|
@ -38,8 +48,14 @@ public:
|
|||
virtual bool is_dirty() const;
|
||||
virtual void clear_changes() = 0;
|
||||
|
||||
ReadonlySpan<Annotation> annotations() const { return m_annotations; }
|
||||
void add_annotation(Annotation);
|
||||
void delete_annotation(Annotation const&);
|
||||
Optional<Annotation&> closest_annotation_at(size_t position);
|
||||
|
||||
protected:
|
||||
HashMap<size_t, u8> m_changes;
|
||||
Vector<Annotation> m_annotations;
|
||||
};
|
||||
|
||||
class HexDocumentMemory final : public HexDocument {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include "HexEditor.h"
|
||||
#include "EditAnnotationDialog.h"
|
||||
#include "SearchResultsModel.h"
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Debug.h>
|
||||
|
@ -43,6 +44,32 @@ HexEditor::HexEditor()
|
|||
set_background_role(ColorRole::Base);
|
||||
set_foreground_role(ColorRole::BaseText);
|
||||
vertical_scrollbar().set_step(line_height());
|
||||
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_add_annotation_action = GUI::Action::create(
|
||||
"&Add Annotation",
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation-add.png"sv).release_value_but_fixme_should_propagate_errors(),
|
||||
[this](GUI::Action&) { show_create_annotation_dialog(); },
|
||||
this);
|
||||
m_context_menu->add_action(*m_add_annotation_action);
|
||||
m_edit_annotation_action = GUI::Action::create(
|
||||
"&Edit Annotation",
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation.png"sv).release_value_but_fixme_should_propagate_errors(),
|
||||
[this](GUI::Action&) {
|
||||
VERIFY(m_hovered_annotation.has_value());
|
||||
show_edit_annotation_dialog(*m_hovered_annotation);
|
||||
},
|
||||
this);
|
||||
m_context_menu->add_action(*m_edit_annotation_action);
|
||||
m_delete_annotation_action = GUI::Action::create(
|
||||
"&Delete Annotation",
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation-remove.png"sv).release_value_but_fixme_should_propagate_errors(),
|
||||
[this](GUI::Action&) {
|
||||
VERIFY(m_hovered_annotation.has_value());
|
||||
show_delete_annotation_dialog(*m_hovered_annotation);
|
||||
},
|
||||
this);
|
||||
m_context_menu->add_action(*m_delete_annotation_action);
|
||||
}
|
||||
|
||||
ErrorOr<void> HexEditor::open_new_file(size_t size)
|
||||
|
@ -320,8 +347,10 @@ void HexEditor::mousemove_event(GUI::MouseEvent& event)
|
|||
|
||||
if (maybe_offset_data.has_value()) {
|
||||
set_override_cursor(Gfx::StandardCursor::IBeam);
|
||||
m_hovered_annotation = m_document->closest_annotation_at(maybe_offset_data->offset);
|
||||
} else {
|
||||
set_override_cursor(Gfx::StandardCursor::None);
|
||||
m_hovered_annotation.clear();
|
||||
}
|
||||
|
||||
if (m_in_drag_select) {
|
||||
|
@ -520,6 +549,13 @@ ErrorOr<void> HexEditor::text_mode_keydown_event(GUI::KeyEvent& event)
|
|||
return {};
|
||||
}
|
||||
|
||||
void HexEditor::context_menu_event(GUI::ContextMenuEvent& event)
|
||||
{
|
||||
m_edit_annotation_action->set_visible(m_hovered_annotation.has_value());
|
||||
m_delete_annotation_action->set_visible(m_hovered_annotation.has_value());
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
void HexEditor::update_status()
|
||||
{
|
||||
if (on_status_change)
|
||||
|
@ -591,6 +627,7 @@ void HexEditor::paint_event(GUI::PaintEvent& event)
|
|||
return;
|
||||
|
||||
auto const cell = m_document->get(byte_position);
|
||||
auto const annotation = m_document->closest_annotation_at(byte_position);
|
||||
|
||||
Gfx::IntRect hex_display_rect_high_nibble {
|
||||
frame_thickness() + offset_margin_width() + j * cell_width() + 2 * m_padding,
|
||||
|
@ -623,13 +660,16 @@ void HexEditor::paint_event(GUI::PaintEvent& event)
|
|||
// 1. Modified bytes
|
||||
// 2. The cursor position
|
||||
// 3. The selection
|
||||
// 4. Null bytes
|
||||
// 5. Regular formatting
|
||||
// 4. Annotations
|
||||
// 5. Null bytes
|
||||
// 6. Regular formatting
|
||||
auto determine_background_color = [&](EditMode edit_mode) -> Gfx::Color {
|
||||
if (selected)
|
||||
return cell.modified ? palette().selection().inverted() : palette().selection();
|
||||
if (byte_position == m_position && m_edit_mode != edit_mode)
|
||||
return palette().inactive_selection();
|
||||
if (annotation.has_value())
|
||||
return annotation->background_color;
|
||||
return palette().color(background_role());
|
||||
};
|
||||
auto determine_text_color = [&](EditMode edit_mode) -> Gfx::Color {
|
||||
|
@ -639,6 +679,8 @@ void HexEditor::paint_event(GUI::PaintEvent& event)
|
|||
return palette().selection_text();
|
||||
if (byte_position == m_position)
|
||||
return (m_edit_mode == edit_mode) ? palette().color(foreground_role()) : palette().inactive_selection_text();
|
||||
if (annotation.has_value())
|
||||
return annotation->background_color.suggested_foreground_color();
|
||||
if (cell.value == 0x00)
|
||||
return palette().color(ColorRole::PlaceholderText);
|
||||
return palette().color(foreground_role());
|
||||
|
@ -869,4 +911,27 @@ GUI::UndoStack& HexEditor::undo_stack()
|
|||
return m_undo_stack;
|
||||
}
|
||||
|
||||
void HexEditor::show_create_annotation_dialog()
|
||||
{
|
||||
auto result = EditAnnotationDialog::show_create_dialog(window(), *m_document, selection());
|
||||
if (result == GUI::Dialog::ExecResult::OK)
|
||||
update();
|
||||
}
|
||||
|
||||
void HexEditor::show_edit_annotation_dialog(Annotation& annotation)
|
||||
{
|
||||
auto result = EditAnnotationDialog::show_edit_dialog(window(), *m_document, annotation);
|
||||
if (result == GUI::Dialog::ExecResult::OK)
|
||||
update();
|
||||
}
|
||||
|
||||
void HexEditor::show_delete_annotation_dialog(Annotation& annotation)
|
||||
{
|
||||
auto result = GUI::MessageBox::show(window(), "Delete this annotation?"sv, "Delete annotation?"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
|
||||
if (result == GUI::Dialog::ExecResult::Yes) {
|
||||
m_document->delete_annotation(annotation);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -68,6 +68,10 @@ public:
|
|||
Function<void(size_t position, EditMode, Selection)> on_status_change;
|
||||
Function<void(bool is_document_dirty)> on_change;
|
||||
|
||||
void show_create_annotation_dialog();
|
||||
void show_edit_annotation_dialog(Annotation&);
|
||||
void show_delete_annotation_dialog(Annotation&);
|
||||
|
||||
protected:
|
||||
HexEditor();
|
||||
|
||||
|
@ -76,6 +80,7 @@ protected:
|
|||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||
virtual void keydown_event(GUI::KeyEvent&) override;
|
||||
virtual void context_menu_event(GUI::ContextMenuEvent&) override;
|
||||
|
||||
private:
|
||||
size_t m_line_spacing { 4 };
|
||||
|
@ -88,6 +93,12 @@ private:
|
|||
EditMode m_edit_mode { Hex };
|
||||
NonnullOwnPtr<HexDocument> m_document;
|
||||
GUI::UndoStack m_undo_stack;
|
||||
Optional<Annotation&> m_hovered_annotation;
|
||||
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
RefPtr<GUI::Action> m_add_annotation_action;
|
||||
RefPtr<GUI::Action> m_edit_annotation_action;
|
||||
RefPtr<GUI::Action> m_delete_annotation_action;
|
||||
|
||||
static constexpr int m_address_bar_width = 90;
|
||||
static constexpr int m_padding = 5;
|
||||
|
|
|
@ -451,6 +451,12 @@ ErrorOr<void> HexEditorWidget::initialize_menubar(GUI::Window& window)
|
|||
}));
|
||||
edit_menu->add_action(*m_fill_selection_action);
|
||||
edit_menu->add_separator();
|
||||
edit_menu->add_action(GUI::Action::create(
|
||||
"Add Annotation",
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/annotation-add.png"sv).release_value_but_fixme_should_propagate_errors(),
|
||||
[this](GUI::Action&) { m_editor->show_create_annotation_dialog(); },
|
||||
this));
|
||||
edit_menu->add_separator();
|
||||
edit_menu->add_action(*m_copy_hex_action);
|
||||
edit_menu->add_action(*m_copy_text_action);
|
||||
edit_menu->add_action(*m_copy_as_c_code_action);
|
||||
|
|
Loading…
Reference in a new issue