Serenity Operating System
1/*
2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/QuickSort.h>
9#include <LibGUI/Button.h>
10#include <LibGUI/FontPicker.h>
11#include <LibGUI/FontPickerDialogGML.h>
12#include <LibGUI/ItemListModel.h>
13#include <LibGUI/Label.h>
14#include <LibGUI/ListView.h>
15#include <LibGUI/SpinBox.h>
16#include <LibGUI/Widget.h>
17#include <LibGfx/Font/FontDatabase.h>
18
19namespace GUI {
20
21FontPicker::FontPicker(Window* parent_window, Gfx::Font const* current_font, bool fixed_width_only)
22 : Dialog(parent_window)
23 , m_fixed_width_only(fixed_width_only)
24{
25 set_title("Font picker");
26 resize(430, 280);
27 set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-font-editor.png"sv).release_value_but_fixme_should_propagate_errors());
28
29 auto widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
30 widget->load_from_gml(font_picker_dialog_gml).release_value_but_fixme_should_propagate_errors();
31
32 m_family_list_view = *widget->find_descendant_of_type_named<ListView>("family_list_view");
33 m_family_list_view->set_model(ItemListModel<DeprecatedString>::create(m_families));
34 m_family_list_view->horizontal_scrollbar().set_visible(false);
35
36 m_variant_list_view = *widget->find_descendant_of_type_named<ListView>("variant_list_view");
37 m_variant_list_view->set_model(ItemListModel<DeprecatedString>::create(m_variants));
38 m_variant_list_view->horizontal_scrollbar().set_visible(false);
39
40 m_size_spin_box = *widget->find_descendant_of_type_named<SpinBox>("size_spin_box");
41 m_size_spin_box->set_range(1, 255);
42
43 m_size_list_view = *widget->find_descendant_of_type_named<ListView>("size_list_view");
44 m_size_list_view->set_model(ItemListModel<int>::create(m_sizes));
45 m_size_list_view->horizontal_scrollbar().set_visible(false);
46
47 m_sample_text_label = *widget->find_descendant_of_type_named<Label>("sample_text_label");
48
49 m_families.clear();
50 Gfx::FontDatabase::the().for_each_typeface([&](auto& typeface) {
51 if (m_fixed_width_only && !typeface.is_fixed_width())
52 return;
53 if (!m_families.contains_slow(typeface.family()))
54 m_families.append(typeface.family());
55 });
56 quick_sort(m_families);
57
58 m_family_list_view->on_selection_change = [this] {
59 const auto& index = m_family_list_view->selection().first();
60 m_family = index.data().to_deprecated_string();
61 m_variants.clear();
62 Gfx::FontDatabase::the().for_each_typeface([&](auto& typeface) {
63 if (m_fixed_width_only && !typeface.is_fixed_width())
64 return;
65 if (typeface.family() == m_family.value() && !m_variants.contains_slow(typeface.variant()))
66 m_variants.append(typeface.variant());
67 });
68 quick_sort(m_variants);
69 Optional<size_t> index_of_old_variant_in_new_list;
70 if (m_variant.has_value())
71 index_of_old_variant_in_new_list = m_variants.find_first_index(m_variant.value());
72
73 m_variant_list_view->model()->invalidate();
74 m_variant_list_view->set_cursor(m_variant_list_view->model()->index(index_of_old_variant_in_new_list.value_or(0)), GUI::AbstractView::SelectionUpdate::Set);
75 update_font();
76 };
77
78 m_variant_list_view->on_selection_change = [this] {
79 const auto& index = m_variant_list_view->selection().first();
80 bool font_is_fixed_size = false;
81 m_variant = index.data().to_deprecated_string();
82 m_sizes.clear();
83 Gfx::FontDatabase::the().for_each_typeface([&](auto& typeface) {
84 if (m_fixed_width_only && !typeface.is_fixed_width())
85 return;
86 if (typeface.family() == m_family.value() && typeface.variant() == m_variant.value()) {
87 font_is_fixed_size = typeface.is_fixed_size();
88 if (font_is_fixed_size) {
89 m_size_spin_box->set_visible(false);
90
91 typeface.for_each_fixed_size_font([&](auto& font) {
92 m_sizes.append(font.presentation_size());
93 });
94 } else {
95 m_size_spin_box->set_visible(true);
96
97 m_sizes.append(8);
98 m_sizes.append(9);
99 m_sizes.append(10);
100 m_sizes.append(11);
101 m_sizes.append(12);
102 m_sizes.append(14);
103 m_sizes.append(16);
104 m_sizes.append(18);
105 m_sizes.append(20);
106 m_sizes.append(22);
107 m_sizes.append(24);
108 m_sizes.append(36);
109 }
110 }
111 });
112 quick_sort(m_sizes);
113 m_size_list_view->model()->invalidate();
114 m_size_list_view->set_selection_mode(GUI::AbstractView::SelectionMode::SingleSelection);
115
116 if (m_size.has_value()) {
117 Optional<size_t> index_of_old_size_in_new_list = m_sizes.find_first_index(m_size.value());
118 if (index_of_old_size_in_new_list.has_value()) {
119 m_size_list_view->set_cursor(m_size_list_view->model()->index(index_of_old_size_in_new_list.value()), GUI::AbstractView::SelectionUpdate::Set);
120 } else {
121 if (font_is_fixed_size) {
122 m_size_list_view->set_cursor(m_size_list_view->model()->index(0), GUI::AbstractView::SelectionUpdate::Set);
123 } else {
124 m_size_list_view->set_selection_mode(GUI::AbstractView::SelectionMode::NoSelection);
125 m_size_spin_box->set_value(m_size.value());
126 }
127 }
128 } else {
129 m_size_list_view->set_cursor(m_size_list_view->model()->index(0), GUI::AbstractView::SelectionUpdate::Set);
130 }
131 update_font();
132 };
133
134 m_size_list_view->on_selection_change = [this] {
135 const auto& index = m_size_list_view->selection().first();
136 auto size = index.data().to_i32();
137 Optional<size_t> index_of_new_size_in_list = m_sizes.find_first_index(size);
138 if (index_of_new_size_in_list.has_value()) {
139 m_size_list_view->set_selection_mode(GUI::AbstractView::SelectionMode::SingleSelection);
140 m_size = size;
141 m_size_spin_box->set_value(m_size.value());
142 }
143 update_font();
144 };
145
146 m_size_spin_box->on_change = [this](int value) {
147 m_size = value;
148
149 Optional<size_t> index_of_new_size_in_list = m_sizes.find_first_index(m_size.value());
150
151 if (index_of_new_size_in_list.has_value()) {
152 m_size_list_view->set_selection_mode(GUI::AbstractView::SelectionMode::SingleSelection);
153 m_size_list_view->set_cursor(m_size_list_view->model()->index(index_of_new_size_in_list.value()), GUI::AbstractView::SelectionUpdate::Set);
154 } else {
155 m_size_list_view->set_selection_mode(GUI::AbstractView::SelectionMode::NoSelection);
156 }
157
158 update_font();
159 };
160
161 auto& ok_button = *widget->find_descendant_of_type_named<GUI::Button>("ok_button");
162 ok_button.on_click = [this](auto) {
163 done(ExecResult::OK);
164 };
165 ok_button.set_default(true);
166
167 auto& cancel_button = *widget->find_descendant_of_type_named<GUI::Button>("cancel_button");
168 cancel_button.on_click = [this](auto) {
169 done(ExecResult::Cancel);
170 };
171
172 set_font(current_font);
173}
174
175void FontPicker::set_font(Gfx::Font const* font)
176{
177 if (m_font == font)
178 return;
179 m_font = font;
180 m_sample_text_label->set_font(m_font);
181
182 if (!m_font) {
183 m_family = {};
184 m_variant = {};
185 m_size = {};
186 m_variants.clear();
187 m_sizes.clear();
188 m_variant_list_view->model()->invalidate();
189 m_size_list_view->model()->invalidate();
190 return;
191 }
192
193 m_family = font->family();
194 m_variant = font->variant();
195 m_size = font->presentation_size();
196
197 auto family_index = m_families.find_first_index(m_font->family());
198 if (family_index.has_value())
199 m_family_list_view->set_cursor(m_family_list_view->model()->index(family_index.value()), GUI::AbstractView::SelectionUpdate::Set);
200
201 auto variant_index = m_variants.find_first_index(m_font->variant());
202 if (variant_index.has_value())
203 m_variant_list_view->set_cursor(m_variant_list_view->model()->index(variant_index.value()), GUI::AbstractView::SelectionUpdate::Set);
204
205 auto size_index = m_sizes.find_first_index(m_font->presentation_size());
206 if (size_index.has_value())
207 m_size_list_view->set_cursor(m_size_list_view->model()->index(size_index.value()), GUI::AbstractView::SelectionUpdate::Set);
208}
209
210void FontPicker::update_font()
211{
212 if (m_family.has_value() && m_size.has_value() && m_variant.has_value()) {
213 m_font = Gfx::FontDatabase::the().get(m_family.value(), m_variant.value(), m_size.value());
214 m_sample_text_label->set_font(m_font);
215 }
216}
217}