MainWidget.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  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. #include "MainWidget.h"
  8. #include "GlyphEditorWidget.h"
  9. #include "NewFontDialog.h"
  10. #include <AK/Array.h>
  11. #include <AK/StringBuilder.h>
  12. #include <AK/StringUtils.h>
  13. #include <Applications/FontEditor/FontEditorWindowGML.h>
  14. #include <Applications/FontEditor/FontPreviewWindowGML.h>
  15. #include <LibConfig/Client.h>
  16. #include <LibDesktop/Launcher.h>
  17. #include <LibGUI/Action.h>
  18. #include <LibGUI/Application.h>
  19. #include <LibGUI/Button.h>
  20. #include <LibGUI/CheckBox.h>
  21. #include <LibGUI/Clipboard.h>
  22. #include <LibGUI/ComboBox.h>
  23. #include <LibGUI/FilePicker.h>
  24. #include <LibGUI/GlyphMapWidget.h>
  25. #include <LibGUI/GroupBox.h>
  26. #include <LibGUI/InputBox.h>
  27. #include <LibGUI/ItemListModel.h>
  28. #include <LibGUI/Label.h>
  29. #include <LibGUI/ListView.h>
  30. #include <LibGUI/Menu.h>
  31. #include <LibGUI/Menubar.h>
  32. #include <LibGUI/MessageBox.h>
  33. #include <LibGUI/SpinBox.h>
  34. #include <LibGUI/Statusbar.h>
  35. #include <LibGUI/TextBox.h>
  36. #include <LibGUI/ToolbarContainer.h>
  37. #include <LibGUI/Window.h>
  38. #include <LibGfx/Font/BitmapFont.h>
  39. #include <LibGfx/Font/Emoji.h>
  40. #include <LibGfx/Font/FontStyleMapping.h>
  41. #include <LibGfx/TextDirection.h>
  42. #include <LibUnicode/CharacterTypes.h>
  43. namespace FontEditor {
  44. static constexpr Array pangrams = {
  45. "quick fox jumps nightly above wizard"sv,
  46. "five quacking zephyrs jolt my wax bed"sv,
  47. "pack my box with five dozen liquor jugs"sv,
  48. "quick brown fox jumps over the lazy dog"sv,
  49. "waxy and quivering jocks fumble the pizza"sv,
  50. "~#:[@_1%]*{$2.3}/4^(5'6\"sv)-&|7+8!=<9,0\\>?;"sv,
  51. "byxfjärmat föl gick på duvshowen"sv,
  52. "         "sv,
  53. "float Fox.quick(h){ is_brown && it_jumps_over(doges.lazy) }"sv,
  54. "<fox color=\"brown\" speed=\"quick\" jumps=\"over\">lazy dog</fox>"sv
  55. };
  56. ErrorOr<RefPtr<GUI::Window>> MainWidget::create_preview_window()
  57. {
  58. auto window = TRY(GUI::Window::try_create(this));
  59. window->set_window_type(GUI::WindowType::ToolWindow);
  60. window->set_title("Preview");
  61. window->resize(400, 150);
  62. window->center_within(*this->window());
  63. auto main_widget = TRY(window->try_set_main_widget<GUI::Widget>());
  64. main_widget->load_from_gml(font_preview_window_gml);
  65. m_preview_label = find_descendant_of_type_named<GUI::Label>("preview_label");
  66. m_preview_label->set_font(edited_font());
  67. m_preview_textbox = find_descendant_of_type_named<GUI::TextBox>("preview_textbox");
  68. m_preview_textbox->on_change = [&] {
  69. auto preview = String::formatted("{}\n{}", m_preview_textbox->text(), Unicode::to_unicode_uppercase_full(m_preview_textbox->text()));
  70. m_preview_label->set_text(preview);
  71. };
  72. m_preview_textbox->set_text(pangrams[0]);
  73. auto& reload_button = *find_descendant_of_type_named<GUI::Button>("reload_button");
  74. reload_button.on_click = [&](auto) {
  75. static size_t i = 1;
  76. if (i >= pangrams.size())
  77. i = 0;
  78. m_preview_textbox->set_text(pangrams[i]);
  79. i++;
  80. };
  81. return window;
  82. }
  83. ErrorOr<void> MainWidget::create_actions()
  84. {
  85. m_new_action = GUI::Action::create("&New Font...", { Mod_Ctrl, Key_N }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/filetype-font.png"sv)), [&](auto&) {
  86. if (!request_close())
  87. return;
  88. auto new_font_wizard = NewFontDialog::construct(window());
  89. if (new_font_wizard->exec() == GUI::Dialog::ExecResult::OK) {
  90. auto metadata = new_font_wizard->new_font_metadata();
  91. auto new_font = Gfx::BitmapFont::create(metadata.glyph_height, metadata.glyph_width, metadata.is_fixed_width, 0x110000);
  92. new_font->set_name(metadata.name);
  93. new_font->set_family(metadata.family);
  94. new_font->set_presentation_size(metadata.presentation_size);
  95. new_font->set_weight(metadata.weight);
  96. new_font->set_slope(metadata.slope);
  97. new_font->set_baseline(metadata.baseline);
  98. new_font->set_mean_line(metadata.mean_line);
  99. window()->set_modified(true);
  100. MUST(initialize({}, move(new_font)));
  101. }
  102. });
  103. m_new_action->set_status_tip("Create a new font");
  104. m_open_action = GUI::CommonActions::make_open_action([&](auto&) {
  105. if (!request_close())
  106. return;
  107. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window(), {}, "/res/fonts/"sv);
  108. if (!open_path.has_value())
  109. return;
  110. open_file(open_path.value());
  111. });
  112. m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
  113. if (m_path.is_empty())
  114. m_save_as_action->activate();
  115. else
  116. save_file(m_path);
  117. });
  118. m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  119. LexicalPath lexical_path(m_path.is_empty() ? "Untitled.font" : m_path);
  120. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window(), lexical_path.title(), lexical_path.extension());
  121. if (!save_path.has_value())
  122. return;
  123. save_file(save_path.value());
  124. });
  125. m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) {
  126. cut_selected_glyphs();
  127. });
  128. m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  129. copy_selected_glyphs();
  130. });
  131. m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  132. paste_glyphs();
  133. });
  134. m_paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "glyph/x-fonteditor");
  135. GUI::Clipboard::the().on_change = [&](String const& data_type) {
  136. m_paste_action->set_enabled(data_type == "glyph/x-fonteditor");
  137. };
  138. m_delete_action = GUI::CommonActions::make_delete_action([this](auto&) {
  139. delete_selected_glyphs();
  140. });
  141. m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  142. undo();
  143. });
  144. m_undo_action->set_enabled(false);
  145. m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  146. redo();
  147. });
  148. m_redo_action->set_enabled(false);
  149. m_select_all_action = GUI::CommonActions::make_select_all_action([this](auto&) {
  150. m_glyph_map_widget->set_selection(m_range.first, m_range.last - m_range.first + 1);
  151. m_glyph_map_widget->update();
  152. auto selection = m_glyph_map_widget->selection().normalized();
  153. m_undo_selection->set_start(selection.start());
  154. m_undo_selection->set_size(selection.size());
  155. });
  156. m_open_preview_action = GUI::Action::create("&Preview Font", { Mod_Ctrl, Key_P }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/find.png"sv)), [&](auto&) {
  157. if (!m_font_preview_window) {
  158. if (auto maybe_window = create_preview_window(); maybe_window.is_error())
  159. warnln("Failed to create preview window: {}", maybe_window.error());
  160. else
  161. m_font_preview_window = maybe_window.release_value();
  162. }
  163. if (m_font_preview_window)
  164. m_font_preview_window->show();
  165. });
  166. m_open_preview_action->set_status_tip("Preview the current font");
  167. bool show_metadata = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowMetadata"sv, true);
  168. set_show_font_metadata(show_metadata);
  169. m_show_metadata_action = GUI::Action::create_checkable("Font &Metadata", { Mod_Ctrl, Key_M }, [&](auto& action) {
  170. set_show_font_metadata(action.is_checked());
  171. Config::write_bool("FontEditor"sv, "Layout"sv, "ShowMetadata"sv, action.is_checked());
  172. });
  173. m_show_metadata_action->set_checked(show_metadata);
  174. m_show_metadata_action->set_status_tip("Show or hide metadata about the current font");
  175. bool show_unicode_blocks = Config::read_bool("FontEditor"sv, "Layout"sv, "ShowUnicodeBlocks"sv, true);
  176. set_show_unicode_blocks(show_unicode_blocks);
  177. m_show_unicode_blocks_action = GUI::Action::create_checkable("&Unicode Blocks", { Mod_Ctrl, Key_U }, [&](auto& action) {
  178. set_show_unicode_blocks(action.is_checked());
  179. Config::write_bool("FontEditor"sv, "Layout"sv, "ShowUnicodeBlocks"sv, action.is_checked());
  180. });
  181. m_show_unicode_blocks_action->set_checked(show_unicode_blocks);
  182. m_show_unicode_blocks_action->set_status_tip("Show or hide the Unicode block list");
  183. m_go_to_glyph_action = GUI::Action::create("&Go to Glyph...", { Mod_Ctrl, Key_G }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-to.png"sv)), [&](auto&) {
  184. String input;
  185. if (GUI::InputBox::show(window(), input, "Hexadecimal:"sv, "Go to glyph"sv) == GUI::InputBox::ExecResult::OK && !input.is_empty()) {
  186. auto maybe_code_point = AK::StringUtils::convert_to_uint_from_hex(input);
  187. if (!maybe_code_point.has_value())
  188. return;
  189. auto code_point = maybe_code_point.value();
  190. code_point = clamp(code_point, m_range.first, m_range.last);
  191. m_glyph_map_widget->set_focus(true);
  192. m_glyph_map_widget->set_active_glyph(code_point);
  193. m_glyph_map_widget->scroll_to_glyph(code_point);
  194. }
  195. });
  196. m_go_to_glyph_action->set_status_tip("Go to the specified code point");
  197. m_previous_glyph_action = GUI::Action::create("Pre&vious Glyph", { Mod_Alt, Key_Left }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png"sv)), [&](auto&) {
  198. m_glyph_map_widget->select_previous_existing_glyph();
  199. });
  200. m_previous_glyph_action->set_status_tip("Seek the previous visible glyph");
  201. m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png"sv)), [&](auto&) {
  202. m_glyph_map_widget->select_next_existing_glyph();
  203. });
  204. m_next_glyph_action->set_status_tip("Seek the next visible glyph");
  205. i32 scale = Config::read_i32("FontEditor"sv, "GlyphEditor"sv, "Scale"sv, 10);
  206. set_scale(scale);
  207. m_scale_five_action = GUI::Action::create_checkable("500%", { Mod_Ctrl, Key_1 }, [this](auto&) {
  208. set_scale_and_save(5);
  209. });
  210. m_scale_five_action->set_checked(scale == 5);
  211. m_scale_five_action->set_status_tip("Scale the editor in proportion to the current font");
  212. m_scale_ten_action = GUI::Action::create_checkable("1000%", { Mod_Ctrl, Key_2 }, [this](auto&) {
  213. set_scale_and_save(10);
  214. });
  215. m_scale_ten_action->set_checked(scale == 10);
  216. m_scale_ten_action->set_status_tip("Scale the editor in proportion to the current font");
  217. m_scale_fifteen_action = GUI::Action::create_checkable("1500%", { Mod_Ctrl, Key_3 }, [this](auto&) {
  218. set_scale_and_save(15);
  219. });
  220. m_scale_fifteen_action->set_checked(scale == 15);
  221. m_scale_fifteen_action->set_status_tip("Scale the editor in proportion to the current font");
  222. m_glyph_editor_scale_actions.add_action(*m_scale_five_action);
  223. m_glyph_editor_scale_actions.add_action(*m_scale_ten_action);
  224. m_glyph_editor_scale_actions.add_action(*m_scale_fifteen_action);
  225. m_glyph_editor_scale_actions.set_exclusive(true);
  226. m_paint_glyph_action = GUI::Action::create_checkable("Paint Glyph", { Mod_Ctrl, KeyCode::Key_J }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/pixelpaint/pen.png"sv)), [&](auto&) {
  227. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Paint);
  228. });
  229. m_paint_glyph_action->set_checked(true);
  230. m_move_glyph_action = GUI::Action::create_checkable("Move Glyph", { Mod_Ctrl, KeyCode::Key_K }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/selection-move.png"sv)), [&](auto&) {
  231. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Move);
  232. });
  233. m_glyph_tool_actions.add_action(*m_paint_glyph_action);
  234. m_glyph_tool_actions.add_action(*m_move_glyph_action);
  235. m_glyph_tool_actions.set_exclusive(true);
  236. m_rotate_counterclockwise_action = GUI::CommonActions::make_rotate_counterclockwise_action([&](auto&) {
  237. m_glyph_editor_widget->rotate_90(GlyphEditorWidget::Counterclockwise);
  238. });
  239. m_rotate_clockwise_action = GUI::CommonActions::make_rotate_clockwise_action([&](auto&) {
  240. m_glyph_editor_widget->rotate_90(GlyphEditorWidget::Clockwise);
  241. });
  242. m_flip_horizontal_action = GUI::Action::create("Flip Horizontally", { Mod_Ctrl | Mod_Shift, Key_Q }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-horizontal.png"sv)), [&](auto&) {
  243. m_glyph_editor_widget->flip_horizontally();
  244. });
  245. m_flip_vertical_action = GUI::Action::create("Flip Vertically", { Mod_Ctrl | Mod_Shift, Key_W }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-vertical.png"sv)), [&](auto&) {
  246. m_glyph_editor_widget->flip_vertically();
  247. });
  248. m_copy_text_action = GUI::Action::create("Copy as Te&xt", { Mod_Ctrl, Key_T }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-copy.png"sv)), [&](auto&) {
  249. StringBuilder builder;
  250. auto selection = m_glyph_map_widget->selection().normalized();
  251. for (auto code_point = selection.start(); code_point < selection.start() + selection.size(); ++code_point) {
  252. if (!m_glyph_map_widget->font().contains_glyph(code_point))
  253. continue;
  254. builder.append_code_point(code_point);
  255. }
  256. GUI::Clipboard::the().set_plain_text(builder.to_string());
  257. });
  258. m_copy_text_action->set_status_tip("Copy to clipboard as text");
  259. return {};
  260. }
  261. ErrorOr<void> MainWidget::create_toolbars()
  262. {
  263. auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  264. (void)TRY(toolbar.try_add_action(*m_new_action));
  265. (void)TRY(toolbar.try_add_action(*m_open_action));
  266. (void)TRY(toolbar.try_add_action(*m_save_action));
  267. TRY(toolbar.try_add_separator());
  268. (void)TRY(toolbar.try_add_action(*m_cut_action));
  269. (void)TRY(toolbar.try_add_action(*m_copy_action));
  270. (void)TRY(toolbar.try_add_action(*m_paste_action));
  271. (void)TRY(toolbar.try_add_action(*m_delete_action));
  272. TRY(toolbar.try_add_separator());
  273. (void)TRY(toolbar.try_add_action(*m_undo_action));
  274. (void)TRY(toolbar.try_add_action(*m_redo_action));
  275. TRY(toolbar.try_add_separator());
  276. (void)TRY(toolbar.try_add_action(*m_open_preview_action));
  277. TRY(toolbar.try_add_separator());
  278. (void)TRY(toolbar.try_add_action(*m_previous_glyph_action));
  279. (void)TRY(toolbar.try_add_action(*m_next_glyph_action));
  280. (void)TRY(toolbar.try_add_action(*m_go_to_glyph_action));
  281. auto& glyph_transform_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_transform_toolbar");
  282. (void)TRY(glyph_transform_toolbar.try_add_action(*m_flip_horizontal_action));
  283. (void)TRY(glyph_transform_toolbar.try_add_action(*m_flip_vertical_action));
  284. (void)TRY(glyph_transform_toolbar.try_add_action(*m_rotate_counterclockwise_action));
  285. (void)TRY(glyph_transform_toolbar.try_add_action(*m_rotate_clockwise_action));
  286. auto& glyph_mode_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_mode_toolbar");
  287. (void)TRY(glyph_mode_toolbar.try_add_action(*m_paint_glyph_action));
  288. (void)TRY(glyph_mode_toolbar.try_add_action(*m_move_glyph_action));
  289. return {};
  290. }
  291. ErrorOr<void> MainWidget::create_models()
  292. {
  293. for (auto& it : Gfx::font_slope_names)
  294. TRY(m_font_slope_list.try_append(it.name));
  295. m_slope_combobox->set_model(GUI::ItemListModel<String>::create(m_font_slope_list));
  296. for (auto& it : Gfx::font_weight_names)
  297. TRY(m_font_weight_list.try_append(it.name));
  298. m_weight_combobox->set_model(GUI::ItemListModel<String>::create(m_font_weight_list));
  299. auto unicode_blocks = Unicode::block_display_names();
  300. TRY(m_unicode_block_list.try_append("Show All"));
  301. for (auto& block : unicode_blocks)
  302. TRY(m_unicode_block_list.try_append(block.display_name));
  303. m_unicode_block_model = GUI::ItemListModel<String>::create(m_unicode_block_list);
  304. m_filter_model = TRY(GUI::FilteringProxyModel::create(*m_unicode_block_model));
  305. m_filter_model->set_filter_term(""sv);
  306. m_unicode_block_listview = find_descendant_of_type_named<GUI::ListView>("unicode_block_listview");
  307. m_unicode_block_listview->on_selection_change = [this, unicode_blocks] {
  308. auto index = m_unicode_block_listview->selection().first();
  309. auto mapped_index = m_filter_model->map(index);
  310. if (mapped_index.row() > 0)
  311. m_range = unicode_blocks[mapped_index.row() - 1].code_point_range;
  312. else
  313. m_range = { 0x0000, 0x10FFFF };
  314. m_glyph_map_widget->set_active_range(m_range);
  315. };
  316. m_unicode_block_listview->set_model(m_filter_model);
  317. m_unicode_block_listview->set_activates_on_selection(true);
  318. m_unicode_block_listview->horizontal_scrollbar().set_visible(false);
  319. m_unicode_block_listview->set_cursor(m_unicode_block_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set);
  320. return {};
  321. }
  322. ErrorOr<void> MainWidget::create_undo_stack()
  323. {
  324. m_undo_stack = TRY(try_make<GUI::UndoStack>());
  325. m_undo_stack->on_state_change = [this] {
  326. m_undo_action->set_enabled(m_undo_stack->can_undo());
  327. m_redo_action->set_enabled(m_undo_stack->can_redo());
  328. if (m_undo_stack->is_current_modified())
  329. did_modify_font();
  330. };
  331. return {};
  332. }
  333. MainWidget::MainWidget()
  334. {
  335. load_from_gml(font_editor_window_gml);
  336. m_font_metadata_groupbox = find_descendant_of_type_named<GUI::GroupBox>("font_metadata_groupbox");
  337. m_unicode_block_container = find_descendant_of_type_named<GUI::Widget>("unicode_block_container");
  338. m_glyph_map_widget = find_descendant_of_type_named<GUI::GlyphMapWidget>("glyph_map_widget");
  339. m_glyph_editor_widget = find_descendant_of_type_named<GlyphEditorWidget>("glyph_editor_widget");
  340. m_glyph_editor_widget->on_glyph_altered = [this](int glyph) {
  341. m_glyph_map_widget->update_glyph(glyph);
  342. update_preview();
  343. did_modify_font();
  344. };
  345. m_glyph_editor_widget->on_undo_event = [this] {
  346. reset_selection_and_push_undo();
  347. };
  348. m_glyph_editor_width_spinbox = find_descendant_of_type_named<GUI::SpinBox>("glyph_editor_width_spinbox");
  349. m_glyph_editor_present_checkbox = find_descendant_of_type_named<GUI::CheckBox>("glyph_editor_present_checkbox");
  350. m_glyph_map_widget->on_active_glyph_changed = [this](int glyph) {
  351. if (m_undo_selection) {
  352. auto selection = m_glyph_map_widget->selection().normalized();
  353. m_undo_selection->set_start(selection.start());
  354. m_undo_selection->set_size(selection.size());
  355. m_undo_selection->set_active_glyph(glyph);
  356. }
  357. m_glyph_editor_widget->set_glyph(glyph);
  358. auto glyph_width = m_edited_font->raw_glyph_width(glyph);
  359. if (m_edited_font->is_fixed_width())
  360. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  361. else
  362. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  363. update_statusbar();
  364. };
  365. m_glyph_map_widget->on_context_menu_request = [this](auto& event) {
  366. m_context_menu->popup(event.screen_position());
  367. };
  368. m_name_textbox = find_descendant_of_type_named<GUI::TextBox>("name_textbox");
  369. m_name_textbox->on_change = [&] {
  370. m_edited_font->set_name(m_name_textbox->text());
  371. did_modify_font();
  372. };
  373. m_family_textbox = find_descendant_of_type_named<GUI::TextBox>("family_textbox");
  374. m_family_textbox->on_change = [&] {
  375. m_edited_font->set_family(m_family_textbox->text());
  376. did_modify_font();
  377. };
  378. m_fixed_width_checkbox = find_descendant_of_type_named<GUI::CheckBox>("fixed_width_checkbox");
  379. m_fixed_width_checkbox->on_checked = [this](bool checked) {
  380. m_edited_font->set_fixed_width(checked);
  381. auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph());
  382. m_glyph_editor_width_spinbox->set_visible(!checked);
  383. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  384. m_glyph_editor_present_checkbox->set_visible(checked);
  385. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  386. m_glyph_editor_widget->update();
  387. update_preview();
  388. did_modify_font();
  389. };
  390. m_glyph_editor_width_spinbox->on_change = [this](int value) {
  391. reset_selection_and_push_undo();
  392. m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), value);
  393. m_glyph_editor_widget->update();
  394. m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
  395. update_preview();
  396. update_statusbar();
  397. did_modify_font();
  398. };
  399. m_glyph_editor_present_checkbox->on_checked = [this](bool checked) {
  400. reset_selection_and_push_undo();
  401. m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
  402. m_glyph_editor_widget->update();
  403. m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
  404. update_preview();
  405. update_statusbar();
  406. did_modify_font();
  407. };
  408. m_weight_combobox = find_descendant_of_type_named<GUI::ComboBox>("weight_combobox");
  409. m_weight_combobox->on_change = [this](auto&, auto&) {
  410. m_edited_font->set_weight(Gfx::name_to_weight(m_weight_combobox->text()));
  411. did_modify_font();
  412. };
  413. m_slope_combobox = find_descendant_of_type_named<GUI::ComboBox>("slope_combobox");
  414. m_slope_combobox->on_change = [this](auto&, auto&) {
  415. m_edited_font->set_slope(Gfx::name_to_slope(m_slope_combobox->text()));
  416. did_modify_font();
  417. };
  418. m_presentation_spinbox = find_descendant_of_type_named<GUI::SpinBox>("presentation_spinbox");
  419. m_presentation_spinbox->on_change = [this](int value) {
  420. m_edited_font->set_presentation_size(value);
  421. update_preview();
  422. did_modify_font();
  423. };
  424. m_spacing_spinbox = find_descendant_of_type_named<GUI::SpinBox>("spacing_spinbox");
  425. m_spacing_spinbox->on_change = [this](int value) {
  426. m_edited_font->set_glyph_spacing(value);
  427. update_preview();
  428. did_modify_font();
  429. };
  430. m_baseline_spinbox = find_descendant_of_type_named<GUI::SpinBox>("baseline_spinbox");
  431. m_baseline_spinbox->on_change = [this](int value) {
  432. m_edited_font->set_baseline(value);
  433. m_glyph_editor_widget->update();
  434. update_preview();
  435. did_modify_font();
  436. };
  437. m_mean_line_spinbox = find_descendant_of_type_named<GUI::SpinBox>("mean_line_spinbox");
  438. m_mean_line_spinbox->on_change = [this](int value) {
  439. m_edited_font->set_mean_line(value);
  440. m_glyph_editor_widget->update();
  441. update_preview();
  442. did_modify_font();
  443. };
  444. m_search_textbox = find_descendant_of_type_named<GUI::TextBox>("search_textbox");
  445. m_search_textbox->on_return_pressed = [this] {
  446. if (!m_unicode_block_listview->selection().is_empty())
  447. m_unicode_block_listview->activate_selected();
  448. };
  449. m_search_textbox->on_down_pressed = [this] {
  450. m_unicode_block_listview->move_cursor(GUI::AbstractView::CursorMovement::Down, GUI::AbstractView::SelectionUpdate::Set);
  451. };
  452. m_search_textbox->on_up_pressed = [this] {
  453. m_unicode_block_listview->move_cursor(GUI::AbstractView::CursorMovement::Up, GUI::AbstractView::SelectionUpdate::Set);
  454. };
  455. m_search_textbox->on_change = [this] {
  456. m_filter_model->set_filter_term(m_search_textbox->text());
  457. if (m_filter_model->row_count() != 0)
  458. m_unicode_block_listview->set_cursor(m_filter_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set);
  459. };
  460. m_statusbar = find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  461. GUI::Application::the()->on_action_enter = [this](GUI::Action& action) {
  462. auto text = action.status_tip();
  463. if (text.is_empty())
  464. text = Gfx::parse_ampersand_string(action.text());
  465. m_statusbar->set_override_text(move(text));
  466. };
  467. GUI::Application::the()->on_action_leave = [this](GUI::Action&) {
  468. m_statusbar->set_override_text({});
  469. };
  470. }
  471. ErrorOr<void> MainWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&& edited_font)
  472. {
  473. if (m_edited_font == edited_font)
  474. return {};
  475. m_path = path;
  476. m_edited_font = edited_font;
  477. if (m_preview_label)
  478. m_preview_label->set_font(*m_edited_font);
  479. m_glyph_map_widget->set_font(*m_edited_font);
  480. m_glyph_editor_widget->set_font(*m_edited_font);
  481. m_glyph_editor_widget->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  482. m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width());
  483. m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width(), GUI::AllowCallback::No);
  484. m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
  485. m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width());
  486. m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
  487. m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width(), GUI::AllowCallback::No);
  488. m_name_textbox->set_text(m_edited_font->name(), GUI::AllowCallback::No);
  489. m_family_textbox->set_text(m_edited_font->family(), GUI::AllowCallback::No);
  490. m_presentation_spinbox->set_value(m_edited_font->presentation_size(), GUI::AllowCallback::No);
  491. m_spacing_spinbox->set_value(m_edited_font->glyph_spacing(), GUI::AllowCallback::No);
  492. m_mean_line_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No);
  493. m_baseline_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No);
  494. m_mean_line_spinbox->set_value(m_edited_font->mean_line(), GUI::AllowCallback::No);
  495. m_baseline_spinbox->set_value(m_edited_font->baseline(), GUI::AllowCallback::No);
  496. int i = 0;
  497. for (auto& it : Gfx::font_weight_names) {
  498. if (it.style == m_edited_font->weight()) {
  499. m_weight_combobox->set_selected_index(i, GUI::AllowCallback::No);
  500. break;
  501. }
  502. i++;
  503. }
  504. i = 0;
  505. for (auto& it : Gfx::font_slope_names) {
  506. if (it.style == m_edited_font->slope()) {
  507. m_slope_combobox->set_selected_index(i, GUI::AllowCallback::No);
  508. break;
  509. }
  510. i++;
  511. }
  512. auto selection = m_glyph_map_widget->selection().normalized();
  513. m_undo_selection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) UndoSelection(selection.start(), selection.size(), m_glyph_map_widget->active_glyph(), *m_edited_font)));
  514. m_undo_stack->clear();
  515. update_statusbar();
  516. deferred_invoke([this] {
  517. auto glyph = m_glyph_map_widget->active_glyph();
  518. m_glyph_map_widget->set_focus(true);
  519. m_glyph_map_widget->scroll_to_glyph(glyph);
  520. m_glyph_editor_widget->set_glyph(glyph);
  521. update_title();
  522. });
  523. return {};
  524. }
  525. ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
  526. {
  527. auto file_menu = TRY(window.try_add_menu("&File"));
  528. TRY(file_menu->try_add_action(*m_new_action));
  529. TRY(file_menu->try_add_action(*m_open_action));
  530. TRY(file_menu->try_add_action(*m_save_action));
  531. TRY(file_menu->try_add_action(*m_save_as_action));
  532. TRY(file_menu->try_add_separator());
  533. TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([this](auto&) {
  534. if (!request_close())
  535. return;
  536. GUI::Application::the()->quit();
  537. })));
  538. auto edit_menu = TRY(window.try_add_menu("&Edit"));
  539. TRY(edit_menu->try_add_action(*m_undo_action));
  540. TRY(edit_menu->try_add_action(*m_redo_action));
  541. TRY(edit_menu->try_add_separator());
  542. TRY(edit_menu->try_add_action(*m_cut_action));
  543. TRY(edit_menu->try_add_action(*m_copy_action));
  544. TRY(edit_menu->try_add_action(*m_paste_action));
  545. TRY(edit_menu->try_add_action(*m_delete_action));
  546. TRY(edit_menu->try_add_separator());
  547. TRY(edit_menu->try_add_action(*m_select_all_action));
  548. TRY(edit_menu->try_add_separator());
  549. TRY(edit_menu->try_add_action(*m_copy_text_action));
  550. m_context_menu = edit_menu;
  551. auto go_menu = TRY(window.try_add_menu("&Go"));
  552. TRY(go_menu->try_add_action(*m_previous_glyph_action));
  553. TRY(go_menu->try_add_action(*m_next_glyph_action));
  554. TRY(go_menu->try_add_action(*m_go_to_glyph_action));
  555. auto view_menu = TRY(window.try_add_menu("&View"));
  556. TRY(view_menu->try_add_action(*m_open_preview_action));
  557. TRY(view_menu->try_add_separator());
  558. TRY(view_menu->try_add_action(*m_show_metadata_action));
  559. TRY(view_menu->try_add_action(*m_show_unicode_blocks_action));
  560. TRY(view_menu->try_add_separator());
  561. auto scale_menu = TRY(view_menu->try_add_submenu("&Scale"));
  562. TRY(scale_menu->try_add_action(*m_scale_five_action));
  563. TRY(scale_menu->try_add_action(*m_scale_ten_action));
  564. TRY(scale_menu->try_add_action(*m_scale_fifteen_action));
  565. auto help_menu = TRY(window.try_add_menu("&Help"));
  566. TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) {
  567. Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/FontEditor.md"), "/bin/Help");
  568. })));
  569. 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)));
  570. return {};
  571. }
  572. bool MainWidget::save_file(String const& path)
  573. {
  574. auto saved_font = m_edited_font->masked_character_set();
  575. auto ret_val = saved_font->write_to_file(path);
  576. if (!ret_val) {
  577. GUI::MessageBox::show(window(), "The font file could not be saved."sv, "Save failed"sv, GUI::MessageBox::Type::Error);
  578. return false;
  579. }
  580. m_path = path;
  581. m_undo_stack->set_current_unmodified();
  582. window()->set_modified(false);
  583. update_title();
  584. return true;
  585. }
  586. void MainWidget::set_show_font_metadata(bool show)
  587. {
  588. if (m_font_metadata == show)
  589. return;
  590. m_font_metadata = show;
  591. m_font_metadata_groupbox->set_visible(m_font_metadata);
  592. }
  593. void MainWidget::set_show_unicode_blocks(bool show)
  594. {
  595. if (m_unicode_blocks == show)
  596. return;
  597. m_unicode_blocks = show;
  598. m_unicode_block_container->set_visible(m_unicode_blocks);
  599. }
  600. bool MainWidget::open_file(String const& path)
  601. {
  602. auto bitmap_font = Gfx::BitmapFont::load_from_file(path);
  603. if (!bitmap_font) {
  604. String message = String::formatted("Couldn't load font: {}\n", path);
  605. GUI::MessageBox::show(window(), message, "Font Editor"sv, GUI::MessageBox::Type::Error);
  606. return false;
  607. }
  608. auto new_font = bitmap_font->unmasked_character_set();
  609. window()->set_modified(false);
  610. MUST(initialize(path, move(new_font)));
  611. return true;
  612. }
  613. void MainWidget::push_undo()
  614. {
  615. auto maybe_state = m_undo_selection->save_state();
  616. if (maybe_state.is_error()) {
  617. warnln("Failed to save undo state: {}", maybe_state.error());
  618. return;
  619. }
  620. auto state = maybe_state.release_value();
  621. auto maybe_command = try_make<SelectionUndoCommand>(*m_undo_selection, move(state));
  622. if (maybe_command.is_error()) {
  623. warnln("Failed to make undo command: {}", maybe_command.error());
  624. return;
  625. }
  626. auto command = maybe_command.release_value();
  627. if (auto maybe_push = m_undo_stack->try_push(move(command)); maybe_push.is_error())
  628. warnln("Failed to push undo stack: {}", maybe_push.error());
  629. }
  630. void MainWidget::reset_selection_and_push_undo()
  631. {
  632. auto selection = m_glyph_map_widget->selection().normalized();
  633. if (selection.size() != 1) {
  634. auto start = m_glyph_map_widget->active_glyph();
  635. m_undo_selection->set_start(start);
  636. m_undo_selection->set_size(1);
  637. m_glyph_map_widget->set_selection(start, 1);
  638. m_glyph_map_widget->update();
  639. }
  640. push_undo();
  641. }
  642. void MainWidget::undo()
  643. {
  644. if (!m_undo_stack->can_undo())
  645. return;
  646. m_undo_stack->undo();
  647. auto glyph = m_undo_selection->restored_active_glyph();
  648. auto glyph_width = edited_font().raw_glyph_width(glyph);
  649. if (glyph < m_range.first || glyph > m_range.last)
  650. m_search_textbox->set_text(""sv);
  651. deferred_invoke([this, glyph] {
  652. auto start = m_undo_selection->restored_start();
  653. auto size = m_undo_selection->restored_size();
  654. m_glyph_map_widget->set_selection(start, size, glyph);
  655. m_glyph_map_widget->scroll_to_glyph(glyph);
  656. m_glyph_map_widget->set_focus(true);
  657. });
  658. if (m_edited_font->is_fixed_width()) {
  659. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  660. } else {
  661. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  662. }
  663. m_glyph_editor_widget->update();
  664. m_glyph_map_widget->update();
  665. update_preview();
  666. update_statusbar();
  667. }
  668. void MainWidget::redo()
  669. {
  670. if (!m_undo_stack->can_redo())
  671. return;
  672. m_undo_stack->redo();
  673. auto glyph = m_undo_selection->restored_active_glyph();
  674. auto glyph_width = edited_font().raw_glyph_width(glyph);
  675. if (glyph < m_range.first || glyph > m_range.last)
  676. m_search_textbox->set_text(""sv);
  677. deferred_invoke([this, glyph] {
  678. auto start = m_undo_selection->restored_start();
  679. auto size = m_undo_selection->restored_size();
  680. m_glyph_map_widget->set_selection(start, size, glyph);
  681. m_glyph_map_widget->scroll_to_glyph(glyph);
  682. m_glyph_map_widget->set_focus(true);
  683. });
  684. if (m_edited_font->is_fixed_width()) {
  685. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  686. } else {
  687. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  688. }
  689. m_glyph_editor_widget->update();
  690. m_glyph_map_widget->update();
  691. update_preview();
  692. update_statusbar();
  693. }
  694. bool MainWidget::request_close()
  695. {
  696. if (!window()->is_modified())
  697. return true;
  698. auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), m_path, m_undo_stack->last_unmodified_timestamp());
  699. if (result == GUI::MessageBox::ExecResult::Yes) {
  700. m_save_action->activate();
  701. if (!window()->is_modified())
  702. return true;
  703. }
  704. if (result == GUI::MessageBox::ExecResult::No)
  705. return true;
  706. return false;
  707. }
  708. void MainWidget::update_title()
  709. {
  710. StringBuilder title;
  711. if (m_path.is_empty())
  712. title.append("Untitled"sv);
  713. else
  714. title.append(m_path);
  715. title.append("[*] - Font Editor"sv);
  716. window()->set_title(title.to_string());
  717. }
  718. void MainWidget::did_modify_font()
  719. {
  720. if (!window() || window()->is_modified())
  721. return;
  722. window()->set_modified(true);
  723. update_title();
  724. }
  725. void MainWidget::update_statusbar()
  726. {
  727. auto glyph = m_glyph_map_widget->active_glyph();
  728. StringBuilder builder;
  729. builder.appendff("U+{:04X} (", glyph);
  730. if (auto abbreviation = Unicode::code_point_abbreviation(glyph); abbreviation.has_value()) {
  731. builder.append(*abbreviation);
  732. } else if (Gfx::get_char_bidi_class(glyph) == Gfx::BidirectionalClass::STRONG_RTL) {
  733. // FIXME: This is a necessary hack, as RTL text will mess up the painting of the statusbar text.
  734. // For now, replace RTL glyphs with U+FFFD, the replacement character.
  735. builder.append_code_point(0xFFFD);
  736. } else {
  737. builder.append_code_point(glyph);
  738. }
  739. builder.append(')');
  740. auto glyph_name = Unicode::code_point_display_name(glyph);
  741. if (glyph_name.has_value()) {
  742. builder.appendff(" {}", glyph_name.value());
  743. }
  744. if (m_edited_font->contains_raw_glyph(glyph))
  745. builder.appendff(" [{}x{}]", m_edited_font->raw_glyph_width(glyph), m_edited_font->glyph_height());
  746. else if (Gfx::Emoji::emoji_for_code_point(glyph))
  747. builder.appendff(" [emoji]");
  748. m_statusbar->set_text(builder.to_string());
  749. }
  750. void MainWidget::update_preview()
  751. {
  752. if (m_font_preview_window)
  753. m_font_preview_window->update();
  754. }
  755. void MainWidget::drop_event(GUI::DropEvent& event)
  756. {
  757. event.accept();
  758. if (event.mime_data().has_urls()) {
  759. auto urls = event.mime_data().urls();
  760. if (urls.is_empty())
  761. return;
  762. window()->move_to_front();
  763. if (!request_close())
  764. return;
  765. open_file(urls.first().path());
  766. }
  767. }
  768. void MainWidget::set_scale(i32 scale)
  769. {
  770. m_glyph_editor_widget->set_scale(scale);
  771. }
  772. void MainWidget::set_scale_and_save(i32 scale)
  773. {
  774. set_scale(scale);
  775. Config::write_i32("FontEditor"sv, "GlyphEditor"sv, "Scale"sv, scale);
  776. m_glyph_editor_widget->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  777. }
  778. void MainWidget::copy_selected_glyphs()
  779. {
  780. size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * edited_font().glyph_height();
  781. auto selection = m_glyph_map_widget->selection().normalized();
  782. auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph;
  783. auto* widths = m_edited_font->widths() + selection.start();
  784. ByteBuffer buffer;
  785. buffer.append(rows, bytes_per_glyph * selection.size());
  786. buffer.append(widths, selection.size());
  787. HashMap<String, String> metadata;
  788. metadata.set("start", String::number(selection.start()));
  789. metadata.set("count", String::number(selection.size()));
  790. metadata.set("width", String::number(edited_font().max_glyph_width()));
  791. metadata.set("height", String::number(edited_font().glyph_height()));
  792. GUI::Clipboard::the().set_data(buffer.bytes(), "glyph/x-fonteditor", metadata);
  793. }
  794. void MainWidget::cut_selected_glyphs()
  795. {
  796. copy_selected_glyphs();
  797. delete_selected_glyphs();
  798. }
  799. void MainWidget::paste_glyphs()
  800. {
  801. auto [data, mime_type, metadata] = GUI::Clipboard::the().fetch_data_and_type();
  802. if (!mime_type.starts_with("glyph/"sv))
  803. return;
  804. auto glyph_count = metadata.get("count").value().to_uint().value_or(0);
  805. if (!glyph_count)
  806. return;
  807. auto height = metadata.get("height").value().to_uint().value_or(0);
  808. if (!height)
  809. return;
  810. auto selection = m_glyph_map_widget->selection().normalized();
  811. auto range_bound_glyph_count = min(glyph_count, 1 + m_range.last - selection.start());
  812. m_undo_selection->set_size(range_bound_glyph_count);
  813. push_undo();
  814. size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * edited_font().glyph_height();
  815. size_t bytes_per_copied_glyph = Gfx::GlyphBitmap::bytes_per_row() * height;
  816. size_t copyable_bytes_per_glyph = min(bytes_per_glyph, bytes_per_copied_glyph);
  817. auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph;
  818. auto* widths = m_edited_font->widths() + selection.start();
  819. for (size_t i = 0; i < range_bound_glyph_count; ++i) {
  820. auto copyable_width = edited_font().is_fixed_width()
  821. ? data[bytes_per_copied_glyph * glyph_count + i] ? edited_font().glyph_fixed_width() : 0
  822. : min(edited_font().max_glyph_width(), data[bytes_per_copied_glyph * glyph_count + i]);
  823. memcpy(&rows[i * bytes_per_glyph], &data[i * bytes_per_copied_glyph], copyable_bytes_per_glyph);
  824. memset(&widths[i], copyable_width, sizeof(u8));
  825. }
  826. m_glyph_map_widget->set_selection(selection.start() + range_bound_glyph_count - 1, -range_bound_glyph_count + 1);
  827. if (m_edited_font->is_fixed_width())
  828. m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
  829. else
  830. m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
  831. m_glyph_editor_widget->update();
  832. m_glyph_map_widget->update();
  833. update_statusbar();
  834. }
  835. void MainWidget::delete_selected_glyphs()
  836. {
  837. push_undo();
  838. auto selection = m_glyph_map_widget->selection().normalized();
  839. size_t bytes_per_glyph = Gfx::GlyphBitmap::bytes_per_row() * m_edited_font->glyph_height();
  840. auto* rows = m_edited_font->rows() + selection.start() * bytes_per_glyph;
  841. auto* widths = m_edited_font->widths() + selection.start();
  842. memset(rows, 0, bytes_per_glyph * selection.size());
  843. memset(widths, 0, selection.size());
  844. if (m_edited_font->is_fixed_width())
  845. m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
  846. else
  847. m_glyph_editor_width_spinbox->set_value(0, GUI::AllowCallback::No);
  848. m_glyph_editor_widget->update();
  849. m_glyph_map_widget->update();
  850. update_statusbar();
  851. }
  852. }