FontEditor.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "FontEditor.h"
  7. #include "GlyphEditorWidget.h"
  8. #include "GlyphMapWidget.h"
  9. #include "NewFontDialog.h"
  10. #include <AK/StringBuilder.h>
  11. #include <AK/UnicodeUtils.h>
  12. #include <Applications/FontEditor/FontEditorWindowGML.h>
  13. #include <LibDesktop/Launcher.h>
  14. #include <LibGUI/Action.h>
  15. #include <LibGUI/Application.h>
  16. #include <LibGUI/BoxLayout.h>
  17. #include <LibGUI/Button.h>
  18. #include <LibGUI/CheckBox.h>
  19. #include <LibGUI/Clipboard.h>
  20. #include <LibGUI/ComboBox.h>
  21. #include <LibGUI/FilePicker.h>
  22. #include <LibGUI/GroupBox.h>
  23. #include <LibGUI/InputBox.h>
  24. #include <LibGUI/ItemListModel.h>
  25. #include <LibGUI/Label.h>
  26. #include <LibGUI/Menu.h>
  27. #include <LibGUI/Menubar.h>
  28. #include <LibGUI/MessageBox.h>
  29. #include <LibGUI/Painter.h>
  30. #include <LibGUI/SpinBox.h>
  31. #include <LibGUI/Statusbar.h>
  32. #include <LibGUI/TextBox.h>
  33. #include <LibGUI/ToolbarContainer.h>
  34. #include <LibGUI/Window.h>
  35. #include <LibGfx/BitmapFont.h>
  36. #include <LibGfx/FontStyleMapping.h>
  37. #include <LibGfx/Palette.h>
  38. #include <LibGfx/TextDirection.h>
  39. #include <LibUnicode/CharacterTypes.h>
  40. #include <stdlib.h>
  41. static constexpr int s_pangram_count = 7;
  42. static char const* pangrams[s_pangram_count] = {
  43. "quick fox jumps nightly above wizard",
  44. "five quacking zephyrs jolt my wax bed",
  45. "pack my box with five dozen liquor jugs",
  46. "quick brown fox jumps over the lazy dog",
  47. "waxy and quivering jocks fumble the pizza",
  48. "~#:[@_1%]*{$2.3}/4^(5'6\")-&|7+8!=<9,0\\>?;",
  49. "byxfjärmat föl gick på duvshowen"
  50. };
  51. static RefPtr<GUI::Window> create_font_preview_window(FontEditorWidget& editor)
  52. {
  53. auto window = GUI::Window::construct(&editor);
  54. window->set_window_type(GUI::WindowType::ToolWindow);
  55. window->set_title("Preview");
  56. window->resize(400, 150);
  57. window->set_minimum_size(200, 100);
  58. window->center_within(*editor.window());
  59. auto& main_widget = window->set_main_widget<GUI::Widget>();
  60. main_widget.set_fill_with_background_color(true);
  61. main_widget.set_layout<GUI::VerticalBoxLayout>();
  62. main_widget.layout()->set_margins(2);
  63. main_widget.layout()->set_spacing(4);
  64. auto& preview_box = main_widget.add<GUI::GroupBox>();
  65. preview_box.set_layout<GUI::VerticalBoxLayout>();
  66. preview_box.layout()->set_margins(8);
  67. auto& preview_label = preview_box.add<GUI::Label>();
  68. preview_label.set_font(editor.edited_font());
  69. editor.on_initialize = [&] {
  70. preview_label.set_font(editor.edited_font());
  71. };
  72. auto& textbox_button_container = main_widget.add<GUI::Widget>();
  73. textbox_button_container.set_layout<GUI::HorizontalBoxLayout>();
  74. textbox_button_container.set_fixed_height(22);
  75. auto& preview_textbox = textbox_button_container.add<GUI::TextBox>();
  76. preview_textbox.set_text(pangrams[0]);
  77. preview_textbox.set_placeholder("Preview text");
  78. preview_textbox.on_change = [&] {
  79. auto preview = String::formatted("{}\n{}",
  80. preview_textbox.text(),
  81. Unicode::to_unicode_uppercase_full(preview_textbox.text()));
  82. preview_label.set_text(preview);
  83. };
  84. auto& reload_button = textbox_button_container.add<GUI::Button>();
  85. reload_button.set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/reload.png").release_value_but_fixme_should_propagate_errors());
  86. reload_button.set_fixed_width(22);
  87. reload_button.on_click = [&](auto) {
  88. static int i = 1;
  89. if (i >= s_pangram_count)
  90. i = 0;
  91. preview_textbox.set_text(pangrams[i]);
  92. i++;
  93. };
  94. return window;
  95. }
  96. FontEditorWidget::FontEditorWidget()
  97. {
  98. load_from_gml(font_editor_window_gml);
  99. auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  100. auto& glyph_mode_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_mode_toolbar");
  101. auto& glyph_transform_toolbar = *find_descendant_of_type_named<GUI::Toolbar>("glyph_transform_toolbar");
  102. auto& glyph_map_container = *find_descendant_of_type_named<GUI::Widget>("glyph_map_container");
  103. m_statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  104. m_glyph_editor_container = *find_descendant_of_type_named<GUI::Widget>("glyph_editor_container");
  105. m_left_column_container = *find_descendant_of_type_named<GUI::Widget>("left_column_container");
  106. m_glyph_editor_width_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("glyph_editor_width_spinbox");
  107. m_glyph_editor_present_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("glyph_editor_present_checkbox");
  108. m_name_textbox = *find_descendant_of_type_named<GUI::TextBox>("name_textbox");
  109. m_family_textbox = *find_descendant_of_type_named<GUI::TextBox>("family_textbox");
  110. m_presentation_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("presentation_spinbox");
  111. m_weight_combobox = *find_descendant_of_type_named<GUI::ComboBox>("weight_combobox");
  112. m_slope_combobox = *find_descendant_of_type_named<GUI::ComboBox>("slope_combobox");
  113. m_spacing_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("spacing_spinbox");
  114. m_mean_line_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("mean_line_spinbox");
  115. m_baseline_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("baseline_spinbox");
  116. m_fixed_width_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("fixed_width_checkbox");
  117. m_font_metadata_groupbox = *find_descendant_of_type_named<GUI::GroupBox>("font_metadata_groupbox");
  118. m_glyph_editor_widget = m_glyph_editor_container->add<GlyphEditorWidget>();
  119. m_glyph_map_widget = glyph_map_container.add<GlyphMapWidget>();
  120. m_new_action = GUI::Action::create("&New Font...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/filetype-font.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  121. if (!request_close())
  122. return;
  123. auto new_font_wizard = NewFontDialog::construct(window());
  124. if (new_font_wizard->exec() == GUI::Dialog::ExecOK) {
  125. auto metadata = new_font_wizard->new_font_metadata();
  126. auto new_font = Gfx::BitmapFont::create(metadata.glyph_height, metadata.glyph_width, metadata.is_fixed_width, 0x110000);
  127. new_font->set_name(metadata.name);
  128. new_font->set_family(metadata.family);
  129. new_font->set_presentation_size(metadata.presentation_size);
  130. new_font->set_weight(metadata.weight);
  131. new_font->set_slope(metadata.slope);
  132. new_font->set_baseline(metadata.baseline);
  133. new_font->set_mean_line(metadata.mean_line);
  134. window()->set_modified(true);
  135. initialize({}, move(new_font));
  136. }
  137. });
  138. m_new_action->set_status_tip("Create a new font");
  139. m_open_action = GUI::CommonActions::make_open_action([&](auto&) {
  140. if (!request_close())
  141. return;
  142. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window(), {}, "/res/fonts/");
  143. if (!open_path.has_value())
  144. return;
  145. open_file(open_path.value());
  146. });
  147. m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
  148. if (m_path.is_empty())
  149. m_save_as_action->activate();
  150. else
  151. save_as(m_path);
  152. });
  153. m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  154. LexicalPath lexical_path(m_path.is_empty() ? "Untitled.font" : m_path);
  155. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window(), lexical_path.title(), lexical_path.extension());
  156. if (!save_path.has_value())
  157. return;
  158. save_as(save_path.value());
  159. });
  160. m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) {
  161. if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
  162. return;
  163. m_glyph_editor_widget->cut_glyph();
  164. if (m_edited_font->is_fixed_width())
  165. m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
  166. else
  167. m_glyph_editor_width_spinbox->set_value(0, GUI::AllowCallback::No);
  168. update_statusbar();
  169. });
  170. m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  171. if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
  172. return;
  173. m_glyph_editor_widget->copy_glyph();
  174. });
  175. m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  176. m_glyph_editor_widget->paste_glyph();
  177. if (m_edited_font->is_fixed_width())
  178. m_glyph_editor_present_checkbox->set_checked(true, GUI::AllowCallback::No);
  179. else
  180. m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
  181. update_statusbar();
  182. });
  183. m_paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "glyph/x-fonteditor");
  184. m_delete_action = GUI::CommonActions::make_delete_action([this](auto&) {
  185. if (m_glyph_editor_widget->is_glyph_empty() && !m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
  186. return;
  187. m_glyph_editor_widget->delete_glyph();
  188. if (m_edited_font->is_fixed_width())
  189. m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
  190. else
  191. m_glyph_editor_width_spinbox->set_value(0, GUI::AllowCallback::No);
  192. update_statusbar();
  193. });
  194. m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  195. undo();
  196. });
  197. m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  198. redo();
  199. });
  200. m_open_preview_action = GUI::Action::create("&Preview Font", { Mod_Ctrl, Key_P }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/find.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  201. if (!m_font_preview_window)
  202. m_font_preview_window = create_font_preview_window(*this);
  203. m_font_preview_window->show();
  204. m_font_preview_window->move_to_front();
  205. });
  206. m_open_preview_action->set_checked(false);
  207. m_open_preview_action->set_status_tip("Preview the current font");
  208. m_show_metadata_action = GUI::Action::create_checkable("Font &Metadata", { Mod_Ctrl, Key_M }, [&](auto& action) {
  209. set_show_font_metadata(action.is_checked());
  210. });
  211. m_show_metadata_action->set_checked(true);
  212. m_show_metadata_action->set_status_tip("Show or hide metadata about the current font");
  213. m_go_to_glyph_action = GUI::Action::create("&Go to Glyph...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-to.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  214. String input;
  215. if (GUI::InputBox::show(window(), input, "Hexadecimal:", "Go to Glyph") == GUI::InputBox::ExecOK && !input.is_empty()) {
  216. int code_point = strtoul(&input[0], nullptr, 16);
  217. code_point = clamp(code_point, 0x0000, 0x10FFFF);
  218. m_glyph_map_widget->set_focus(true);
  219. m_glyph_map_widget->set_selected_glyph(code_point);
  220. m_glyph_map_widget->scroll_to_glyph(code_point);
  221. }
  222. });
  223. m_go_to_glyph_action->set_status_tip("Go to the specified code point");
  224. 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&) {
  225. bool search_wrapped = false;
  226. for (int i = m_glyph_map_widget->selected_glyph() - 1;; --i) {
  227. if (i < 0 && !search_wrapped) {
  228. i = 0x10FFFF;
  229. search_wrapped = true;
  230. } else if (i < 0 && search_wrapped) {
  231. break;
  232. }
  233. if (m_edited_font->contains_raw_glyph(i)) {
  234. m_glyph_map_widget->set_focus(true);
  235. m_glyph_map_widget->set_selected_glyph(i);
  236. m_glyph_map_widget->scroll_to_glyph(i);
  237. break;
  238. }
  239. }
  240. });
  241. m_previous_glyph_action->set_status_tip("Seek the previous visible glyph");
  242. 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&) {
  243. bool search_wrapped = false;
  244. for (int i = m_glyph_map_widget->selected_glyph() + 1;; ++i) {
  245. if (i > 0x10FFFF && !search_wrapped) {
  246. i = 0;
  247. search_wrapped = true;
  248. } else if (i > 0x10FFFF && search_wrapped) {
  249. break;
  250. }
  251. if (m_edited_font->contains_raw_glyph(i)) {
  252. m_glyph_map_widget->set_focus(true);
  253. m_glyph_map_widget->set_selected_glyph(i);
  254. m_glyph_map_widget->scroll_to_glyph(i);
  255. break;
  256. }
  257. }
  258. });
  259. m_next_glyph_action->set_status_tip("Seek the next visible glyph");
  260. toolbar.add_action(*m_new_action);
  261. toolbar.add_action(*m_open_action);
  262. toolbar.add_action(*m_save_action);
  263. toolbar.add_separator();
  264. toolbar.add_action(*m_cut_action);
  265. toolbar.add_action(*m_copy_action);
  266. toolbar.add_action(*m_paste_action);
  267. toolbar.add_action(*m_delete_action);
  268. toolbar.add_separator();
  269. toolbar.add_action(*m_undo_action);
  270. toolbar.add_action(*m_redo_action);
  271. toolbar.add_separator();
  272. toolbar.add_action(*m_open_preview_action);
  273. toolbar.add_separator();
  274. toolbar.add_action(*m_previous_glyph_action);
  275. toolbar.add_action(*m_next_glyph_action);
  276. toolbar.add_action(*m_go_to_glyph_action);
  277. m_scale_five_action = GUI::Action::create_checkable("500%", { Mod_Ctrl, Key_1 }, [&](auto&) {
  278. m_glyph_editor_widget->set_scale(5);
  279. did_resize_glyph_editor();
  280. });
  281. m_scale_five_action->set_checked(false);
  282. m_scale_five_action->set_status_tip("Scale the editor in proportion to the current font");
  283. m_scale_ten_action = GUI::Action::create_checkable("1000%", { Mod_Ctrl, Key_2 }, [&](auto&) {
  284. m_glyph_editor_widget->set_scale(10);
  285. did_resize_glyph_editor();
  286. });
  287. m_scale_ten_action->set_checked(true);
  288. m_scale_ten_action->set_status_tip("Scale the editor in proportion to the current font");
  289. m_scale_fifteen_action = GUI::Action::create_checkable("1500%", { Mod_Ctrl, Key_3 }, [&](auto&) {
  290. m_glyph_editor_widget->set_scale(15);
  291. did_resize_glyph_editor();
  292. });
  293. m_scale_fifteen_action->set_checked(false);
  294. m_scale_fifteen_action->set_status_tip("Scale the editor in proportion to the current font");
  295. m_glyph_editor_scale_actions.add_action(*m_scale_five_action);
  296. m_glyph_editor_scale_actions.add_action(*m_scale_ten_action);
  297. m_glyph_editor_scale_actions.add_action(*m_scale_fifteen_action);
  298. m_glyph_editor_scale_actions.set_exclusive(true);
  299. m_paint_glyph_action = GUI::Action::create_checkable("Paint Glyph", { Mod_Ctrl, KeyCode::Key_J }, Gfx::Bitmap::try_load_from_file("/res/icons/pixelpaint/pen.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  300. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Paint);
  301. });
  302. m_paint_glyph_action->set_checked(true);
  303. m_move_glyph_action = GUI::Action::create_checkable("Move Glyph", { Mod_Ctrl, KeyCode::Key_K }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/selection-move.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  304. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Move);
  305. });
  306. m_glyph_tool_actions.add_action(*m_move_glyph_action);
  307. m_glyph_tool_actions.add_action(*m_paint_glyph_action);
  308. m_glyph_tool_actions.set_exclusive(true);
  309. glyph_mode_toolbar.add_action(*m_paint_glyph_action);
  310. glyph_mode_toolbar.add_action(*m_move_glyph_action);
  311. m_rotate_counterclockwise_action = GUI::CommonActions::make_rotate_counterclockwise_action([&](auto&) {
  312. m_glyph_editor_widget->rotate_90(GlyphEditorWidget::Counterclockwise);
  313. });
  314. m_rotate_clockwise_action = GUI::CommonActions::make_rotate_clockwise_action([&](auto&) {
  315. m_glyph_editor_widget->rotate_90(GlyphEditorWidget::Clockwise);
  316. });
  317. m_flip_horizontal_action = GUI::Action::create("Flip Horizontally", { Mod_Ctrl | Mod_Shift, Key_A }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-horizontal.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  318. m_glyph_editor_widget->flip_horizontally();
  319. });
  320. m_flip_vertical_action = GUI::Action::create("Flip Vertically", { Mod_Ctrl | Mod_Shift, Key_S }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-flip-vertical.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  321. m_glyph_editor_widget->flip_vertically();
  322. });
  323. m_copy_character_action = GUI::Action::create("Cop&y as Character", { Mod_Ctrl | Mod_Shift, Key_C }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-copy.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
  324. StringBuilder glyph_builder;
  325. glyph_builder.append_code_point(m_glyph_editor_widget->glyph());
  326. GUI::Clipboard::the().set_plain_text(glyph_builder.build());
  327. });
  328. glyph_transform_toolbar.add_action(*m_flip_horizontal_action);
  329. glyph_transform_toolbar.add_action(*m_flip_vertical_action);
  330. glyph_transform_toolbar.add_action(*m_rotate_counterclockwise_action);
  331. glyph_transform_toolbar.add_action(*m_rotate_clockwise_action);
  332. GUI::Clipboard::the().on_change = [&](String const& data_type) {
  333. m_paste_action->set_enabled(data_type == "glyph/x-fonteditor");
  334. };
  335. m_glyph_editor_widget->on_glyph_altered = [this](int glyph) {
  336. m_glyph_map_widget->update_glyph(glyph);
  337. update_preview();
  338. did_modify_font();
  339. };
  340. m_glyph_editor_widget->on_undo_event = [this] {
  341. m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
  342. };
  343. m_glyph_map_widget->on_glyph_selected = [this](int glyph) {
  344. if (m_undo_glyph)
  345. m_undo_glyph->set_code_point(glyph);
  346. m_glyph_editor_widget->set_glyph(glyph);
  347. auto glyph_width = m_edited_font->raw_glyph_width(glyph);
  348. if (m_edited_font->is_fixed_width())
  349. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  350. else
  351. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  352. update_statusbar();
  353. };
  354. m_name_textbox->on_change = [&] {
  355. m_edited_font->set_name(m_name_textbox->text());
  356. did_modify_font();
  357. };
  358. m_family_textbox->on_change = [&] {
  359. m_edited_font->set_family(m_family_textbox->text());
  360. did_modify_font();
  361. };
  362. m_fixed_width_checkbox->on_checked = [this](bool checked) {
  363. m_edited_font->set_fixed_width(checked);
  364. auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
  365. m_glyph_editor_width_spinbox->set_visible(!checked);
  366. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  367. m_glyph_editor_present_checkbox->set_visible(checked);
  368. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  369. m_glyph_editor_widget->update();
  370. update_preview();
  371. did_modify_font();
  372. };
  373. m_glyph_editor_width_spinbox->on_change = [this](int value) {
  374. m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
  375. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), value);
  376. m_glyph_editor_widget->update();
  377. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  378. update_preview();
  379. update_statusbar();
  380. did_modify_font();
  381. };
  382. m_glyph_editor_present_checkbox->on_checked = [this](bool checked) {
  383. m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
  384. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
  385. m_glyph_editor_widget->update();
  386. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  387. update_preview();
  388. update_statusbar();
  389. did_modify_font();
  390. };
  391. m_weight_combobox->on_change = [this](auto&, auto&) {
  392. m_edited_font->set_weight(Gfx::name_to_weight(m_weight_combobox->text()));
  393. did_modify_font();
  394. };
  395. for (auto& it : Gfx::font_weight_names)
  396. m_font_weight_list.append(it.name);
  397. m_weight_combobox->set_model(*GUI::ItemListModel<String>::create(m_font_weight_list));
  398. m_slope_combobox->on_change = [this](auto&, auto&) {
  399. m_edited_font->set_slope(Gfx::name_to_slope(m_slope_combobox->text()));
  400. did_modify_font();
  401. };
  402. for (auto& it : Gfx::font_slope_names)
  403. m_font_slope_list.append(it.name);
  404. m_slope_combobox->set_model(*GUI::ItemListModel<String>::create(m_font_slope_list));
  405. m_presentation_spinbox->on_change = [this](int value) {
  406. m_edited_font->set_presentation_size(value);
  407. update_preview();
  408. did_modify_font();
  409. };
  410. m_spacing_spinbox->on_change = [this](int value) {
  411. m_edited_font->set_glyph_spacing(value);
  412. update_preview();
  413. did_modify_font();
  414. };
  415. m_baseline_spinbox->on_change = [this](int value) {
  416. m_edited_font->set_baseline(value);
  417. m_glyph_editor_widget->update();
  418. update_preview();
  419. did_modify_font();
  420. };
  421. m_mean_line_spinbox->on_change = [this](int value) {
  422. m_edited_font->set_mean_line(value);
  423. m_glyph_editor_widget->update();
  424. update_preview();
  425. did_modify_font();
  426. };
  427. GUI::Application::the()->on_action_enter = [this](GUI::Action& action) {
  428. auto text = action.status_tip();
  429. if (text.is_empty())
  430. text = Gfx::parse_ampersand_string(action.text());
  431. m_statusbar->set_override_text(move(text));
  432. };
  433. GUI::Application::the()->on_action_leave = [this](GUI::Action&) {
  434. m_statusbar->set_override_text({});
  435. };
  436. }
  437. FontEditorWidget::~FontEditorWidget()
  438. {
  439. }
  440. void FontEditorWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&& edited_font)
  441. {
  442. if (m_edited_font == edited_font)
  443. return;
  444. m_path = path;
  445. m_edited_font = edited_font;
  446. m_glyph_map_widget->initialize(*m_edited_font);
  447. m_glyph_editor_widget->initialize(*m_edited_font);
  448. did_resize_glyph_editor();
  449. m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width());
  450. m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width(), GUI::AllowCallback::No);
  451. m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
  452. m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width());
  453. m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
  454. m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width(), GUI::AllowCallback::No);
  455. m_name_textbox->set_text(m_edited_font->name(), GUI::AllowCallback::No);
  456. m_family_textbox->set_text(m_edited_font->family(), GUI::AllowCallback::No);
  457. m_presentation_spinbox->set_value(m_edited_font->presentation_size(), GUI::AllowCallback::No);
  458. m_spacing_spinbox->set_value(m_edited_font->glyph_spacing(), GUI::AllowCallback::No);
  459. m_mean_line_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No);
  460. m_baseline_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), GUI::AllowCallback::No);
  461. m_mean_line_spinbox->set_value(m_edited_font->mean_line(), GUI::AllowCallback::No);
  462. m_baseline_spinbox->set_value(m_edited_font->baseline(), GUI::AllowCallback::No);
  463. int i = 0;
  464. for (auto& it : Gfx::font_weight_names) {
  465. if (it.style == m_edited_font->weight()) {
  466. m_weight_combobox->set_selected_index(i, GUI::AllowCallback::No);
  467. break;
  468. }
  469. i++;
  470. }
  471. i = 0;
  472. for (auto& it : Gfx::font_slope_names) {
  473. if (it.style == m_edited_font->slope()) {
  474. m_slope_combobox->set_selected_index(i, GUI::AllowCallback::No);
  475. break;
  476. }
  477. i++;
  478. }
  479. deferred_invoke([this] {
  480. m_glyph_map_widget->set_focus(true);
  481. m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->selected_glyph());
  482. update_title();
  483. });
  484. m_undo_stack = make<GUI::UndoStack>();
  485. m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->selected_glyph(), *m_edited_font));
  486. m_undo_stack->on_state_change = [this] {
  487. m_undo_action->set_enabled(m_undo_stack->can_undo());
  488. m_redo_action->set_enabled(m_undo_stack->can_redo());
  489. did_modify_font();
  490. };
  491. m_undo_action->set_enabled(false);
  492. m_redo_action->set_enabled(false);
  493. update_statusbar();
  494. if (on_initialize)
  495. on_initialize();
  496. }
  497. void FontEditorWidget::initialize_menubar(GUI::Window& window)
  498. {
  499. auto& file_menu = window.add_menu("&File");
  500. file_menu.add_action(*m_new_action);
  501. file_menu.add_action(*m_open_action);
  502. file_menu.add_action(*m_save_action);
  503. file_menu.add_action(*m_save_as_action);
  504. file_menu.add_separator();
  505. file_menu.add_action(GUI::CommonActions::make_quit_action([this](auto&) {
  506. if (!request_close())
  507. return;
  508. GUI::Application::the()->quit();
  509. }));
  510. auto& edit_menu = window.add_menu("&Edit");
  511. edit_menu.add_action(*m_undo_action);
  512. edit_menu.add_action(*m_redo_action);
  513. edit_menu.add_separator();
  514. edit_menu.add_action(*m_cut_action);
  515. edit_menu.add_action(*m_copy_action);
  516. edit_menu.add_action(*m_paste_action);
  517. edit_menu.add_action(*m_delete_action);
  518. edit_menu.add_separator();
  519. edit_menu.add_action(*m_copy_character_action);
  520. edit_menu.add_separator();
  521. edit_menu.add_action(*m_previous_glyph_action);
  522. edit_menu.add_action(*m_next_glyph_action);
  523. edit_menu.add_action(*m_go_to_glyph_action);
  524. auto& view_menu = window.add_menu("&View");
  525. view_menu.add_action(*m_open_preview_action);
  526. view_menu.add_separator();
  527. view_menu.add_action(*m_show_metadata_action);
  528. view_menu.add_separator();
  529. auto& scale_menu = view_menu.add_submenu("&Scale");
  530. scale_menu.add_action(*m_scale_five_action);
  531. scale_menu.add_action(*m_scale_ten_action);
  532. scale_menu.add_action(*m_scale_fifteen_action);
  533. auto& help_menu = window.add_menu("&Help");
  534. help_menu.add_action(GUI::CommonActions::make_help_action([](auto&) {
  535. Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/FontEditor.md"), "/bin/Help");
  536. }));
  537. help_menu.add_action(GUI::CommonActions::make_about_action("Font Editor", GUI::Icon::default_icon("app-font-editor"), &window));
  538. }
  539. bool FontEditorWidget::save_as(String const& path)
  540. {
  541. auto saved_font = m_edited_font->masked_character_set();
  542. auto ret_val = saved_font->write_to_file(path);
  543. if (!ret_val) {
  544. GUI::MessageBox::show(window(), "The font file could not be saved.", "Save failed", GUI::MessageBox::Type::Error);
  545. return false;
  546. }
  547. m_path = path;
  548. m_undo_stack->set_current_unmodified();
  549. window()->set_modified(false);
  550. update_title();
  551. return true;
  552. }
  553. void FontEditorWidget::set_show_font_metadata(bool show)
  554. {
  555. if (m_font_metadata == show)
  556. return;
  557. m_font_metadata = show;
  558. m_font_metadata_groupbox->set_visible(m_font_metadata);
  559. }
  560. bool FontEditorWidget::open_file(String const& path)
  561. {
  562. auto bitmap_font = Gfx::BitmapFont::load_from_file(path);
  563. if (!bitmap_font) {
  564. String message = String::formatted("Couldn't load font: {}\n", path);
  565. GUI::MessageBox::show(window(), message, "Font Editor", GUI::MessageBox::Type::Error);
  566. return false;
  567. }
  568. auto new_font = bitmap_font->unmasked_character_set();
  569. window()->set_modified(false);
  570. initialize(path, move(new_font));
  571. return true;
  572. }
  573. void FontEditorWidget::undo()
  574. {
  575. if (!m_undo_stack->can_undo())
  576. return;
  577. m_undo_stack->undo();
  578. auto glyph = m_undo_glyph->restored_code_point();
  579. auto glyph_width = m_undo_glyph->restored_width();
  580. m_glyph_map_widget->set_selected_glyph(glyph);
  581. m_glyph_map_widget->scroll_to_glyph(glyph);
  582. if (m_edited_font->is_fixed_width()) {
  583. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  584. } else {
  585. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  586. }
  587. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
  588. m_glyph_editor_widget->update();
  589. m_glyph_map_widget->update_glyph(glyph);
  590. update_preview();
  591. update_statusbar();
  592. }
  593. void FontEditorWidget::redo()
  594. {
  595. if (!m_undo_stack->can_redo())
  596. return;
  597. m_undo_stack->redo();
  598. auto glyph = m_undo_glyph->restored_code_point();
  599. auto glyph_width = m_undo_glyph->restored_width();
  600. m_glyph_map_widget->set_selected_glyph(glyph);
  601. m_glyph_map_widget->scroll_to_glyph(glyph);
  602. if (m_edited_font->is_fixed_width()) {
  603. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
  604. } else {
  605. m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
  606. }
  607. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
  608. m_glyph_editor_widget->update();
  609. m_glyph_map_widget->update_glyph(glyph);
  610. update_preview();
  611. update_statusbar();
  612. }
  613. bool FontEditorWidget::request_close()
  614. {
  615. if (!window()->is_modified())
  616. return true;
  617. auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), m_path, m_undo_stack->last_unmodified_timestamp());
  618. if (result == GUI::MessageBox::ExecYes) {
  619. m_save_action->activate();
  620. if (!window()->is_modified())
  621. return true;
  622. }
  623. if (result == GUI::MessageBox::ExecNo)
  624. return true;
  625. return false;
  626. }
  627. void FontEditorWidget::update_title()
  628. {
  629. StringBuilder title;
  630. if (m_path.is_empty())
  631. title.append("Untitled");
  632. else
  633. title.append(m_path);
  634. title.append("[*] - Font Editor");
  635. window()->set_title(title.to_string());
  636. }
  637. void FontEditorWidget::did_modify_font()
  638. {
  639. if (!window() || window()->is_modified())
  640. return;
  641. window()->set_modified(true);
  642. update_title();
  643. }
  644. void FontEditorWidget::update_statusbar()
  645. {
  646. auto glyph = m_glyph_map_widget->selected_glyph();
  647. StringBuilder builder;
  648. builder.appendff("U+{:04X} (", glyph);
  649. if (AK::UnicodeUtils::is_unicode_control_code_point(glyph)) {
  650. builder.append(AK::UnicodeUtils::get_unicode_control_code_point_alias(glyph).value());
  651. } else if (Gfx::get_char_bidi_class(glyph) == Gfx::BidirectionalClass::STRONG_RTL) {
  652. // FIXME: This is a necessary hack, as RTL text will mess up the painting of the statusbar text.
  653. // For now, replace RTL glyphs with U+FFFD, the replacement character.
  654. builder.append_code_point(0xFFFD);
  655. } else {
  656. builder.append_code_point(glyph);
  657. }
  658. builder.append(")");
  659. auto glyph_name = Unicode::code_point_display_name(glyph);
  660. if (glyph_name.has_value()) {
  661. builder.appendff(" {}", glyph_name.value());
  662. }
  663. if (m_edited_font->contains_raw_glyph(glyph))
  664. builder.appendff(" [{}x{}]", m_edited_font->raw_glyph_width(glyph), m_edited_font->glyph_height());
  665. m_statusbar->set_text(builder.to_string());
  666. }
  667. void FontEditorWidget::update_preview()
  668. {
  669. if (m_font_preview_window)
  670. m_font_preview_window->update();
  671. }
  672. void FontEditorWidget::drop_event(GUI::DropEvent& event)
  673. {
  674. event.accept();
  675. if (event.mime_data().has_urls()) {
  676. auto urls = event.mime_data().urls();
  677. if (urls.is_empty())
  678. return;
  679. window()->move_to_front();
  680. if (!request_close())
  681. return;
  682. open_file(urls.first().path());
  683. }
  684. }
  685. void FontEditorWidget::did_resize_glyph_editor()
  686. {
  687. constexpr int glyph_toolbars_width = 100;
  688. m_glyph_editor_container->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  689. m_left_column_container->set_fixed_width(max(m_glyph_editor_widget->preferred_width(), glyph_toolbars_width));
  690. }