FontEditor.cpp 26 KB

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