FontEditor.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  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 <Applications/FontEditor/FontEditorWindowGML.h>
  12. #include <LibDesktop/Launcher.h>
  13. #include <LibGUI/Action.h>
  14. #include <LibGUI/Application.h>
  15. #include <LibGUI/BoxLayout.h>
  16. #include <LibGUI/Button.h>
  17. #include <LibGUI/CheckBox.h>
  18. #include <LibGUI/Clipboard.h>
  19. #include <LibGUI/ComboBox.h>
  20. #include <LibGUI/FilePicker.h>
  21. #include <LibGUI/FontPickerWeightModel.h>
  22. #include <LibGUI/GroupBox.h>
  23. #include <LibGUI/Label.h>
  24. #include <LibGUI/Menu.h>
  25. #include <LibGUI/Menubar.h>
  26. #include <LibGUI/MessageBox.h>
  27. #include <LibGUI/Painter.h>
  28. #include <LibGUI/SpinBox.h>
  29. #include <LibGUI/Statusbar.h>
  30. #include <LibGUI/TextBox.h>
  31. #include <LibGUI/ToolbarContainer.h>
  32. #include <LibGUI/Window.h>
  33. #include <LibGfx/BitmapFont.h>
  34. #include <LibGfx/Palette.h>
  35. #include <stdlib.h>
  36. static constexpr int s_pangram_count = 7;
  37. static const char* pangrams[s_pangram_count] = {
  38. "quick fox jumps nightly above wizard",
  39. "five quacking zephyrs jolt my wax bed",
  40. "pack my box with five dozen liquor jugs",
  41. "quick brown fox jumps over the lazy dog",
  42. "waxy and quivering jocks fumble the pizza",
  43. "~#:[@_1%]*{$2.3}/4^(5'6\")-&|7+8!=<9,0\\>?;",
  44. "byxfjärmat föl gick på duvshowen"
  45. };
  46. static RefPtr<GUI::Window> create_font_preview_window(FontEditorWidget& editor)
  47. {
  48. auto window = GUI::Window::construct();
  49. window->set_window_type(GUI::WindowType::ToolWindow);
  50. window->set_title("Font preview");
  51. window->resize(400, 150);
  52. window->set_minimum_size(200, 100);
  53. window->center_within(*editor.window());
  54. auto& main_widget = window->set_main_widget<GUI::Widget>();
  55. main_widget.set_fill_with_background_color(true);
  56. main_widget.set_layout<GUI::VerticalBoxLayout>();
  57. main_widget.layout()->set_margins({ 2, 2, 2, 2 });
  58. main_widget.layout()->set_spacing(4);
  59. auto& preview_box = main_widget.add<GUI::GroupBox>();
  60. preview_box.set_layout<GUI::VerticalBoxLayout>();
  61. preview_box.layout()->set_margins({ 8, 8, 8, 8 });
  62. auto& preview_label = preview_box.add<GUI::Label>();
  63. preview_label.set_font(editor.edited_font());
  64. editor.on_initialize = [&] {
  65. preview_label.set_font(editor.edited_font());
  66. };
  67. auto& textbox_button_container = main_widget.add<GUI::Widget>();
  68. textbox_button_container.set_layout<GUI::HorizontalBoxLayout>();
  69. textbox_button_container.set_fixed_height(22);
  70. auto& preview_textbox = textbox_button_container.add<GUI::TextBox>();
  71. preview_textbox.set_text(pangrams[0]);
  72. preview_textbox.set_placeholder("Preview text");
  73. preview_textbox.on_change = [&] {
  74. auto preview = String::formatted("{}\n{}",
  75. preview_textbox.text(),
  76. preview_textbox.text().to_uppercase());
  77. preview_label.set_text(preview);
  78. };
  79. auto& reload_button = textbox_button_container.add<GUI::Button>();
  80. reload_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"));
  81. reload_button.set_fixed_width(22);
  82. reload_button.on_click = [&] {
  83. static int i = 1;
  84. if (i >= s_pangram_count)
  85. i = 0;
  86. preview_textbox.set_text(pangrams[i]);
  87. i++;
  88. };
  89. return window;
  90. }
  91. FontEditorWidget::FontEditorWidget(const String& path, RefPtr<Gfx::BitmapFont>&& edited_font)
  92. {
  93. load_from_gml(font_editor_window_gml);
  94. auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  95. auto& statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  96. auto& glyph_map_container = *find_descendant_of_type_named<GUI::Widget>("glyph_map_container");
  97. auto& move_glyph_button = *find_descendant_of_type_named<GUI::Button>("move_glyph_button");
  98. m_glyph_editor_container = *find_descendant_of_type_named<GUI::Widget>("glyph_editor_container");
  99. m_left_column_container = *find_descendant_of_type_named<GUI::Widget>("left_column_container");
  100. m_glyph_editor_width_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("glyph_editor_width_spinbox");
  101. m_glyph_editor_present_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("glyph_editor_present_checkbox");
  102. m_name_textbox = *find_descendant_of_type_named<GUI::TextBox>("name_textbox");
  103. m_family_textbox = *find_descendant_of_type_named<GUI::TextBox>("family_textbox");
  104. m_presentation_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("presentation_spinbox");
  105. m_weight_combobox = *find_descendant_of_type_named<GUI::ComboBox>("weight_combobox");
  106. m_type_combobox = *find_descendant_of_type_named<GUI::ComboBox>("type_combobox");
  107. m_spacing_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("spacing_spinbox");
  108. m_mean_line_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("mean_line_spinbox");
  109. m_baseline_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("baseline_spinbox");
  110. m_fixed_width_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("fixed_width_checkbox");
  111. m_font_metadata_groupbox = *find_descendant_of_type_named<GUI::GroupBox>("font_metadata_groupbox");
  112. m_glyph_editor_widget = m_glyph_editor_container->add<GlyphEditorWidget>();
  113. m_glyph_map_widget = glyph_map_container.add<GlyphMapWidget>();
  114. auto update_statusbar = [&] {
  115. auto glyph = m_glyph_map_widget->selected_glyph();
  116. StringBuilder builder;
  117. builder.appendff("{:#02x} (", glyph);
  118. if (glyph < 128) {
  119. if (glyph == 10)
  120. builder.append("LF");
  121. else
  122. builder.append(glyph);
  123. } else {
  124. builder.append(128 | 64 | (glyph / 64));
  125. builder.append(128 | (glyph % 64));
  126. }
  127. builder.append(") ");
  128. builder.appendff("[{}x{}]", m_edited_font->raw_glyph_width(glyph), m_edited_font->glyph_height());
  129. statusbar.set_text(builder.to_string());
  130. };
  131. auto update_demo = [&] {
  132. if (m_font_preview_window)
  133. m_font_preview_window->update();
  134. };
  135. m_new_action = GUI::Action::create("&New Font...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-font.png"), [&](auto&) {
  136. auto new_font_wizard = NewFontDialog::construct(window());
  137. if (new_font_wizard->exec() == GUI::Dialog::ExecOK) {
  138. auto metadata = new_font_wizard->new_font_metadata();
  139. String name = metadata.name;
  140. auto parts = name.split(' ');
  141. if (parts.size() > 1)
  142. name = parts[0];
  143. parts.clear();
  144. String weight = GUI::weight_to_name(metadata.weight).to_string();
  145. parts = weight.split(' ');
  146. if (parts.size() > 1)
  147. weight = String::formatted("{}{}", parts[0], parts[1]);
  148. RefPtr<Gfx::BitmapFont> new_font = Gfx::BitmapFont::create(metadata.glyph_height, metadata.glyph_width, metadata.is_fixed_width, metadata.type);
  149. String path = String::formatted("/tmp/{}{}{}.font", name, weight, metadata.presentation_size);
  150. if (!new_font) {
  151. String message = String::formatted("Failed to create new font: {}\n", path);
  152. GUI::MessageBox::show(window(), message, "Font Editor", GUI::MessageBox::Type::Error);
  153. return;
  154. }
  155. new_font->set_name(metadata.name);
  156. new_font->set_family(metadata.family);
  157. new_font->set_presentation_size(metadata.presentation_size);
  158. new_font->set_weight(metadata.weight);
  159. new_font->set_baseline(metadata.baseline);
  160. new_font->set_mean_line(metadata.mean_line);
  161. window()->set_title(String::formatted("{} - Font Editor", path));
  162. initialize(path, move(new_font));
  163. }
  164. });
  165. m_new_action->set_status_tip("Create a new font");
  166. m_open_action = GUI::CommonActions::make_open_action([&](auto&) {
  167. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window(), {}, "/res/fonts/");
  168. if (!open_path.has_value())
  169. return;
  170. auto bitmap_font = Gfx::BitmapFont::load_from_file(open_path.value());
  171. if (!bitmap_font) {
  172. String message = String::formatted("Couldn't load font: {}\n", open_path.value());
  173. GUI::MessageBox::show(window(), message, "Font Editor", GUI::MessageBox::Type::Error);
  174. return;
  175. }
  176. RefPtr<Gfx::BitmapFont> new_font = static_ptr_cast<Gfx::BitmapFont>(bitmap_font->clone());
  177. if (!new_font) {
  178. String message = String::formatted("Couldn't load font: {}\n", open_path.value());
  179. GUI::MessageBox::show(window(), message, "Font Editor", GUI::MessageBox::Type::Error);
  180. return;
  181. }
  182. window()->set_title(String::formatted("{} - Font Editor", open_path.value()));
  183. initialize(open_path.value(), move(new_font));
  184. });
  185. m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
  186. save_as(m_path);
  187. });
  188. m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  189. LexicalPath lexical_path(m_path);
  190. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window(), lexical_path.title(), lexical_path.extension());
  191. if (!save_path.has_value())
  192. return;
  193. if (save_as(save_path.value()))
  194. window()->set_title(String::formatted("{} - Font Editor", save_path.value()));
  195. });
  196. m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) {
  197. m_glyph_editor_widget->cut_glyph();
  198. });
  199. m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  200. m_glyph_editor_widget->copy_glyph();
  201. });
  202. m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  203. m_glyph_editor_widget->paste_glyph();
  204. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  205. });
  206. m_paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "glyph/x-fonteditor");
  207. m_delete_action = GUI::CommonActions::make_delete_action([&](auto&) {
  208. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), m_edited_font->max_glyph_width());
  209. m_glyph_editor_widget->delete_glyph();
  210. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  211. auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
  212. m_glyph_editor_width_spinbox->set_value(glyph_width);
  213. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0);
  214. });
  215. m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  216. undo();
  217. });
  218. m_undo_action->set_enabled(false);
  219. m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  220. redo();
  221. });
  222. m_redo_action->set_enabled(false);
  223. m_open_preview_action = GUI::Action::create("&Preview Font", { Mod_Ctrl, Key_P }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [&](auto&) {
  224. if (!m_font_preview_window)
  225. m_font_preview_window = create_font_preview_window(*this);
  226. m_font_preview_window->show();
  227. m_font_preview_window->move_to_front();
  228. });
  229. m_open_preview_action->set_checked(false);
  230. m_open_preview_action->set_status_tip("Preview the current font");
  231. m_show_metadata_action = GUI::Action::create_checkable("Font &Metadata", { Mod_Ctrl, Key_M }, [&](auto& action) {
  232. set_show_font_metadata(action.is_checked());
  233. });
  234. m_show_metadata_action->set_checked(true);
  235. m_show_metadata_action->set_status_tip("Show or hide metadata about the current font");
  236. toolbar.add_action(*m_new_action);
  237. toolbar.add_action(*m_open_action);
  238. toolbar.add_action(*m_save_action);
  239. toolbar.add_separator();
  240. toolbar.add_action(*m_cut_action);
  241. toolbar.add_action(*m_copy_action);
  242. toolbar.add_action(*m_paste_action);
  243. toolbar.add_action(*m_delete_action);
  244. toolbar.add_separator();
  245. toolbar.add_action(*m_undo_action);
  246. toolbar.add_action(*m_redo_action);
  247. toolbar.add_separator();
  248. toolbar.add_action(*m_open_preview_action);
  249. m_scale_five_action = GUI::Action::create_checkable("500%", { Mod_Ctrl, Key_1 }, [&](auto&) {
  250. m_glyph_editor_widget->set_scale(5);
  251. m_glyph_editor_container->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  252. m_left_column_container->set_fixed_width(m_glyph_editor_widget->preferred_width());
  253. });
  254. m_scale_five_action->set_checked(false);
  255. m_scale_five_action->set_status_tip("Scale the editor in proportion to the current font");
  256. m_scale_ten_action = GUI::Action::create_checkable("1000%", { Mod_Ctrl, Key_2 }, [&](auto&) {
  257. m_glyph_editor_widget->set_scale(10);
  258. m_glyph_editor_container->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  259. m_left_column_container->set_fixed_width(m_glyph_editor_widget->preferred_width());
  260. });
  261. m_scale_ten_action->set_checked(true);
  262. m_scale_ten_action->set_status_tip("Scale the editor in proportion to the current font");
  263. m_scale_fifteen_action = GUI::Action::create_checkable("1500%", { Mod_Ctrl, Key_3 }, [&](auto&) {
  264. m_glyph_editor_widget->set_scale(15);
  265. m_glyph_editor_container->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  266. m_left_column_container->set_fixed_width(m_glyph_editor_widget->preferred_width());
  267. });
  268. m_scale_fifteen_action->set_checked(false);
  269. m_scale_fifteen_action->set_status_tip("Scale the editor in proportion to the current font");
  270. m_glyph_editor_scale_actions.add_action(*m_scale_five_action);
  271. m_glyph_editor_scale_actions.add_action(*m_scale_ten_action);
  272. m_glyph_editor_scale_actions.add_action(*m_scale_fifteen_action);
  273. m_glyph_editor_scale_actions.set_exclusive(true);
  274. move_glyph_button.on_click = [&] {
  275. if (move_glyph_button.is_checked())
  276. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Move);
  277. else
  278. m_glyph_editor_widget->set_mode(GlyphEditorWidget::Paint);
  279. };
  280. move_glyph_button.set_checkable(true);
  281. move_glyph_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/selection-move.png"));
  282. GUI::Clipboard::the().on_change = [&](const String& data_type) {
  283. m_paste_action->set_enabled(data_type == "glyph/x-fonteditor");
  284. };
  285. m_glyph_editor_widget->on_glyph_altered = [this, update_demo](int glyph) {
  286. m_glyph_map_widget->update_glyph(glyph);
  287. update_demo();
  288. };
  289. m_glyph_editor_widget->on_undo_event = [this](bool finalize) {
  290. m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
  291. if (finalize)
  292. m_undo_stack->finalize_current_combo();
  293. did_change_undo_stack();
  294. };
  295. m_glyph_map_widget->on_glyph_selected = [&, update_statusbar](int glyph) {
  296. if (m_undo_glyph)
  297. m_undo_glyph->set_code_point(glyph);
  298. m_glyph_editor_widget->set_glyph(glyph);
  299. auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
  300. m_glyph_editor_width_spinbox->set_value(glyph_width);
  301. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0);
  302. update_statusbar();
  303. };
  304. m_name_textbox->on_change = [&] {
  305. m_edited_font->set_name(m_name_textbox->text());
  306. };
  307. m_family_textbox->on_change = [&] {
  308. m_edited_font->set_family(m_family_textbox->text());
  309. };
  310. m_fixed_width_checkbox->on_checked = [&, update_demo](bool checked) {
  311. m_edited_font->set_fixed_width(checked);
  312. auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
  313. m_glyph_editor_width_spinbox->set_visible(!checked);
  314. m_glyph_editor_width_spinbox->set_value(glyph_width);
  315. m_glyph_editor_present_checkbox->set_visible(checked);
  316. m_glyph_editor_present_checkbox->set_checked(glyph_width > 0);
  317. m_glyph_editor_widget->update();
  318. update_demo();
  319. };
  320. m_glyph_editor_width_spinbox->on_change = [this, update_demo, update_statusbar](int value) {
  321. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), value);
  322. m_glyph_editor_widget->update();
  323. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  324. update_demo();
  325. update_statusbar();
  326. };
  327. m_glyph_editor_present_checkbox->on_checked = [this, update_demo, update_statusbar](bool checked) {
  328. if (!m_edited_font->is_fixed_width())
  329. return;
  330. m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
  331. m_glyph_editor_widget->update();
  332. m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
  333. update_demo();
  334. update_statusbar();
  335. };
  336. m_weight_combobox->on_change = [this]() {
  337. m_edited_font->set_weight(GUI::name_to_weight(m_weight_combobox->text()));
  338. };
  339. m_type_combobox->on_change = [this](auto&, const auto& index) {
  340. m_edited_font->set_type(static_cast<Gfx::FontTypes>(index.row()));
  341. m_glyph_map_widget->reprobe_font();
  342. };
  343. m_presentation_spinbox->on_change = [this, update_demo](int value) {
  344. m_edited_font->set_presentation_size(value);
  345. update_demo();
  346. };
  347. m_spacing_spinbox->on_change = [this, update_demo](int value) {
  348. m_edited_font->set_glyph_spacing(value);
  349. update_demo();
  350. };
  351. m_baseline_spinbox->on_change = [this, update_demo](int value) {
  352. m_edited_font->set_baseline(value);
  353. m_glyph_editor_widget->update();
  354. update_demo();
  355. };
  356. m_mean_line_spinbox->on_change = [this, update_demo](int value) {
  357. m_edited_font->set_mean_line(value);
  358. m_glyph_editor_widget->update();
  359. update_demo();
  360. };
  361. GUI::Application::the()->on_action_enter = [&statusbar](GUI::Action& action) {
  362. auto text = action.status_tip();
  363. if (text.is_empty())
  364. text = Gfx::parse_ampersand_string(action.text());
  365. statusbar.set_override_text(move(text));
  366. };
  367. GUI::Application::the()->on_action_leave = [&statusbar](GUI::Action&) {
  368. statusbar.set_override_text({});
  369. };
  370. initialize(path, move(edited_font));
  371. }
  372. FontEditorWidget::~FontEditorWidget()
  373. {
  374. }
  375. void FontEditorWidget::initialize(const String& path, RefPtr<Gfx::BitmapFont>&& edited_font)
  376. {
  377. if (m_edited_font == edited_font)
  378. return;
  379. m_path = path;
  380. m_edited_font = edited_font;
  381. m_glyph_map_widget->initialize(*m_edited_font);
  382. m_glyph_editor_widget->initialize(*m_edited_font);
  383. m_glyph_editor_container->set_fixed_size(m_glyph_editor_widget->preferred_width(), m_glyph_editor_widget->preferred_height());
  384. m_left_column_container->set_fixed_width(m_glyph_editor_widget->preferred_width());
  385. m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width());
  386. m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width());
  387. m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width());
  388. m_name_textbox->set_text(m_edited_font->name());
  389. m_family_textbox->set_text(m_edited_font->family());
  390. m_presentation_spinbox->set_value(m_edited_font->presentation_size());
  391. m_spacing_spinbox->set_value(m_edited_font->glyph_spacing());
  392. m_mean_line_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), false);
  393. m_baseline_spinbox->set_range(0, max(m_edited_font->glyph_height() - 2, 0), false);
  394. m_mean_line_spinbox->set_value(m_edited_font->mean_line());
  395. m_baseline_spinbox->set_value(m_edited_font->baseline());
  396. m_font_weight_list.clear();
  397. for (auto& it : GUI::font_weight_names)
  398. m_font_weight_list.append(it.name);
  399. m_weight_combobox->set_model(*GUI::ItemListModel<String>::create(m_font_weight_list));
  400. int i = 0;
  401. for (auto it : GUI::font_weight_names) {
  402. if (it.weight == m_edited_font->weight()) {
  403. m_weight_combobox->set_selected_index(i);
  404. break;
  405. }
  406. i++;
  407. }
  408. m_font_type_list.clear();
  409. StringBuilder type_count;
  410. for (int i = 0; i < Gfx::FontTypes::__Count; i++) {
  411. type_count.appendff("{}", Gfx::BitmapFont::type_name_by_type(static_cast<Gfx::FontTypes>(i)));
  412. m_font_type_list.append(type_count.to_string());
  413. type_count.clear();
  414. }
  415. m_type_combobox->set_model(*GUI::ItemListModel<String>::create(m_font_type_list));
  416. m_type_combobox->set_selected_index(m_edited_font->type());
  417. m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width());
  418. m_glyph_map_widget->set_selected_glyph('A');
  419. deferred_invoke([this](auto&) {
  420. m_glyph_map_widget->set_focus(true);
  421. m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->selected_glyph());
  422. });
  423. m_undo_stack = make<GUI::UndoStack>();
  424. m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->selected_glyph(), *m_edited_font));
  425. did_change_undo_stack();
  426. if (on_initialize)
  427. on_initialize();
  428. }
  429. void FontEditorWidget::initialize_menubar(GUI::Menubar& menubar)
  430. {
  431. auto& app_menu = menubar.add_menu("&File");
  432. app_menu.add_action(*m_new_action);
  433. app_menu.add_action(*m_open_action);
  434. app_menu.add_action(*m_save_action);
  435. app_menu.add_action(*m_save_as_action);
  436. app_menu.add_separator();
  437. app_menu.add_action(GUI::CommonActions::make_quit_action([this](auto&) {
  438. GUI::Application::the()->quit();
  439. }));
  440. auto& edit_menu = menubar.add_menu("&Edit");
  441. edit_menu.add_action(*m_undo_action);
  442. edit_menu.add_action(*m_redo_action);
  443. edit_menu.add_separator();
  444. edit_menu.add_action(*m_cut_action);
  445. edit_menu.add_action(*m_copy_action);
  446. edit_menu.add_action(*m_paste_action);
  447. edit_menu.add_action(*m_delete_action);
  448. auto& view_menu = menubar.add_menu("&View");
  449. view_menu.add_action(*m_open_preview_action);
  450. view_menu.add_separator();
  451. view_menu.add_action(*m_show_metadata_action);
  452. view_menu.add_separator();
  453. auto& scale_menu = view_menu.add_submenu("&Scale");
  454. scale_menu.add_action(*m_scale_five_action);
  455. scale_menu.add_action(*m_scale_ten_action);
  456. scale_menu.add_action(*m_scale_fifteen_action);
  457. auto& help_menu = menubar.add_menu("&Help");
  458. help_menu.add_action(GUI::CommonActions::make_help_action([](auto&) {
  459. Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/FontEditor.md"), "/bin/Help");
  460. }));
  461. help_menu.add_action(GUI::CommonActions::make_about_action("Font Editor", GUI::Icon::default_icon("app-font-editor"), window()));
  462. }
  463. bool FontEditorWidget::save_as(const String& path)
  464. {
  465. auto ret_val = m_edited_font->write_to_file(path);
  466. if (!ret_val) {
  467. GUI::MessageBox::show(window(), "The font file could not be saved.", "Save failed", GUI::MessageBox::Type::Error);
  468. return false;
  469. }
  470. m_path = path;
  471. return true;
  472. }
  473. void FontEditorWidget::set_show_font_metadata(bool show)
  474. {
  475. if (m_font_metadata == show)
  476. return;
  477. m_font_metadata = show;
  478. m_font_metadata_groupbox->set_visible(m_font_metadata);
  479. }
  480. void FontEditorWidget::undo()
  481. {
  482. if (!m_undo_stack->can_undo())
  483. return;
  484. m_undo_stack->undo();
  485. did_change_undo_stack();
  486. m_glyph_editor_widget->update();
  487. m_glyph_map_widget->update();
  488. if (m_font_preview_window)
  489. m_font_preview_window->update();
  490. }
  491. void FontEditorWidget::redo()
  492. {
  493. if (!m_undo_stack->can_redo())
  494. return;
  495. m_undo_stack->redo();
  496. did_change_undo_stack();
  497. m_glyph_editor_widget->update();
  498. m_glyph_map_widget->update();
  499. if (m_font_preview_window)
  500. m_font_preview_window->update();
  501. }
  502. void FontEditorWidget::did_change_undo_stack()
  503. {
  504. m_undo_action->set_enabled(m_undo_stack->can_undo());
  505. m_redo_action->set_enabled(m_undo_stack->can_redo());
  506. }