Serenity Operating System
at master 1098 lines 46 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "MainWidget.h" 9#include "GlyphEditorWidget.h" 10#include "NewFontDialog.h" 11#include <AK/Array.h> 12#include <AK/StringBuilder.h> 13#include <AK/StringUtils.h> 14#include <Applications/FontEditor/FontEditorWindowGML.h> 15#include <Applications/FontEditor/FontPreviewWindowGML.h> 16#include <LibConfig/Client.h> 17#include <LibDesktop/Launcher.h> 18#include <LibGUI/Action.h> 19#include <LibGUI/Application.h> 20#include <LibGUI/Button.h> 21#include <LibGUI/CheckBox.h> 22#include <LibGUI/Clipboard.h> 23#include <LibGUI/ComboBox.h> 24#include <LibGUI/FilePicker.h> 25#include <LibGUI/GlyphMapWidget.h> 26#include <LibGUI/GroupBox.h> 27#include <LibGUI/InputBox.h> 28#include <LibGUI/ItemListModel.h> 29#include <LibGUI/Label.h> 30#include <LibGUI/ListView.h> 31#include <LibGUI/Menu.h> 32#include <LibGUI/Menubar.h> 33#include <LibGUI/MessageBox.h> 34#include <LibGUI/SpinBox.h> 35#include <LibGUI/Statusbar.h> 36#include <LibGUI/TextBox.h> 37#include <LibGUI/ToolbarContainer.h> 38#include <LibGUI/Window.h> 39#include <LibGfx/Font/BitmapFont.h> 40#include <LibGfx/Font/Emoji.h> 41#include <LibGfx/Font/FontStyleMapping.h> 42#include <LibGfx/TextDirection.h> 43#include <LibUnicode/CharacterTypes.h> 44 45namespace FontEditor { 46 47static constexpr Array pangrams = { 48 "quick fox jumps nightly above wizard"sv, 49 "five quacking zephyrs jolt my wax bed"sv, 50 "pack my box with five dozen liquor jugs"sv, 51 "quick brown fox jumps over the lazy dog"sv, 52 "waxy and quivering jocks fumble the pizza"sv, 53 "~#:[@_1%]*{$2.3}/4^(5'6\")-&|7+8!=<9,0\\>?;"sv, 54 "byxfjärmat föl gick på duvshowen"sv, 55 "         "sv, 56 "float Fox.quick(h){ is_brown && it_jumps_over(doges.lazy) }"sv, 57 "<fox color=\"brown\" speed=\"quick\" jumps=\"over\">lazy dog</fox>"sv 58}; 59 60ErrorOr<RefPtr<GUI::Window>> MainWidget::create_preview_window() 61{ 62 auto window = TRY(GUI::Window::try_create(this)); 63 window->set_window_mode(GUI::WindowMode::RenderAbove); 64 window->set_title("Preview"); 65 window->resize(400, 150); 66 window->center_within(*this->window()); 67 68 auto main_widget = TRY(window->set_main_widget<GUI::Widget>()); 69 TRY(main_widget->load_from_gml(font_preview_window_gml)); 70 71 m_preview_label = find_descendant_of_type_named<GUI::Label>("preview_label"); 72 m_preview_label->set_font(edited_font()); 73 74 m_preview_textbox = find_descendant_of_type_named<GUI::TextBox>("preview_textbox"); 75 m_preview_textbox->on_change = [&] { 76 auto preview = DeprecatedString::formatted("{}\n{}", m_preview_textbox->text(), Unicode::to_unicode_uppercase_full(m_preview_textbox->text()).release_value_but_fixme_should_propagate_errors()); 77 m_preview_label->set_text(preview); 78 }; 79 m_preview_textbox->set_text(pangrams[0]); 80 81 auto& reload_button = *find_descendant_of_type_named<GUI::Button>("reload_button"); 82 reload_button.on_click = [&](auto) { 83 static size_t i = 1; 84 if (i >= pangrams.size()) 85 i = 0; 86 m_preview_textbox->set_text(pangrams[i]); 87 i++; 88 }; 89 90 return window; 91} 92 93ErrorOr<void> MainWidget::create_actions() 94{ 95 m_new_action = GUI::Action::create("&New Font...", { Mod_Ctrl, Key_N }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-font.png"sv)), [&](auto&) { 96 if (!request_close()) 97 return; 98 auto new_font_wizard = NewFontDialog::construct(window()); 99 if (new_font_wizard->exec() != GUI::Dialog::ExecResult::OK) 100 return; 101 new_font_wizard->hide(); 102 auto maybe_font = new_font_wizard->create_font(); 103 if (maybe_font.is_error()) 104 return show_error(maybe_font.release_error(), "Creating new font failed"sv); 105 if (auto result = initialize({}, move(maybe_font.value())); result.is_error()) 106 show_error(result.release_error(), "Initializing new font failed"sv); 107 }); 108 m_new_action->set_status_tip("Create a new font"); 109 110 m_open_action = GUI::CommonActions::make_open_action([&](auto&) { 111 if (!request_close()) 112 return; 113 Optional<DeprecatedString> open_path = GUI::FilePicker::get_open_filepath(window(), {}, "/res/fonts/"sv); 114 if (!open_path.has_value()) 115 return; 116 if (auto result = open_file(open_path.value()); result.is_error()) 117 show_error(result.release_error(), "Opening"sv, LexicalPath { open_path.value() }.basename()); 118 }); 119 120 m_save_action = GUI::CommonActions::make_save_action([&](auto&) { 121 if (m_path.is_empty()) 122 return m_save_as_action->activate(); 123 if (auto result = save_file(m_path); result.is_error()) 124 show_error(result.release_error(), "Saving"sv, LexicalPath { m_path }.basename()); 125 }); 126 127 m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) { 128 LexicalPath lexical_path(m_path.is_empty() ? "Untitled.font" : m_path); 129 Optional<DeprecatedString> save_path = GUI::FilePicker::get_save_filepath(window(), lexical_path.title(), lexical_path.extension()); 130 if (!save_path.has_value()) 131 return; 132 if (auto result = save_file(save_path.value()); result.is_error()) 133 show_error(result.release_error(), "Saving"sv, lexical_path.basename()); 134 }); 135 136 m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) { 137 if (auto result = cut_selected_glyphs(); result.is_error()) 138 show_error(result.release_error(), "Cutting selection failed"sv); 139 }); 140 141 m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) { 142 if (auto result = copy_selected_glyphs(); result.is_error()) 143 show_error(result.release_error(), "Copying selection failed"sv); 144 }); 145 146 m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) { 147 paste_glyphs(); 148 }); 149 m_paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "glyph/x-fonteditor"); 150 151 GUI::Clipboard::the().on_change = [&](DeprecatedString const& data_type) { 152 m_paste_action->set_enabled(data_type == "glyph/x-fonteditor"); 153 }; 154 155 m_delete_action = GUI::CommonActions::make_delete_action([this](auto&) { 156 delete_selected_glyphs(); 157 }); 158 159 m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) { 160 undo(); 161 }); 162 m_undo_action->set_enabled(false); 163 164 m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) { 165 redo(); 166 }); 167 m_redo_action->set_enabled(false); 168 169 m_select_all_action = GUI::CommonActions::make_select_all_action([this](auto&) { 170 m_glyph_map_widget->set_selection(m_range.first, m_range.last - m_range.first + 1); 171 m_glyph_map_widget->update(); 172 auto selection = m_glyph_map_widget->selection().normalized(); 173 m_undo_selection->set_start(selection.start()); 174 m_undo_selection->set_size(selection.size()); 175 update_statusbar(); 176 }); 177 178 m_open_preview_action = GUI::Action::create("&Preview Font", { Mod_Ctrl, Key_P }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"sv)), [&](auto&) { 179 if (!m_font_preview_window) { 180 if (auto maybe_window = create_preview_window(); maybe_window.is_error()) 181 show_error(maybe_window.release_error(), "Creating preview window failed"sv); 182 else 183 m_font_preview_window = maybe_window.release_value(); 184 } 185 if (m_font_preview_window) 186 m_font_preview_window->show(); 187 }); 188 m_open_preview_action->set_status_tip("Preview the current font"); 189 190 bool show_metadata = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowMetadata"sv, true); 191 set_show_font_metadata(show_metadata); 192 m_show_metadata_action = GUI::Action::create_checkable("Font &Metadata", { Mod_Ctrl, Key_M }, [&](auto& action) { 193 set_show_font_metadata(action.is_checked()); 194 Config::write_bool("FontEditor"sv, "Layout"sv, "ShowMetadata"sv, action.is_checked()); 195 }); 196 m_show_metadata_action->set_checked(show_metadata); 197 m_show_metadata_action->set_status_tip("Show or hide metadata about the current font"); 198 199 bool show_unicode_blocks = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowUnicodeBlocks"sv, true); 200 set_show_unicode_blocks(show_unicode_blocks); 201 m_show_unicode_blocks_action = GUI::Action::create_checkable("&Unicode Blocks", { Mod_Ctrl, Key_U }, [&](auto& action) { 202 set_show_unicode_blocks(action.is_checked()); 203 Config::write_bool("FontEditor"sv, "Layout"sv, "ShowUnicodeBlocks"sv, action.is_checked()); 204 }); 205 m_show_unicode_blocks_action->set_checked(show_unicode_blocks); 206 m_show_unicode_blocks_action->set_status_tip("Show or hide the Unicode block list"); 207 208 bool show_toolbar = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowToolbar"sv, true); 209 set_show_toolbar(show_toolbar); 210 m_show_toolbar_action = GUI::Action::create_checkable("&Toolbar", [&](auto& action) { 211 set_show_toolbar(action.is_checked()); 212 Config::write_bool("FontEditor"sv, "Layout"sv, "ShowToolbar"sv, action.is_checked()); 213 }); 214 m_show_toolbar_action->set_checked(show_toolbar); 215 m_show_toolbar_action->set_status_tip("Show or hide the toolbar"); 216 217 bool show_statusbar = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowStatusbar"sv, true); 218 set_show_statusbar(show_statusbar); 219 m_show_statusbar_action = GUI::Action::create_checkable("&Status Bar", [&](auto& action) { 220 set_show_statusbar(action.is_checked()); 221 Config::write_bool("FontEditor"sv, "Layout"sv, "ShowStatusbar"sv, action.is_checked()); 222 }); 223 m_show_statusbar_action->set_checked(show_statusbar); 224 m_show_statusbar_action->set_status_tip("Show or hide the status bar"); 225 226 bool highlight_modifications = Config::read_bool("FontEditor"sv, "GlyphMap"sv, "HighlightModifications"sv, true); 227 set_highlight_modifications(highlight_modifications); 228 m_highlight_modifications_action = GUI::Action::create_checkable("&Highlight Modifications", { Mod_Ctrl, Key_H }, [&](auto& action) { 229 set_highlight_modifications(action.is_checked()); 230 Config::write_bool("FontEditor"sv, "GlyphMap"sv, "HighlightModifications"sv, action.is_checked()); 231 }); 232 m_highlight_modifications_action->set_checked(highlight_modifications); 233 m_highlight_modifications_action->set_status_tip("Show or hide highlights on modified glyphs. (Green = New, Blue = Modified, Red = Deleted)"); 234 235 bool show_system_emoji = Config::read_bool("FontEditor"sv, "GlyphMap"sv, "ShowSystemEmoji"sv, true); 236 set_show_system_emoji(show_system_emoji); 237 m_show_system_emoji_action = GUI::Action::create_checkable("System &Emoji", { Mod_Ctrl, Key_E }, [&](auto& action) { 238 set_show_system_emoji(action.is_checked()); 239 Config::write_bool("FontEditor"sv, "GlyphMap"sv, "ShowSystemEmoji"sv, action.is_checked()); 240 }); 241 m_show_system_emoji_action->set_checked(show_system_emoji); 242 m_show_system_emoji_action->set_status_tip("Show or hide system emoji"); 243 244 m_go_to_glyph_action = GUI::Action::create("&Go to Glyph...", { Mod_Ctrl, Key_G }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-to.png"sv)), [&](auto&) { 245 DeprecatedString input; 246 if (GUI::InputBox::show(window(), input, "Hexadecimal:"sv, "Go to glyph"sv, GUI::InputType::NonemptyText) == GUI::InputBox::ExecResult::OK) { 247 auto maybe_code_point = AK::StringUtils::convert_to_uint_from_hex(input); 248 if (!maybe_code_point.has_value()) 249 return; 250 auto code_point = maybe_code_point.value(); 251 code_point = clamp(code_point, m_range.first, m_range.last); 252 m_glyph_map_widget->set_focus(true); 253 m_glyph_map_widget->set_active_glyph(code_point); 254 m_glyph_map_widget->scroll_to_glyph(code_point); 255 } 256 }); 257 m_go_to_glyph_action->set_status_tip("Go to the specified code point"); 258 259 m_previous_glyph_action = GUI::Action::create("Pre&vious Glyph", { Mod_Alt, Key_Left }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv)), [&](auto&) { 260 m_glyph_map_widget->select_previous_existing_glyph(); 261 }); 262 m_previous_glyph_action->set_status_tip("Seek the previous visible glyph"); 263 264 m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv)), [&](auto&) { 265 m_glyph_map_widget->select_next_existing_glyph(); 266 }); 267 m_next_glyph_action->set_status_tip("Seek the next visible glyph"); 268 269 i32 scale = Config::read_i32("FontEditor"sv, "GlyphEditor"sv, "Scale"sv, 10); 270 m_glyph_editor_widget->set_scale(scale); 271 m_scale_five_action = GUI::Action::create_checkable("500%", { Mod_Ctrl, Key_1 }, [this](auto&) { 272 set_scale_and_save(5); 273 }); 274 m_scale_five_action->set_checked(scale == 5); 275 m_scale_five_action->set_status_tip("Scale the editor in proportion to the current font"); 276 m_scale_ten_action = GUI::Action::create_checkable("1000%", { Mod_Ctrl, Key_2 }, [this](auto&) { 277 set_scale_and_save(10); 278 }); 279 m_scale_ten_action->set_checked(scale == 10); 280 m_scale_ten_action->set_status_tip("Scale the editor in proportion to the current font"); 281 m_scale_fifteen_action = GUI::Action::create_checkable("1500%", { Mod_Ctrl, Key_3 }, [this](auto&) { 282 set_scale_and_save(15); 283 }); 284 m_scale_fifteen_action->set_checked(scale == 15); 285 m_scale_fifteen_action->set_status_tip("Scale the editor in proportion to the current font"); 286 287 m_glyph_editor_scale_actions.add_action(*m_scale_five_action); 288 m_glyph_editor_scale_actions.add_action(*m_scale_ten_action); 289 m_glyph_editor_scale_actions.add_action(*m_scale_fifteen_action); 290 m_glyph_editor_scale_actions.set_exclusive(true); 291 292 m_paint_glyph_action = GUI::Action::create_checkable("Paint Glyph", { Mod_Ctrl, KeyCode::Key_J }, TRY(Gfx::Bitmap::load_from_file("/res/icons/pixelpaint/pen.png"sv)), [&](auto&) { 293 m_glyph_editor_widget->set_mode(GlyphEditorWidget::Paint); 294 }); 295 m_paint_glyph_action->set_checked(true); 296 297 m_move_glyph_action = GUI::Action::create_checkable("Move Glyph", { Mod_Ctrl, KeyCode::Key_K }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/selection-move.png"sv)), [&](auto&) { 298 m_glyph_editor_widget->set_mode(GlyphEditorWidget::Move); 299 }); 300 301 m_glyph_tool_actions.add_action(*m_paint_glyph_action); 302 m_glyph_tool_actions.add_action(*m_move_glyph_action); 303 m_glyph_tool_actions.set_exclusive(true); 304 305 m_rotate_counterclockwise_action = GUI::CommonActions::make_rotate_counterclockwise_action([&](auto&) { 306 m_glyph_editor_widget->rotate_90(Gfx::RotationDirection::CounterClockwise); 307 }); 308 309 m_rotate_clockwise_action = GUI::CommonActions::make_rotate_clockwise_action([&](auto&) { 310 m_glyph_editor_widget->rotate_90(Gfx::RotationDirection::Clockwise); 311 }); 312 313 m_flip_horizontal_action = GUI::Action::create("Flip Horizontally", { Mod_Ctrl | Mod_Shift, Key_Q }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-flip-horizontal.png"sv)), [&](auto&) { 314 m_glyph_editor_widget->flip(Gfx::Orientation::Horizontal); 315 }); 316 317 m_flip_vertical_action = GUI::Action::create("Flip Vertically", { Mod_Ctrl | Mod_Shift, Key_W }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-flip-vertical.png"sv)), [&](auto&) { 318 m_glyph_editor_widget->flip(Gfx::Orientation::Vertical); 319 }); 320 321 m_copy_text_action = GUI::Action::create("Copy as Te&xt", { Mod_Ctrl, Key_T }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"sv)), [&](auto&) { 322 StringBuilder builder; 323 auto selection = m_glyph_map_widget->selection().normalized(); 324 for (auto code_point = selection.start(); code_point < selection.start() + selection.size(); ++code_point) { 325 if (!m_glyph_map_widget->font().contains_glyph(code_point)) 326 continue; 327 builder.append_code_point(code_point); 328 } 329 GUI::Clipboard::the().set_plain_text(builder.to_deprecated_string()); 330 }); 331 m_copy_text_action->set_status_tip("Copy to clipboard as text"); 332 333 return {}; 334} 335 336ErrorOr<void> MainWidget::create_toolbars() 337{ 338 auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar"); 339 (void)TRY(toolbar.try_add_action(*m_new_action)); 340 (void)TRY(toolbar.try_add_action(*m_open_action)); 341 (void)TRY(toolbar.try_add_action(*m_save_action)); 342 TRY(toolbar.try_add_separator()); 343 (void)TRY(toolbar.try_add_action(*m_cut_action)); 344 (void)TRY(toolbar.try_add_action(*m_copy_action)); 345 (void)TRY(toolbar.try_add_action(*m_paste_action)); 346 (void)TRY(toolbar.try_add_action(*m_delete_action)); 347 TRY(toolbar.try_add_separator()); 348 (void)TRY(toolbar.try_add_action(*m_undo_action)); 349 (void)TRY(toolbar.try_add_action(*m_redo_action)); 350 TRY(toolbar.try_add_separator()); 351 (void)TRY(toolbar.try_add_action(*m_open_preview_action)); 352 TRY(toolbar.try_add_separator()); 353 (void)TRY(toolbar.try_add_action(*m_previous_glyph_action)); 354 (void)TRY(toolbar.try_add_action(*m_next_glyph_action)); 355 (void)TRY(toolbar.try_add_action(*m_go_to_glyph_action)); 356 357 auto& glyph_transform_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_transform_toolbar"); 358 (void)TRY(glyph_transform_toolbar.try_add_action(*m_flip_horizontal_action)); 359 (void)TRY(glyph_transform_toolbar.try_add_action(*m_flip_vertical_action)); 360 (void)TRY(glyph_transform_toolbar.try_add_action(*m_rotate_counterclockwise_action)); 361 (void)TRY(glyph_transform_toolbar.try_add_action(*m_rotate_clockwise_action)); 362 363 auto& glyph_mode_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_mode_toolbar"); 364 (void)TRY(glyph_mode_toolbar.try_add_action(*m_paint_glyph_action)); 365 (void)TRY(glyph_mode_toolbar.try_add_action(*m_move_glyph_action)); 366 367 return {}; 368} 369 370ErrorOr<void> MainWidget::create_models() 371{ 372 for (auto& it : Gfx::font_slope_names) 373 TRY(m_font_slope_list.try_append(it.name)); 374 m_slope_combobox->set_model(GUI::ItemListModel<DeprecatedString>::create(m_font_slope_list)); 375 376 for (auto& it : Gfx::font_weight_names) 377 TRY(m_font_weight_list.try_append(it.name)); 378 m_weight_combobox->set_model(GUI::ItemListModel<DeprecatedString>::create(m_font_weight_list)); 379 380 auto unicode_blocks = Unicode::block_display_names(); 381 TRY(m_unicode_block_list.try_append("Show All")); 382 for (auto& block : unicode_blocks) 383 TRY(m_unicode_block_list.try_append(block.display_name)); 384 385 m_unicode_block_model = GUI::ItemListModel<DeprecatedString>::create(m_unicode_block_list); 386 m_filter_model = TRY(GUI::FilteringProxyModel::create(*m_unicode_block_model)); 387 m_filter_model->set_filter_term(""sv); 388 389 m_unicode_block_listview = find_descendant_of_type_named<GUI::ListView>("unicode_block_listview"); 390 m_unicode_block_listview->on_selection_change = [this, unicode_blocks] { 391 auto index = m_unicode_block_listview->selection().first(); 392 auto mapped_index = m_filter_model->map(index); 393 if (mapped_index.row() > 0) 394 m_range = unicode_blocks[mapped_index.row() - 1].code_point_range; 395 else 396 m_range = { 0x0000, 0x10FFFF }; 397 m_glyph_map_widget->set_active_range(m_range); 398 }; 399 m_unicode_block_listview->set_model(m_filter_model); 400 m_unicode_block_listview->set_activates_on_selection(true); 401 m_unicode_block_listview->horizontal_scrollbar().set_visible(false); 402 m_unicode_block_listview->set_cursor(m_unicode_block_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set); 403 m_unicode_block_listview->set_focus_proxy(m_search_textbox); 404 405 return {}; 406} 407 408ErrorOr<void> MainWidget::create_undo_stack() 409{ 410 m_undo_stack = TRY(try_make<GUI::UndoStack>()); 411 m_undo_stack->on_state_change = [this] { 412 m_undo_action->set_enabled(m_undo_stack->can_undo()); 413 m_redo_action->set_enabled(m_undo_stack->can_redo()); 414 if (m_undo_stack->is_current_modified()) 415 did_modify_font(); 416 }; 417 418 return {}; 419} 420 421MainWidget::MainWidget() 422{ 423 load_from_gml(font_editor_window_gml).release_value_but_fixme_should_propagate_errors(); 424 425 m_font_metadata_groupbox = find_descendant_of_type_named<GUI::GroupBox>("font_metadata_groupbox"); 426 m_unicode_block_container = find_descendant_of_type_named<GUI::Widget>("unicode_block_container"); 427 m_toolbar_container = find_descendant_of_type_named<GUI::ToolbarContainer>("toolbar_container"); 428 429 m_glyph_map_widget = find_descendant_of_type_named<GUI::GlyphMapWidget>("glyph_map_widget"); 430 m_glyph_editor_widget = find_descendant_of_type_named<GlyphEditorWidget>("glyph_editor_widget"); 431 m_glyph_editor_widget->on_glyph_altered = [this](int glyph) { 432 m_glyph_map_widget->update_glyph(glyph); 433 update_preview(); 434 did_modify_font(); 435 }; 436 437 m_glyph_editor_widget->on_undo_event = [this] { 438 reset_selection_and_push_undo(); 439 }; 440 441 m_glyph_editor_width_spinbox = find_descendant_of_type_named<GUI::SpinBox>("glyph_editor_width_spinbox"); 442 m_glyph_editor_present_checkbox = find_descendant_of_type_named<GUI::CheckBox>("glyph_editor_present_checkbox"); 443 m_glyph_map_widget->on_active_glyph_changed = [this](int glyph) { 444 if (m_undo_selection) { 445 auto selection = m_glyph_map_widget->selection().normalized(); 446 m_undo_selection->set_start(selection.start()); 447 m_undo_selection->set_size(selection.size()); 448 m_undo_selection->set_active_glyph(glyph); 449 } 450 m_glyph_editor_widget->set_glyph(glyph); 451 auto glyph_width = m_edited_font->raw_glyph_width(glyph); 452 if (m_edited_font->is_fixed_width()) 453 m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No); 454 else 455 m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No); 456 update_statusbar(); 457 }; 458 459 m_glyph_map_widget->on_context_menu_request = [this](auto& event) { 460 m_context_menu->popup(event.screen_position()); 461 }; 462 463 m_glyph_map_widget->on_escape_pressed = [this]() { 464 update_statusbar(); 465 }; 466 467 m_name_textbox = find_descendant_of_type_named<GUI::TextBox>("name_textbox"); 468 m_name_textbox->on_change = [&] { 469 m_edited_font->set_name(m_name_textbox->text()); 470 did_modify_font(); 471 }; 472 473 m_family_textbox = find_descendant_of_type_named<GUI::TextBox>("family_textbox"); 474 m_family_textbox->on_change = [&] { 475 m_edited_font->set_family(m_family_textbox->text()); 476 did_modify_font(); 477 }; 478 479 m_fixed_width_checkbox = find_descendant_of_type_named<GUI::CheckBox>("fixed_width_checkbox"); 480 m_fixed_width_checkbox->on_checked = [this](bool checked) { 481 m_edited_font->set_fixed_width(checked); 482 auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()); 483 m_glyph_editor_width_spinbox->set_visible(!checked); 484 m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No); 485 m_glyph_editor_present_checkbox->set_visible(checked); 486 m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No); 487 m_glyph_editor_widget->update(); 488 update_preview(); 489 did_modify_font(); 490 }; 491 492 m_glyph_editor_width_spinbox->on_change = [this](int value) { 493 reset_selection_and_push_undo(); 494 m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), value); 495 m_glyph_editor_widget->update(); 496 m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph()); 497 update_preview(); 498 update_statusbar(); 499 did_modify_font(); 500 }; 501 502 m_glyph_editor_present_checkbox->on_checked = [this](bool checked) { 503 reset_selection_and_push_undo(); 504 m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0); 505 m_glyph_editor_widget->update(); 506 m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph()); 507 update_preview(); 508 update_statusbar(); 509 did_modify_font(); 510 }; 511 512 m_weight_combobox = find_descendant_of_type_named<GUI::ComboBox>("weight_combobox"); 513 m_weight_combobox->on_change = [this](auto&, auto&) { 514 m_edited_font->set_weight(Gfx::name_to_weight(m_weight_combobox->text())); 515 did_modify_font(); 516 }; 517 518 m_slope_combobox = find_descendant_of_type_named<GUI::ComboBox>("slope_combobox"); 519 m_slope_combobox->on_change = [this](auto&, auto&) { 520 m_edited_font->set_slope(Gfx::name_to_slope(m_slope_combobox->text())); 521 did_modify_font(); 522 }; 523 524 m_presentation_spinbox = find_descendant_of_type_named<GUI::SpinBox>("presentation_spinbox"); 525 m_presentation_spinbox->on_change = [this](int value) { 526 m_edited_font->set_presentation_size(value); 527 update_preview(); 528 did_modify_font(); 529 }; 530 531 m_spacing_spinbox = find_descendant_of_type_named<GUI::SpinBox>("spacing_spinbox"); 532 m_spacing_spinbox->on_change = [this](int value) { 533 m_edited_font->set_glyph_spacing(value); 534 update_preview(); 535 did_modify_font(); 536 }; 537 538 m_baseline_spinbox = find_descendant_of_type_named<GUI::SpinBox>("baseline_spinbox"); 539 m_baseline_spinbox->on_change = [this](int value) { 540 m_edited_font->set_baseline(value); 541 m_glyph_editor_widget->update(); 542 update_preview(); 543 did_modify_font(); 544 }; 545 546 m_mean_line_spinbox = find_descendant_of_type_named<GUI::SpinBox>("mean_line_spinbox"); 547 m_mean_line_spinbox->on_change = [this](int value) { 548 m_edited_font->set_mean_line(value); 549 m_glyph_editor_widget->update(); 550 update_preview(); 551 did_modify_font(); 552 }; 553 554 m_search_textbox = find_descendant_of_type_named<GUI::TextBox>("search_textbox"); 555 m_search_textbox->on_return_pressed = [this] { 556 if (!m_unicode_block_listview->selection().is_empty()) 557 m_unicode_block_listview->activate_selected(); 558 }; 559 560 m_search_textbox->on_down_pressed = [this] { 561 m_unicode_block_listview->move_cursor(GUI::AbstractView::CursorMovement::Down, GUI::AbstractView::SelectionUpdate::Set); 562 }; 563 564 m_search_textbox->on_up_pressed = [this] { 565 m_unicode_block_listview->move_cursor(GUI::AbstractView::CursorMovement::Up, GUI::AbstractView::SelectionUpdate::Set); 566 }; 567 568 m_search_textbox->on_change = [this] { 569 m_filter_model->set_filter_term(m_search_textbox->text()); 570 if (m_filter_model->row_count() != 0) 571 m_unicode_block_listview->set_cursor(m_filter_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set); 572 }; 573 574 m_statusbar = find_descendant_of_type_named<GUI::Statusbar>("statusbar"); 575 m_statusbar->segment(1).set_mode(GUI::Statusbar::Segment::Mode::Auto); 576 m_statusbar->segment(1).set_clickable(true); 577 m_statusbar->segment(1).on_click = [&](auto) { 578 m_show_unicode_blocks_action->activate(); 579 }; 580 581 GUI::Application::the()->on_action_enter = [this](GUI::Action& action) { 582 auto text = action.status_tip(); 583 if (text.is_empty()) 584 text = Gfx::parse_ampersand_string(action.text()); 585 m_statusbar->set_override_text(move(text)); 586 }; 587 588 GUI::Application::the()->on_action_leave = [this](GUI::Action&) { 589 m_statusbar->set_override_text({}); 590 }; 591} 592 593ErrorOr<void> MainWidget::initialize(DeprecatedString const& path, RefPtr<Gfx::BitmapFont>&& edited_font) 594{ 595 if (m_edited_font == edited_font) 596 return {}; 597 598 TRY(m_glyph_map_widget->set_font(*edited_font)); 599 600 auto selection = m_glyph_map_widget->selection().normalized(); 601 m_undo_selection = TRY(try_make_ref_counted<UndoSelection>(selection.start(), selection.size(), m_glyph_map_widget->active_glyph(), *edited_font, *m_glyph_map_widget)); 602 m_undo_stack->clear(); 603 604 m_path = path; 605 m_edited_font = edited_font; 606 607 if (m_preview_label) 608 m_preview_label->set_font(*m_edited_font); 609 610 m_glyph_editor_widget->set_font(*m_edited_font); 611 m_glyph_editor_widget->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height()); 612 613 m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width()); 614 m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width(), GUI::AllowCallback::No); 615 m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No); 616 617 m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width()); 618 m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No); 619 m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width(), GUI::AllowCallback::No); 620 621 m_name_textbox->set_text(m_edited_font->name(), GUI::AllowCallback::No); 622 m_family_textbox->set_text(m_edited_font->family(), GUI::AllowCallback::No); 623 624 m_presentation_spinbox->set_value(m_edited_font->presentation_size(), GUI::AllowCallback::No); 625 m_spacing_spinbox->set_value(m_edited_font->glyph_spacing(), GUI::AllowCallback::No); 626 627 m_mean_line_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No); 628 m_baseline_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No); 629 m_mean_line_spinbox->set_value(m_edited_font->mean_line(), GUI::AllowCallback::No); 630 m_baseline_spinbox->set_value(m_edited_font->baseline(), GUI::AllowCallback::No); 631 632 int i = 0; 633 for (auto& it : Gfx::font_weight_names) { 634 if (it.style == m_edited_font->weight()) { 635 m_weight_combobox->set_selected_index(i, GUI::AllowCallback::No); 636 break; 637 } 638 i++; 639 } 640 i = 0; 641 for (auto& it : Gfx::font_slope_names) { 642 if (it.style == m_edited_font->slope()) { 643 m_slope_combobox->set_selected_index(i, GUI::AllowCallback::No); 644 break; 645 } 646 i++; 647 } 648 649 update_statusbar(); 650 651 deferred_invoke([this] { 652 auto glyph = m_glyph_map_widget->active_glyph(); 653 m_glyph_map_widget->set_focus(true); 654 m_glyph_map_widget->scroll_to_glyph(glyph); 655 m_glyph_editor_widget->set_glyph(glyph); 656 657 VERIFY(window()); 658 window()->set_modified(false); 659 update_title(); 660 }); 661 662 return {}; 663} 664 665ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window) 666{ 667 auto file_menu = TRY(window.try_add_menu("&File")); 668 TRY(file_menu->try_add_action(*m_new_action)); 669 TRY(file_menu->try_add_action(*m_open_action)); 670 TRY(file_menu->try_add_action(*m_save_action)); 671 TRY(file_menu->try_add_action(*m_save_as_action)); 672 TRY(file_menu->try_add_separator()); 673 TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([this](auto&) { 674 if (!request_close()) 675 return; 676 GUI::Application::the()->quit(); 677 }))); 678 679 auto edit_menu = TRY(window.try_add_menu("&Edit")); 680 TRY(edit_menu->try_add_action(*m_undo_action)); 681 TRY(edit_menu->try_add_action(*m_redo_action)); 682 TRY(edit_menu->try_add_separator()); 683 TRY(edit_menu->try_add_action(*m_cut_action)); 684 TRY(edit_menu->try_add_action(*m_copy_action)); 685 TRY(edit_menu->try_add_action(*m_paste_action)); 686 TRY(edit_menu->try_add_action(*m_delete_action)); 687 TRY(edit_menu->try_add_separator()); 688 TRY(edit_menu->try_add_action(*m_select_all_action)); 689 TRY(edit_menu->try_add_separator()); 690 TRY(edit_menu->try_add_action(*m_copy_text_action)); 691 692 m_context_menu = edit_menu; 693 694 auto go_menu = TRY(window.try_add_menu("&Go")); 695 TRY(go_menu->try_add_action(*m_previous_glyph_action)); 696 TRY(go_menu->try_add_action(*m_next_glyph_action)); 697 TRY(go_menu->try_add_action(*m_go_to_glyph_action)); 698 699 auto view_menu = TRY(window.try_add_menu("&View")); 700 auto layout_menu = TRY(view_menu->try_add_submenu("&Layout")); 701 TRY(layout_menu->try_add_action(*m_show_toolbar_action)); 702 TRY(layout_menu->try_add_action(*m_show_statusbar_action)); 703 TRY(layout_menu->try_add_action(*m_show_metadata_action)); 704 TRY(layout_menu->try_add_action(*m_show_unicode_blocks_action)); 705 TRY(view_menu->try_add_separator()); 706 TRY(view_menu->try_add_action(*m_open_preview_action)); 707 TRY(view_menu->try_add_separator()); 708 TRY(view_menu->try_add_action(*m_highlight_modifications_action)); 709 TRY(view_menu->try_add_action(*m_show_system_emoji_action)); 710 TRY(view_menu->try_add_separator()); 711 auto scale_menu = TRY(view_menu->try_add_submenu("&Scale")); 712 scale_menu->set_icon(TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/scale.png"sv))); 713 TRY(scale_menu->try_add_action(*m_scale_five_action)); 714 TRY(scale_menu->try_add_action(*m_scale_ten_action)); 715 TRY(scale_menu->try_add_action(*m_scale_fifteen_action)); 716 717 auto help_menu = TRY(window.try_add_menu("&Help")); 718 TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(&window))); 719 TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) { 720 Desktop::Launcher::open(URL::create_with_file_scheme("/usr/share/man/man1/FontEditor.md"), "/bin/Help"); 721 }))); 722 TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Font Editor", TRY(GUI::Icon::try_create_default_icon("app-font-editor"sv)), &window))); 723 724 return {}; 725} 726 727ErrorOr<void> MainWidget::save_file(DeprecatedString const& path) 728{ 729 auto masked_font = TRY(m_edited_font->masked_character_set()); 730 TRY(masked_font->write_to_file(path)); 731 m_path = path; 732 m_undo_stack->set_current_unmodified(); 733 window()->set_modified(false); 734 update_title(); 735 return {}; 736} 737 738void MainWidget::set_show_toolbar(bool show) 739{ 740 if (m_toolbar_container->is_visible() == show) 741 return; 742 m_toolbar_container->set_visible(show); 743} 744 745void MainWidget::set_show_statusbar(bool show) 746{ 747 if (m_statusbar->is_visible() == show) 748 return; 749 m_statusbar->set_visible(show); 750 if (show) 751 update_statusbar(); 752} 753 754void MainWidget::set_show_font_metadata(bool show) 755{ 756 if (m_font_metadata == show) 757 return; 758 m_font_metadata = show; 759 m_font_metadata_groupbox->set_visible(m_font_metadata); 760} 761 762void MainWidget::set_show_unicode_blocks(bool show) 763{ 764 if (m_unicode_blocks == show) 765 return; 766 m_unicode_blocks = show; 767 m_unicode_block_container->set_visible(m_unicode_blocks); 768 if (show) 769 m_search_textbox->set_focus(true); 770 else 771 m_glyph_map_widget->set_focus(true); 772} 773 774void MainWidget::set_highlight_modifications(bool highlight_modifications) 775{ 776 m_glyph_map_widget->set_highlight_modifications(highlight_modifications); 777} 778 779void MainWidget::set_show_system_emoji(bool show) 780{ 781 m_glyph_map_widget->set_show_system_emoji(show); 782} 783 784ErrorOr<void> MainWidget::open_file(DeprecatedString const& path) 785{ 786 auto unmasked_font = TRY(TRY(Gfx::BitmapFont::try_load_from_file(path))->unmasked_character_set()); 787 TRY(initialize(path, move(unmasked_font))); 788 return {}; 789} 790 791void MainWidget::push_undo() 792{ 793 auto maybe_state = m_undo_selection->save_state(); 794 if (maybe_state.is_error()) 795 return show_error(maybe_state.release_error(), "Saving undo state failed"sv); 796 auto maybe_command = try_make<SelectionUndoCommand>(*m_undo_selection, move(maybe_state.value())); 797 if (maybe_command.is_error()) 798 return show_error(maybe_command.release_error(), "Making undo command failed"sv); 799 if (auto maybe_push = m_undo_stack->try_push(move(maybe_command.value())); maybe_push.is_error()) 800 show_error(maybe_push.release_error(), "Pushing undo stack failed"sv); 801} 802 803void MainWidget::reset_selection_and_push_undo() 804{ 805 auto selection = m_glyph_map_widget->selection().normalized(); 806 if (selection.size() != 1) { 807 auto start = m_glyph_map_widget->active_glyph(); 808 m_undo_selection->set_start(start); 809 m_undo_selection->set_size(1); 810 m_glyph_map_widget->set_selection(start, 1); 811 m_glyph_map_widget->update(); 812 } 813 push_undo(); 814} 815 816void MainWidget::undo() 817{ 818 if (!m_undo_stack->can_undo()) 819 return; 820 m_undo_stack->undo(); 821 822 auto glyph = m_undo_selection->restored_active_glyph(); 823 auto glyph_width = edited_font().raw_glyph_width(glyph); 824 if (glyph < m_range.first || glyph > m_range.last) 825 m_search_textbox->set_text(""sv); 826 827 deferred_invoke([this, glyph] { 828 auto start = m_undo_selection->restored_start(); 829 auto size = m_undo_selection->restored_size(); 830 m_glyph_map_widget->restore_selection(start, size, glyph); 831 m_glyph_map_widget->scroll_to_glyph(glyph); 832 m_glyph_map_widget->set_focus(true); 833 }); 834 835 if (m_edited_font->is_fixed_width()) { 836 m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No); 837 } else { 838 m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No); 839 } 840 m_glyph_editor_widget->update(); 841 m_glyph_map_widget->update(); 842 update_preview(); 843 update_statusbar(); 844} 845 846void MainWidget::redo() 847{ 848 if (!m_undo_stack->can_redo()) 849 return; 850 m_undo_stack->redo(); 851 852 auto glyph = m_undo_selection->restored_active_glyph(); 853 auto glyph_width = edited_font().raw_glyph_width(glyph); 854 if (glyph < m_range.first || glyph > m_range.last) 855 m_search_textbox->set_text(""sv); 856 857 deferred_invoke([this, glyph] { 858 auto start = m_undo_selection->restored_start(); 859 auto size = m_undo_selection->restored_size(); 860 m_glyph_map_widget->restore_selection(start, size, glyph); 861 m_glyph_map_widget->scroll_to_glyph(glyph); 862 m_glyph_map_widget->set_focus(true); 863 }); 864 865 if (m_edited_font->is_fixed_width()) { 866 m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No); 867 } else { 868 m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No); 869 } 870 m_glyph_editor_widget->update(); 871 m_glyph_map_widget->update(); 872 update_preview(); 873 update_statusbar(); 874} 875 876bool MainWidget::request_close() 877{ 878 if (!window()->is_modified()) 879 return true; 880 auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), m_path, m_undo_stack->last_unmodified_timestamp()); 881 if (result == GUI::MessageBox::ExecResult::Yes) { 882 m_save_action->activate(); 883 if (!window()->is_modified()) 884 return true; 885 } 886 if (result == GUI::MessageBox::ExecResult::No) 887 return true; 888 return false; 889} 890 891void MainWidget::update_title() 892{ 893 StringBuilder title; 894 if (m_path.is_empty()) 895 title.append("Untitled"sv); 896 else 897 title.append(m_path); 898 title.append("[*] - Font Editor"sv); 899 window()->set_title(title.to_deprecated_string()); 900} 901 902void MainWidget::did_modify_font() 903{ 904 if (!window() || window()->is_modified()) 905 return; 906 window()->set_modified(true); 907 update_title(); 908} 909 910void MainWidget::update_statusbar() 911{ 912 if (!m_statusbar->is_visible()) 913 return; 914 915 auto glyph = m_glyph_map_widget->active_glyph(); 916 StringBuilder builder; 917 builder.appendff("U+{:04X} (", glyph); 918 919 if (auto abbreviation = Unicode::code_point_abbreviation(glyph); abbreviation.has_value()) { 920 builder.append(*abbreviation); 921 } else if (Gfx::get_char_bidi_class(glyph) == Gfx::BidirectionalClass::STRONG_RTL) { 922 // FIXME: This is a necessary hack, as RTL text will mess up the painting of the statusbar text. 923 // For now, replace RTL glyphs with U+FFFD, the replacement character. 924 builder.append_code_point(0xFFFD); 925 } else { 926 builder.append_code_point(glyph); 927 } 928 929 builder.append(')'); 930 931 auto glyph_name = Unicode::code_point_display_name(glyph); 932 if (glyph_name.has_value()) { 933 builder.appendff(" {}", glyph_name.value()); 934 } 935 936 if (m_edited_font->contains_raw_glyph(glyph)) 937 builder.appendff(" [{}x{}]", m_edited_font->raw_glyph_width(glyph), m_edited_font->glyph_height()); 938 else if (Gfx::Emoji::emoji_for_code_point(glyph)) 939 builder.appendff(" [emoji]"); 940 m_statusbar->set_text(builder.to_deprecated_string()); 941 942 builder.clear(); 943 944 auto selection = m_glyph_map_widget->selection().normalized(); 945 if (selection.size() > 1) 946 builder.appendff("{} glyphs selected", selection.size()); 947 else 948 builder.appendff("U+{:04X}-U+{:04X}", m_range.first, m_range.last); 949 m_statusbar->set_text(1, builder.to_deprecated_string()); 950} 951 952void MainWidget::update_preview() 953{ 954 if (m_font_preview_window) 955 m_font_preview_window->update(); 956} 957 958void MainWidget::drag_enter_event(GUI::DragEvent& event) 959{ 960 auto const& mime_types = event.mime_types(); 961 if (mime_types.contains_slow("text/uri-list")) 962 event.accept(); 963} 964 965void MainWidget::drop_event(GUI::DropEvent& event) 966{ 967 event.accept(); 968 969 if (event.mime_data().has_urls()) { 970 auto urls = event.mime_data().urls(); 971 if (urls.is_empty()) 972 return; 973 974 window()->move_to_front(); 975 if (!request_close()) 976 return; 977 978 if (auto result = open_file(urls.first().path()); result.is_error()) 979 show_error(result.release_error(), "Opening"sv, LexicalPath { urls.first().path() }.basename()); 980 } 981} 982 983void MainWidget::set_scale_and_save(i32 scale) 984{ 985 Config::write_i32("FontEditor"sv, "GlyphEditor"sv, "Scale"sv, scale); 986 m_glyph_editor_widget->set_scale(scale); 987 m_glyph_editor_widget->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height()); 988} 989 990ErrorOr<void> MainWidget::copy_selected_glyphs() 991{ 992 size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * edited_font().glyph_height(); 993 auto selection = m_glyph_map_widget->selection().normalized(); 994 auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph; 995 auto* widths = m_edited_font->widths() + selection.start(); 996 997 ByteBuffer buffer; 998 TRY(buffer.try_append(rows, bytes_per_glyph * selection.size())); 999 TRY(buffer.try_append(widths, selection.size())); 1000 1001 HashMap<DeprecatedString, DeprecatedString> metadata; 1002 metadata.set("start", DeprecatedString::number(selection.start())); 1003 metadata.set("count", DeprecatedString::number(selection.size())); 1004 metadata.set("width", DeprecatedString::number(edited_font().max_glyph_width())); 1005 metadata.set("height", DeprecatedString::number(edited_font().glyph_height())); 1006 GUI::Clipboard::the().set_data(buffer.bytes(), "glyph/x-fonteditor", metadata); 1007 1008 return {}; 1009} 1010 1011ErrorOr<void> MainWidget::cut_selected_glyphs() 1012{ 1013 TRY(copy_selected_glyphs()); 1014 delete_selected_glyphs(); 1015 return {}; 1016} 1017 1018void MainWidget::paste_glyphs() 1019{ 1020 auto [data, mime_type, metadata] = GUI::Clipboard::the().fetch_data_and_type(); 1021 if (!mime_type.starts_with("glyph/"sv)) 1022 return; 1023 1024 auto glyph_count = metadata.get("count").value().to_uint().value_or(0); 1025 if (!glyph_count) 1026 return; 1027 1028 auto height = metadata.get("height").value().to_uint().value_or(0); 1029 if (!height) 1030 return; 1031 1032 auto selection = m_glyph_map_widget->selection().normalized(); 1033 auto range_bound_glyph_count = min(glyph_count, 1 + m_range.last - selection.start()); 1034 m_undo_selection->set_size(range_bound_glyph_count); 1035 push_undo(); 1036 1037 size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * edited_font().glyph_height(); 1038 size_t bytes_per_copied_glyph = Gfx::GlyphBitmap::bytes_per_row() * height; 1039 size_t copyable_bytes_per_glyph = min(bytes_per_glyph, bytes_per_copied_glyph); 1040 auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph; 1041 auto* widths = m_edited_font->widths() + selection.start(); 1042 1043 for (size_t i = 0; i < range_bound_glyph_count; ++i) { 1044 auto copyable_width = edited_font().is_fixed_width() 1045 ? data[bytes_per_copied_glyph * glyph_count + i] ? edited_font().glyph_fixed_width() : 0 1046 : min(edited_font().max_glyph_width(), data[bytes_per_copied_glyph * glyph_count + i]); 1047 memcpy(&rows[i * bytes_per_glyph], &data[i * bytes_per_copied_glyph], copyable_bytes_per_glyph); 1048 memset(&widths[i], copyable_width, sizeof(u8)); 1049 m_glyph_map_widget->set_glyph_modified(selection.start() + i, true); 1050 } 1051 1052 m_glyph_map_widget->set_selection(selection.start() + range_bound_glyph_count - 1, -range_bound_glyph_count + 1); 1053 1054 if (m_edited_font->is_fixed_width()) 1055 m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No); 1056 else 1057 m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No); 1058 1059 m_glyph_editor_widget->update(); 1060 m_glyph_map_widget->update(); 1061 update_preview(); 1062 update_statusbar(); 1063} 1064 1065void MainWidget::delete_selected_glyphs() 1066{ 1067 push_undo(); 1068 1069 auto selection = m_glyph_map_widget->selection().normalized(); 1070 size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * m_edited_font->glyph_height(); 1071 auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph; 1072 auto* widths = m_edited_font->widths() + selection.start(); 1073 memset(rows, 0, bytes_per_glyph * selection.size()); 1074 memset(widths, 0, selection.size()); 1075 1076 if (m_edited_font->is_fixed_width()) 1077 m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No); 1078 else 1079 m_glyph_editor_width_spinbox->set_value(0, GUI::AllowCallback::No); 1080 1081 m_glyph_editor_widget->update(); 1082 m_glyph_map_widget->update(); 1083 update_preview(); 1084 update_statusbar(); 1085} 1086 1087void MainWidget::show_error(Error error, StringView preface, StringView basename) 1088{ 1089 DeprecatedString formatted_error; 1090 if (basename.is_empty()) 1091 formatted_error = DeprecatedString::formatted("{}: {}", preface, error); 1092 else 1093 formatted_error = DeprecatedString::formatted("{} \"{}\" failed: {}", preface, basename, error); 1094 GUI::MessageBox::show_error(window(), formatted_error); 1095 warnln(formatted_error); 1096} 1097 1098}