1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14:48:14 +00:00

FontEditor: Support selection ranges

This makes editing much easier, e.g. you don't need longer to copy
hundreds of glyphs one by one.

It has some flaws, e.g. it's not integrated with undo stack,
but we need to start with something!
This commit is contained in:
Maciej 2022-01-04 18:24:06 +01:00 committed by Andreas Kling
parent ad2551e6b8
commit 1d98499234
6 changed files with 283 additions and 163 deletions

View file

@ -179,9 +179,7 @@ FontEditorWidget::FontEditorWidget()
save_as(save_path.value());
});
m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) {
if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->cut_glyph();
cut_selected_glyphs();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
else
@ -189,23 +187,15 @@ FontEditorWidget::FontEditorWidget()
update_statusbar();
});
m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->copy_glyph();
copy_selected_glyphs();
});
m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
m_glyph_editor_widget->paste_glyph();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(true, GUI::AllowCallback::No);
else
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
paste_glyphs();
update_statusbar();
});
m_paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "glyph/x-fonteditor");
m_delete_action = GUI::CommonActions::make_delete_action([this](auto&) {
if (m_glyph_editor_widget->is_glyph_empty() && !m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->delete_glyph();
delete_selected_glyphs();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
else
@ -237,14 +227,14 @@ FontEditorWidget::FontEditorWidget()
int code_point = strtoul(&input[0], nullptr, 16);
code_point = clamp(code_point, 0x0000, 0x10FFFF);
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(code_point);
m_glyph_map_widget->set_active_glyph(code_point);
m_glyph_map_widget->scroll_to_glyph(code_point);
}
});
m_go_to_glyph_action->set_status_tip("Go to the specified code point");
m_previous_glyph_action = GUI::Action::create("Pre&vious Glyph", { Mod_Alt, Key_Left }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
bool search_wrapped = false;
for (int i = m_glyph_map_widget->selected_glyph() - 1;; --i) {
for (int i = m_glyph_map_widget->active_glyph() - 1;; --i) {
if (i < 0 && !search_wrapped) {
i = 0x10FFFF;
search_wrapped = true;
@ -253,7 +243,7 @@ FontEditorWidget::FontEditorWidget()
}
if (m_edited_font->contains_raw_glyph(i)) {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(i);
m_glyph_map_widget->set_active_glyph(i);
m_glyph_map_widget->scroll_to_glyph(i);
break;
}
@ -262,7 +252,7 @@ FontEditorWidget::FontEditorWidget()
m_previous_glyph_action->set_status_tip("Seek the previous visible glyph");
m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
bool search_wrapped = false;
for (int i = m_glyph_map_widget->selected_glyph() + 1;; ++i) {
for (int i = m_glyph_map_widget->active_glyph() + 1;; ++i) {
if (i > 0x10FFFF && !search_wrapped) {
i = 0;
search_wrapped = true;
@ -271,7 +261,7 @@ FontEditorWidget::FontEditorWidget()
}
if (m_edited_font->contains_raw_glyph(i)) {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(i);
m_glyph_map_widget->set_active_glyph(i);
m_glyph_map_widget->scroll_to_glyph(i);
break;
}
@ -377,7 +367,7 @@ FontEditorWidget::FontEditorWidget()
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
};
m_glyph_map_widget->on_glyph_selected = [this](int glyph) {
m_glyph_map_widget->on_active_glyph_changed = [this](int glyph) {
if (m_undo_glyph)
m_undo_glyph->set_code_point(glyph);
m_glyph_editor_widget->set_glyph(glyph);
@ -401,7 +391,7 @@ FontEditorWidget::FontEditorWidget()
m_fixed_width_checkbox->on_checked = [this](bool checked) {
m_edited_font->set_fixed_width(checked);
auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph());
m_glyph_editor_width_spinbox->set_visible(!checked);
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_visible(checked);
@ -413,9 +403,9 @@ FontEditorWidget::FontEditorWidget()
m_glyph_editor_width_spinbox->on_change = [this](int value) {
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), value);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), value);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
update_preview();
update_statusbar();
did_modify_font();
@ -423,9 +413,9 @@ FontEditorWidget::FontEditorWidget()
m_glyph_editor_present_checkbox->on_checked = [this](bool checked) {
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
update_preview();
update_statusbar();
did_modify_font();
@ -504,10 +494,10 @@ void FontEditorWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&&
m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width());
m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width(), GUI::AllowCallback::No);
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width());
m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width(), GUI::AllowCallback::No);
m_name_textbox->set_text(m_edited_font->name(), GUI::AllowCallback::No);
@ -540,12 +530,12 @@ void FontEditorWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&&
deferred_invoke([this] {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->active_glyph());
update_title();
});
m_undo_stack = make<GUI::UndoStack>();
m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->selected_glyph(), *m_edited_font));
m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->active_glyph(), *m_edited_font));
m_undo_stack->on_state_change = [this] {
m_undo_action->set_enabled(m_undo_stack->can_undo());
@ -651,14 +641,14 @@ void FontEditorWidget::undo()
m_undo_stack->undo();
auto glyph = m_undo_glyph->restored_code_point();
auto glyph_width = m_undo_glyph->restored_width();
m_glyph_map_widget->set_selected_glyph(glyph);
m_glyph_map_widget->set_active_glyph(glyph);
m_glyph_map_widget->scroll_to_glyph(glyph);
if (m_edited_font->is_fixed_width()) {
m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
} else {
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
}
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), glyph_width);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(glyph);
update_preview();
@ -672,14 +662,14 @@ void FontEditorWidget::redo()
m_undo_stack->redo();
auto glyph = m_undo_glyph->restored_code_point();
auto glyph_width = m_undo_glyph->restored_width();
m_glyph_map_widget->set_selected_glyph(glyph);
m_glyph_map_widget->set_active_glyph(glyph);
m_glyph_map_widget->scroll_to_glyph(glyph);
if (m_edited_font->is_fixed_width()) {
m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
} else {
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
}
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), glyph_width);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(glyph);
update_preview();
@ -722,7 +712,7 @@ void FontEditorWidget::did_modify_font()
void FontEditorWidget::update_statusbar()
{
auto glyph = m_glyph_map_widget->selected_glyph();
auto glyph = m_glyph_map_widget->active_glyph();
StringBuilder builder;
builder.appendff("U+{:04X} (", glyph);
@ -803,3 +793,118 @@ void FontEditorWidget::set_scale_and_save(i32 scale)
Config::write_i32("FontEditor", "GlyphEditor", "Scale", scale);
did_resize_glyph_editor();
}
void FontEditorWidget::copy_selected_glyphs()
{
ByteBuffer buffer;
int first_glyph = -1;
size_t glyph_count = 0;
auto append_glyph_to_buffer = [&](int glyph) {
if (!edited_font().contains_glyph(glyph))
return;
if (first_glyph == -1 || glyph < first_glyph)
first_glyph = glyph;
buffer.append({ (char*)&glyph, sizeof(int) });
auto bitmap = edited_font().raw_glyph(glyph).glyph_bitmap();
buffer.append((u8)bitmap.width());
buffer.append((u8)bitmap.height());
for (int x = 0; x < bitmap.width(); x++) {
for (int y = 0; y < bitmap.height(); y++)
buffer.append(bitmap.bit_at(x, y));
}
glyph_count++;
};
auto selection = m_glyph_map_widget->selection().normalized();
for (int i = selection.start(); i < selection.start() + selection.size(); i++)
append_glyph_to_buffer(i);
HashMap<String, String> metadata;
metadata.set("first_glyph", String::number(first_glyph));
metadata.set("count", String::number(glyph_count));
GUI::Clipboard::the().set_data(buffer.bytes(), "glyph/x-fonteditor", metadata);
}
void FontEditorWidget::cut_selected_glyphs()
{
copy_selected_glyphs();
delete_selected_glyphs();
}
void FontEditorWidget::paste_glyphs()
{
auto [data, mime_type, metadata] = GUI::Clipboard::the().fetch_data_and_type();
if (!mime_type.starts_with("glyph/"))
return;
;
auto glyph_count = metadata.get("count").value_or("0").to_uint().value_or(0);
if (glyph_count == 0)
return;
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if (glyph_count == 1) {
if (m_glyph_editor_widget->on_undo_event)
m_glyph_editor_widget->on_undo_event();
}
auto first_glyph = metadata.get("first_glyph").value_or("0").to_uint().value_or(0);
InputMemoryStream stream(data.bytes());
for (size_t s = 0; s < glyph_count; s++) {
int copied_glyph {};
char width {};
char height {};
stream >> Bytes { (char*)&copied_glyph, sizeof(int) } >> width >> height;
if (stream.has_any_error()) {
dbgln("Failed to read glyph from clipboard, aborting!");
return;
}
int glyph = m_glyph_map_widget->active_glyph() + (copied_glyph - first_glyph);
auto bitmap = edited_font().raw_glyph(glyph).glyph_bitmap();
m_edited_font->set_glyph_width(glyph, min(width, edited_font().max_glyph_width()));
for (int x = 0; x < min(width, edited_font().max_glyph_width()); x++) {
for (int y = 0; y < min(height, edited_font().glyph_height()); y++) {
char byte;
stream >> byte;
if (stream.has_any_error()) {
dbgln("Failed to read glyph from clipboard, aborting!");
return;
}
bitmap.set_bit_at(x, y, byte);
}
}
if (m_glyph_editor_widget->on_glyph_altered)
m_glyph_editor_widget->on_glyph_altered(glyph);
}
m_glyph_editor_widget->update();
}
void FontEditorWidget::delete_selected_glyphs()
{
auto delete_glyph = [&](int glyph) {
auto bitmap = m_edited_font->raw_glyph(glyph).glyph_bitmap();
m_edited_font->set_glyph_width(glyph, 0);
for (int x = 0; x < m_edited_font->max_glyph_width(); x++)
for (int y = 0; y < m_edited_font->glyph_height(); y++)
bitmap.set_bit_at(x, y, false);
if (m_glyph_editor_widget->on_glyph_altered)
m_glyph_editor_widget->on_glyph_altered(glyph);
};
auto selection = m_glyph_map_widget->selection().normalized();
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if (selection.size() == 1) {
if (m_glyph_editor_widget->on_undo_event)
m_glyph_editor_widget->on_undo_event();
}
for (int i = selection.start(); i < selection.start() + selection.size(); i++)
delete_glyph(i);
}