1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2011 Alberto Ruiz <aruiz@gnome.org> |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include <stdlib.h> |
21 | #include <glib/gprintf.h> |
22 | #include <string.h> |
23 | |
24 | #include "gtkfontchooserwidget.h" |
25 | #include "gtkfontchooserwidgetprivate.h" |
26 | |
27 | #include "gtkadjustment.h" |
28 | #include "gtkbuildable.h" |
29 | #include "gtkbox.h" |
30 | #include "gtkbinlayout.h" |
31 | #include "gtkcheckbutton.h" |
32 | #include "gtkcustomfilter.h" |
33 | #include "gtkentry.h" |
34 | #include "gtkfilter.h" |
35 | #include "gtkframe.h" |
36 | #include "gtkgrid.h" |
37 | #include "gtkfontchooser.h" |
38 | #include "gtkfontchooserutils.h" |
39 | #include "gtkintl.h" |
40 | #include "gtklabel.h" |
41 | #include "gtksingleselection.h" |
42 | #include "gtkstack.h" |
43 | #include "gtkprivate.h" |
44 | #include "gtkscale.h" |
45 | #include "gtkscrolledwindow.h" |
46 | #include "gtksearchentry.h" |
47 | #include "gtkspinbutton.h" |
48 | #include "gtktextview.h" |
49 | #include "gtkwidgetprivate.h" |
50 | #include "gtksettings.h" |
51 | #include "gtkdialog.h" |
52 | #include "gtkgestureclick.h" |
53 | #include "gtkeventcontrollerscroll.h" |
54 | #include "gtkroot.h" |
55 | #include "gtkfilterlistmodel.h" |
56 | #include "gtkflattenlistmodel.h" |
57 | #include "gtkslicelistmodel.h" |
58 | #include "gtkmaplistmodel.h" |
59 | #include "gtklistitem.h" |
60 | #include "gtksignallistitemfactory.h" |
61 | #include "gtkstringlist.h" |
62 | #include "gtklistview.h" |
63 | #include "gtksortlistmodel.h" |
64 | #include "gtkstringsorter.h" |
65 | |
66 | #include <hb-ot.h> |
67 | |
68 | #include "language-names.h" |
69 | #include "open-type-layout.h" |
70 | |
71 | /** |
72 | * GtkFontChooserWidget: |
73 | * |
74 | * The `GtkFontChooserWidget` widget lets the user select a font. |
75 | * |
76 | * It is used in the `GtkFontChooserDialog` widget to provide a |
77 | * dialog for selecting fonts. |
78 | * |
79 | * To set the font which is initially selected, use |
80 | * [method@Gtk.FontChooser.set_font] or [method@Gtk.FontChooser.set_font_desc]. |
81 | * |
82 | * To get the selected font use [method@Gtk.FontChooser.get_font] or |
83 | * [method@Gtk.FontChooser.get_font_desc]. |
84 | * |
85 | * To change the text which is shown in the preview area, use |
86 | * [method@Gtk.FontChooser.set_preview_text]. |
87 | * |
88 | * # CSS nodes |
89 | * |
90 | * `GtkFontChooserWidget` has a single CSS node with name fontchooser. |
91 | */ |
92 | |
93 | typedef struct _GtkFontChooserWidgetClass GtkFontChooserWidgetClass; |
94 | |
95 | struct _GtkFontChooserWidget |
96 | { |
97 | GtkWidget parent_instance; |
98 | |
99 | GtkWidget *stack; |
100 | GtkWidget *grid; |
101 | GtkWidget *search_entry; |
102 | GtkWidget *family_face_list; |
103 | GtkWidget *list_stack; |
104 | GtkSingleSelection *selection; |
105 | GtkCustomFilter *custom_filter; |
106 | GtkCustomFilter *user_filter; |
107 | GtkFilterListModel *filter_model; |
108 | |
109 | GtkWidget *preview; |
110 | GtkWidget *preview2; |
111 | GtkWidget *font_name_label; |
112 | char *preview_text; |
113 | gboolean show_preview_entry; |
114 | gboolean preview_text_set; |
115 | |
116 | GtkWidget *size_label; |
117 | GtkWidget *size_spin; |
118 | GtkWidget *size_slider; |
119 | GtkWidget *size_slider2; |
120 | |
121 | GtkWidget *axis_grid; |
122 | GtkWidget *feature_box; |
123 | |
124 | GtkFrame *language_button; |
125 | GtkFrame *language_frame; |
126 | GtkWidget *language_list; |
127 | GtkStringList *languages; |
128 | GHashTable *language_table; |
129 | |
130 | PangoLanguage *filter_language; |
131 | gboolean filter_by_language; |
132 | gboolean filter_by_monospace; |
133 | |
134 | PangoFontMap *font_map; |
135 | |
136 | PangoFontDescription *font_desc; |
137 | char *font_features; |
138 | PangoLanguage *language; |
139 | |
140 | GtkFontFilterFunc filter_func; |
141 | gpointer filter_data; |
142 | GDestroyNotify filter_data_destroy; |
143 | |
144 | guint last_fontconfig_timestamp; |
145 | |
146 | GtkFontChooserLevel level; |
147 | |
148 | GHashTable *axes; |
149 | gboolean updating_variations; |
150 | |
151 | GList *feature_items; |
152 | |
153 | GAction *tweak_action; |
154 | |
155 | hb_map_t *glyphmap; |
156 | }; |
157 | |
158 | struct _GtkFontChooserWidgetClass |
159 | { |
160 | GtkWidgetClass parent_class; |
161 | }; |
162 | |
163 | enum { |
164 | PROP_ZERO, |
165 | PROP_TWEAK_ACTION |
166 | }; |
167 | |
168 | static void gtk_font_chooser_widget_set_property (GObject *object, |
169 | guint prop_id, |
170 | const GValue *value, |
171 | GParamSpec *pspec); |
172 | static void gtk_font_chooser_widget_get_property (GObject *object, |
173 | guint prop_id, |
174 | GValue *value, |
175 | GParamSpec *pspec); |
176 | static void gtk_font_chooser_widget_finalize (GObject *object); |
177 | |
178 | static char *gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser); |
179 | static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, |
180 | const char *fontname); |
181 | |
182 | static PangoFontDescription *gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser); |
183 | static void gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget *fontchooser, |
184 | const PangoFontDescription *font_desc); |
185 | static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, |
186 | PangoFontDescription *font_desc); |
187 | |
188 | |
189 | static const char *gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser); |
190 | static void gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser, |
191 | const char *text); |
192 | |
193 | static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser); |
194 | static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser, |
195 | gboolean show_preview_entry); |
196 | |
197 | static void gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser); |
198 | static void gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser, |
199 | GtkFontChooserLevel level); |
200 | static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser); |
201 | static void gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser, |
202 | const char *language); |
203 | static void update_font_features (GtkFontChooserWidget *fontchooser); |
204 | |
205 | static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface); |
206 | |
207 | G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE_WIDGET, |
208 | G_IMPLEMENT_INTERFACE (GTK_TYPE_FONT_CHOOSER, |
209 | gtk_font_chooser_widget_iface_init)) |
210 | |
211 | static void |
212 | gtk_font_chooser_widget_set_property (GObject *object, |
213 | guint prop_id, |
214 | const GValue *value, |
215 | GParamSpec *pspec) |
216 | { |
217 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); |
218 | |
219 | switch (prop_id) |
220 | { |
221 | case GTK_FONT_CHOOSER_PROP_FONT: |
222 | gtk_font_chooser_widget_set_font (fontchooser, fontname: g_value_get_string (value)); |
223 | break; |
224 | case GTK_FONT_CHOOSER_PROP_FONT_DESC: |
225 | gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc: g_value_dup_boxed (value)); |
226 | break; |
227 | case GTK_FONT_CHOOSER_PROP_PREVIEW_TEXT: |
228 | gtk_font_chooser_widget_set_preview_text (fontchooser, text: g_value_get_string (value)); |
229 | fontchooser->preview_text_set = TRUE; |
230 | break; |
231 | case GTK_FONT_CHOOSER_PROP_SHOW_PREVIEW_ENTRY: |
232 | gtk_font_chooser_widget_set_show_preview_entry (fontchooser, show_preview_entry: g_value_get_boolean (value)); |
233 | break; |
234 | case GTK_FONT_CHOOSER_PROP_LEVEL: |
235 | gtk_font_chooser_widget_set_level (fontchooser, level: g_value_get_flags (value)); |
236 | break; |
237 | case GTK_FONT_CHOOSER_PROP_LANGUAGE: |
238 | gtk_font_chooser_widget_set_language (fontchooser, language: g_value_get_string (value)); |
239 | break; |
240 | default: |
241 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
242 | break; |
243 | } |
244 | } |
245 | |
246 | static void |
247 | gtk_font_chooser_widget_get_property (GObject *object, |
248 | guint prop_id, |
249 | GValue *value, |
250 | GParamSpec *pspec) |
251 | { |
252 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); |
253 | |
254 | switch (prop_id) |
255 | { |
256 | case PROP_TWEAK_ACTION: |
257 | g_value_set_object (value, G_OBJECT (fontchooser->tweak_action)); |
258 | break; |
259 | case GTK_FONT_CHOOSER_PROP_FONT: |
260 | g_value_take_string (value, v_string: gtk_font_chooser_widget_get_font (fontchooser)); |
261 | break; |
262 | case GTK_FONT_CHOOSER_PROP_FONT_DESC: |
263 | g_value_set_boxed (value, v_boxed: gtk_font_chooser_widget_get_font_desc (fontchooser)); |
264 | break; |
265 | case GTK_FONT_CHOOSER_PROP_PREVIEW_TEXT: |
266 | g_value_set_string (value, v_string: gtk_font_chooser_widget_get_preview_text (fontchooser)); |
267 | break; |
268 | case GTK_FONT_CHOOSER_PROP_SHOW_PREVIEW_ENTRY: |
269 | g_value_set_boolean (value, v_boolean: gtk_font_chooser_widget_get_show_preview_entry (fontchooser)); |
270 | break; |
271 | case GTK_FONT_CHOOSER_PROP_LEVEL: |
272 | g_value_set_flags (value, v_flags: gtk_font_chooser_widget_get_level (fontchooser)); |
273 | break; |
274 | case GTK_FONT_CHOOSER_PROP_FONT_FEATURES: |
275 | g_value_set_string (value, v_string: fontchooser->font_features); |
276 | break; |
277 | case GTK_FONT_CHOOSER_PROP_LANGUAGE: |
278 | g_value_set_string (value, pango_language_to_string (fontchooser->language)); |
279 | break; |
280 | default: |
281 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
282 | break; |
283 | } |
284 | } |
285 | |
286 | static void |
287 | stop_search_cb (GtkSearchEntry *entry, |
288 | GtkFontChooserWidget *fc) |
289 | { |
290 | if (gtk_editable_get_text (GTK_EDITABLE (entry))[0] != 0) |
291 | gtk_editable_set_text (GTK_EDITABLE (entry), text: "" ); |
292 | else |
293 | { |
294 | GtkWidget *dialog; |
295 | GtkWidget *button = NULL; |
296 | |
297 | dialog = gtk_widget_get_ancestor (GTK_WIDGET (fc), GTK_TYPE_DIALOG); |
298 | if (dialog) |
299 | button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), response_id: GTK_RESPONSE_CANCEL); |
300 | |
301 | if (button) |
302 | gtk_widget_activate (widget: button); |
303 | } |
304 | } |
305 | |
306 | static void |
307 | size_change_cb (GtkAdjustment *adjustment, |
308 | gpointer user_data) |
309 | { |
310 | GtkFontChooserWidget *fontchooser = user_data; |
311 | PangoFontDescription *font_desc; |
312 | double size = gtk_adjustment_get_value (adjustment); |
313 | |
314 | font_desc = pango_font_description_new (); |
315 | if (pango_font_description_get_size_is_absolute (desc: fontchooser->font_desc)) |
316 | pango_font_description_set_absolute_size (desc: font_desc, size: size * PANGO_SCALE); |
317 | else |
318 | pango_font_description_set_size (desc: font_desc, size: size * PANGO_SCALE); |
319 | |
320 | gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); |
321 | } |
322 | |
323 | static gboolean |
324 | output_cb (GtkSpinButton *spin, |
325 | gpointer data) |
326 | { |
327 | GtkAdjustment *adjustment; |
328 | char *text; |
329 | double value; |
330 | |
331 | adjustment = gtk_spin_button_get_adjustment (spin_button: spin); |
332 | value = gtk_adjustment_get_value (adjustment); |
333 | text = g_strdup_printf (format: "%2.4g" , value); |
334 | gtk_editable_set_text (GTK_EDITABLE (spin), text); |
335 | g_free (mem: text); |
336 | |
337 | return TRUE; |
338 | } |
339 | |
340 | static gboolean |
341 | user_filter_cb (gpointer item, |
342 | gpointer data) |
343 | { |
344 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (data); |
345 | PangoFontFamily *family; |
346 | PangoFontFace *face; |
347 | |
348 | if (PANGO_IS_FONT_FAMILY (item)) |
349 | { |
350 | family = item; |
351 | face = pango_font_family_get_face (family, NULL); |
352 | } |
353 | else |
354 | { |
355 | face = PANGO_FONT_FACE (item); |
356 | family = pango_font_face_get_family (face); |
357 | } |
358 | |
359 | if (self->filter_by_monospace && |
360 | !pango_font_family_is_monospace (family)) |
361 | return FALSE; |
362 | |
363 | if (self->filter_by_language && |
364 | self->filter_language) |
365 | { |
366 | PangoFontDescription *desc; |
367 | PangoContext *context; |
368 | PangoFont *font; |
369 | gboolean ret; |
370 | PangoLanguage **langs; |
371 | |
372 | desc = pango_font_face_describe (face); |
373 | pango_font_description_set_size (desc, size: 20); |
374 | |
375 | context = gtk_widget_get_pango_context (GTK_WIDGET (self)); |
376 | font = pango_context_load_font (context, desc); |
377 | |
378 | ret = FALSE; |
379 | |
380 | langs = pango_font_get_languages (font); |
381 | if (langs) |
382 | { |
383 | for (int i = 0; langs[i]; i++) |
384 | { |
385 | if (langs[i] == self->filter_language) |
386 | { |
387 | ret = TRUE; |
388 | break; |
389 | } |
390 | } |
391 | } |
392 | |
393 | g_object_unref (object: font); |
394 | pango_font_description_free (desc); |
395 | |
396 | return ret; |
397 | } |
398 | |
399 | return TRUE; |
400 | } |
401 | |
402 | static void |
403 | monospace_check_changed (GtkCheckButton *check, |
404 | GParamSpec *pspec, |
405 | GtkFontChooserWidget *self) |
406 | { |
407 | self->filter_by_monospace = gtk_check_button_get_active (self: check); |
408 | gtk_filter_changed (self: GTK_FILTER (ptr: self->user_filter), |
409 | change: self->filter_by_monospace ? GTK_FILTER_CHANGE_MORE_STRICT |
410 | : GTK_FILTER_CHANGE_LESS_STRICT); |
411 | } |
412 | |
413 | static void |
414 | language_check_changed (GtkCheckButton *check, |
415 | GParamSpec *pspec, |
416 | GtkFontChooserWidget *self) |
417 | { |
418 | self->filter_by_language = gtk_check_button_get_active (self: check); |
419 | gtk_filter_changed (self: GTK_FILTER (ptr: self->user_filter), |
420 | change: self->filter_by_language ? GTK_FILTER_CHANGE_MORE_STRICT |
421 | : GTK_FILTER_CHANGE_LESS_STRICT); |
422 | } |
423 | |
424 | static void |
425 | gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *self) |
426 | { |
427 | GtkAdjustment *adj, *spin_adj; |
428 | const int *sizes; |
429 | int *font_sizes; |
430 | int i, n_sizes; |
431 | double value, spin_value; |
432 | gpointer item; |
433 | |
434 | item = gtk_single_selection_get_selected_item (self: self->selection); |
435 | |
436 | if (item) |
437 | { |
438 | PangoFontFace *face; |
439 | |
440 | if (PANGO_IS_FONT_FAMILY (item)) |
441 | face = pango_font_family_get_face (family: item, NULL); |
442 | else |
443 | face = item; |
444 | |
445 | pango_font_face_list_sizes (face, sizes: &font_sizes, n_sizes: &n_sizes); |
446 | |
447 | /* It seems not many fonts actually have a sane set of sizes */ |
448 | for (i = 0; i < n_sizes; i++) |
449 | font_sizes[i] = font_sizes[i] / PANGO_SCALE; |
450 | } |
451 | else |
452 | { |
453 | font_sizes = NULL; |
454 | n_sizes = 0; |
455 | } |
456 | |
457 | if (n_sizes < 2) |
458 | { |
459 | static const int fallback_sizes[] = { |
460 | 6, 8, 9, 10, 11, 12, 13, 14, 16, 20, 24, 36, 48, 72 |
461 | }; |
462 | |
463 | sizes = fallback_sizes; |
464 | n_sizes = G_N_ELEMENTS (fallback_sizes); |
465 | } |
466 | else |
467 | { |
468 | sizes = font_sizes; |
469 | } |
470 | |
471 | gtk_scale_clear_marks (GTK_SCALE (self->size_slider)); |
472 | gtk_scale_clear_marks (GTK_SCALE (self->size_slider2)); |
473 | |
474 | adj = gtk_range_get_adjustment (GTK_RANGE (self->size_slider)); |
475 | spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->size_spin)); |
476 | spin_value = gtk_adjustment_get_value (adjustment: spin_adj); |
477 | |
478 | if (spin_value < sizes[0]) |
479 | value = (double) sizes[0]; |
480 | else if (spin_value > sizes[n_sizes - 1]) |
481 | value = (double)sizes[n_sizes - 1]; |
482 | else |
483 | value = (double)spin_value; |
484 | |
485 | /* ensure clamping doesn't callback into font resizing code */ |
486 | g_signal_handlers_block_by_func (adj, size_change_cb, self); |
487 | gtk_adjustment_configure (adjustment: adj, |
488 | value, |
489 | lower: sizes[0], |
490 | upper: sizes[n_sizes - 1], |
491 | step_increment: gtk_adjustment_get_step_increment (adjustment: adj), |
492 | page_increment: gtk_adjustment_get_page_increment (adjustment: adj), |
493 | page_size: gtk_adjustment_get_page_size (adjustment: adj)); |
494 | g_signal_handlers_unblock_by_func (adj, size_change_cb, self); |
495 | |
496 | for (i = 0; i < n_sizes; i++) |
497 | { |
498 | gtk_scale_add_mark (GTK_SCALE (self->size_slider), |
499 | value: sizes[i], |
500 | position: GTK_POS_BOTTOM, NULL); |
501 | gtk_scale_add_mark (GTK_SCALE (self->size_slider2), |
502 | value: sizes[i], |
503 | position: GTK_POS_BOTTOM, NULL); |
504 | } |
505 | |
506 | g_free (mem: font_sizes); |
507 | } |
508 | |
509 | static void |
510 | row_activated_cb (GtkWidget *view, |
511 | guint pos, |
512 | gpointer user_data) |
513 | { |
514 | GtkFontChooserWidget *fontchooser = user_data; |
515 | char *fontname; |
516 | |
517 | fontname = gtk_font_chooser_widget_get_font (fontchooser); |
518 | _gtk_font_chooser_font_activated (GTK_FONT_CHOOSER (fontchooser), fontname); |
519 | g_free (mem: fontname); |
520 | } |
521 | |
522 | static void |
523 | resize_by_scroll_cb (GtkEventControllerScroll *controller, |
524 | double dx, |
525 | double dy, |
526 | gpointer user_data) |
527 | { |
528 | GtkFontChooserWidget *self = user_data; |
529 | GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->size_spin)); |
530 | |
531 | gtk_adjustment_set_value (adjustment: adj, |
532 | value: gtk_adjustment_get_value (adjustment: adj) + |
533 | gtk_adjustment_get_step_increment (adjustment: adj) * dx); |
534 | } |
535 | |
536 | static void |
537 | maybe_update_preview_text (GtkFontChooserWidget *self, |
538 | PangoFontFace *face, |
539 | PangoFontDescription *desc) |
540 | { |
541 | PangoContext *context; |
542 | PangoFont *font; |
543 | const char *sample; |
544 | PangoLanguage **languages; |
545 | GHashTable *langs = NULL; |
546 | PangoLanguage *default_lang; |
547 | PangoLanguage *alt_default = NULL; |
548 | PangoLanguage *lang = NULL; |
549 | int i; |
550 | const char *p; |
551 | |
552 | /* If the user has typed text into the entry, we don't touch it */ |
553 | if (self->preview_text_set) |
554 | return; |
555 | |
556 | if (self->filter_by_language && self->filter_language) |
557 | { |
558 | sample = pango_language_get_sample_string (language: self->filter_language); |
559 | gtk_font_chooser_widget_set_preview_text (fontchooser: self, text: sample); |
560 | return; |
561 | } |
562 | |
563 | /* We do the work only once, and cache the result on the PangoFontFace */ |
564 | sample = (const char *)g_object_get_data (G_OBJECT (face), key: "gtk-sample-text" ); |
565 | if (sample) |
566 | { |
567 | gtk_font_chooser_widget_set_preview_text (fontchooser: self, text: sample); |
568 | return; |
569 | } |
570 | |
571 | context = gtk_widget_get_pango_context (GTK_WIDGET (self)); |
572 | font = pango_context_load_font (context, desc); |
573 | |
574 | default_lang = pango_language_get_default (); |
575 | p = pango_language_to_string (default_lang); |
576 | |
577 | /* The default language tends to be of the form en-us. |
578 | * Since fontconfig languages just have the language part, |
579 | * and we want to use direct pointer comparisons, we need |
580 | * an PangoLanguage for the shortened default language. |
581 | */ |
582 | if (strchr (s: p, c: '-')) |
583 | { |
584 | char q[10]; |
585 | for (i = 0; p[i] != '-' && i < 9; i++) |
586 | q[i] = p[i]; |
587 | q[i] = '\0'; |
588 | alt_default = pango_language_from_string (language: q); |
589 | } |
590 | |
591 | languages = pango_font_get_languages (font); |
592 | |
593 | /* If the font supports the default language, just use it. */ |
594 | if (languages) |
595 | for (i = 0; languages[i]; i++) |
596 | { |
597 | if (languages[i] == default_lang || languages[i] == alt_default) |
598 | { |
599 | lang = default_lang; |
600 | goto found; |
601 | } |
602 | } |
603 | |
604 | /* Otherwise, we make a list of representative languages */ |
605 | langs = g_hash_table_new (NULL, NULL); |
606 | |
607 | if (languages) |
608 | for (i = 0; languages[i]; i++) |
609 | { |
610 | const PangoScript *scripts; |
611 | int num, j; |
612 | |
613 | scripts = pango_language_get_scripts (language: languages[i], num_scripts: &num); |
614 | for (j = 0; j < num; j++) |
615 | { |
616 | lang = pango_script_get_sample_language (script: scripts[j]); |
617 | if (lang) |
618 | g_hash_table_add (hash_table: langs, key: lang); |
619 | } |
620 | } |
621 | |
622 | /* ... and compare it to the users default and preferred languages */ |
623 | if (g_hash_table_contains (hash_table: langs, key: default_lang) || |
624 | g_hash_table_contains (hash_table: langs, key: alt_default)) |
625 | { |
626 | lang = default_lang; |
627 | } |
628 | else |
629 | { |
630 | PangoLanguage **preferred; |
631 | |
632 | preferred = pango_language_get_preferred (); |
633 | if (preferred) |
634 | { |
635 | for (i = 0; preferred[i]; i++) |
636 | { |
637 | if (g_hash_table_contains (hash_table: langs, key: preferred[i])) |
638 | { |
639 | lang = preferred[i]; |
640 | break; |
641 | } |
642 | } |
643 | } |
644 | |
645 | g_hash_table_unref (hash_table: langs); |
646 | |
647 | found: |
648 | sample = pango_language_get_sample_string (language: lang); |
649 | gtk_font_chooser_widget_set_preview_text (fontchooser: self, text: sample); |
650 | g_object_set_data (G_OBJECT (face), key: "gtk-sample-text" , data: (gpointer)sample); |
651 | } |
652 | |
653 | g_object_unref (object: font); |
654 | } |
655 | |
656 | |
657 | static void |
658 | selection_changed_cb (GtkSingleSelection *selection, |
659 | GParamSpec *pspec, |
660 | GtkFontChooserWidget *self) |
661 | { |
662 | gpointer item; |
663 | |
664 | item = gtk_single_selection_get_selected_item (self: selection); |
665 | if (item) |
666 | { |
667 | PangoFontFace *face; |
668 | PangoFontDescription *desc; |
669 | |
670 | if (PANGO_IS_FONT_FAMILY (item)) |
671 | face = pango_font_family_get_face (family: item, NULL); |
672 | else |
673 | face = item; |
674 | desc = pango_font_face_describe (face); |
675 | pango_font_description_set_variations (desc: self->font_desc, NULL); |
676 | gtk_font_chooser_widget_merge_font_desc (fontchooser: self, font_desc: desc); |
677 | g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), TRUE); |
678 | |
679 | maybe_update_preview_text (self, face, desc); |
680 | |
681 | pango_font_description_free (desc); |
682 | } |
683 | else |
684 | { |
685 | g_simple_action_set_state (G_SIMPLE_ACTION (self->tweak_action), value: g_variant_new_boolean (FALSE)); |
686 | g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), FALSE); |
687 | } |
688 | |
689 | g_object_notify (G_OBJECT (self), property_name: "font" ); |
690 | g_object_notify (G_OBJECT (self), property_name: "font-desc" ); |
691 | } |
692 | |
693 | static char * |
694 | get_font_name (GObject *ignore, |
695 | gpointer item) |
696 | { |
697 | if (item == NULL) |
698 | return NULL; |
699 | |
700 | if (PANGO_IS_FONT_FACE (item)) |
701 | { |
702 | return g_strconcat (string1: pango_font_family_get_name (family: pango_font_face_get_family (face: item)), |
703 | " " , |
704 | pango_font_face_get_face_name (face: item), |
705 | NULL); |
706 | } |
707 | else |
708 | { |
709 | return g_strdup (str: pango_font_family_get_name (family: item)); |
710 | } |
711 | } |
712 | |
713 | static PangoAttrList * |
714 | get_font_attributes (GObject *ignore, |
715 | gpointer item) |
716 | { |
717 | PangoAttribute *attribute; |
718 | PangoAttrList *attrs; |
719 | |
720 | attrs = pango_attr_list_new (); |
721 | |
722 | if (item) |
723 | { |
724 | PangoFontFace *face; |
725 | PangoFontDescription *font_desc; |
726 | |
727 | if (PANGO_IS_FONT_FAMILY (item)) |
728 | face = pango_font_family_get_face (family: item, NULL); |
729 | else |
730 | face = item; |
731 | if (face) |
732 | { |
733 | font_desc = pango_font_face_describe (face); |
734 | attribute = pango_attr_font_desc_new (desc: font_desc); |
735 | pango_attr_list_insert (list: attrs, attr: attribute); |
736 | pango_font_description_free (desc: font_desc); |
737 | } |
738 | } |
739 | |
740 | return attrs; |
741 | } |
742 | |
743 | static void |
744 | gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontchooser) |
745 | { |
746 | PangoAttrList *attrs; |
747 | |
748 | attrs = pango_attr_list_new (); |
749 | |
750 | /* Prevent font fallback */ |
751 | pango_attr_list_insert (list: attrs, attr: pango_attr_fallback_new (FALSE)); |
752 | |
753 | /* Force current font and features */ |
754 | pango_attr_list_insert (list: attrs, attr: pango_attr_font_desc_new (desc: fontchooser->font_desc)); |
755 | if (fontchooser->font_features) |
756 | pango_attr_list_insert (list: attrs, attr: pango_attr_font_features_new (features: fontchooser->font_features)); |
757 | if (fontchooser->language) |
758 | pango_attr_list_insert (list: attrs, attr: pango_attr_language_new (language: fontchooser->language)); |
759 | |
760 | gtk_entry_set_attributes (GTK_ENTRY (fontchooser->preview), attrs); |
761 | |
762 | pango_attr_list_unref (list: attrs); |
763 | } |
764 | |
765 | static void |
766 | rows_changed_cb (GtkFontChooserWidget *self) |
767 | { |
768 | const char *page; |
769 | |
770 | if (g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->selection)) == 0 && |
771 | gtk_filter_list_model_get_pending (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model)) == 0) |
772 | page = "empty" ; |
773 | else |
774 | page = "list" ; |
775 | |
776 | if (strcmp (s1: gtk_stack_get_visible_child_name (GTK_STACK (self->list_stack)), s2: page) != 0) |
777 | gtk_stack_set_visible_child_name (GTK_STACK (self->list_stack), name: page); |
778 | } |
779 | |
780 | static void |
781 | update_key_capture (GtkWidget *chooser) |
782 | { |
783 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); |
784 | GtkWidget *capture_widget; |
785 | |
786 | if (gtk_widget_get_mapped (widget: chooser) && |
787 | g_str_equal (v1: gtk_stack_get_visible_child_name (GTK_STACK (fontchooser->stack)), v2: "list" )) |
788 | { |
789 | GtkWidget *toplevel; |
790 | GtkWidget *focus; |
791 | |
792 | toplevel = GTK_WIDGET (gtk_widget_get_root (chooser)); |
793 | focus = gtk_root_get_focus (self: GTK_ROOT (ptr: toplevel)); |
794 | |
795 | if (GTK_IS_EDITABLE (focus) && focus != fontchooser->search_entry) |
796 | capture_widget = NULL; |
797 | else |
798 | capture_widget = chooser; |
799 | } |
800 | else |
801 | capture_widget = NULL; |
802 | |
803 | gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (fontchooser->search_entry), |
804 | widget: capture_widget); |
805 | } |
806 | |
807 | static void |
808 | gtk_font_chooser_widget_map (GtkWidget *widget) |
809 | { |
810 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget); |
811 | |
812 | gtk_editable_set_text (GTK_EDITABLE (fontchooser->search_entry), text: "" ); |
813 | gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), name: "list" ); |
814 | g_simple_action_set_state (G_SIMPLE_ACTION (fontchooser->tweak_action), value: g_variant_new_boolean (FALSE)); |
815 | |
816 | GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->map (widget); |
817 | |
818 | update_key_capture (chooser: widget); |
819 | } |
820 | |
821 | static void |
822 | gtk_font_chooser_widget_unmap (GtkWidget *widget) |
823 | { |
824 | update_key_capture (chooser: widget); |
825 | |
826 | GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unmap (widget); |
827 | } |
828 | |
829 | static void |
830 | gtk_font_chooser_widget_root (GtkWidget *widget) |
831 | { |
832 | GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->root (widget); |
833 | |
834 | g_signal_connect_swapped (gtk_widget_get_root (widget), "notify::focus-widget" , |
835 | G_CALLBACK (update_key_capture), widget); |
836 | } |
837 | |
838 | static void |
839 | gtk_font_chooser_widget_unroot (GtkWidget *widget) |
840 | { |
841 | g_signal_handlers_disconnect_by_func (gtk_widget_get_root (widget), |
842 | update_key_capture, widget); |
843 | |
844 | GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unroot (widget); |
845 | } |
846 | |
847 | static void |
848 | gtk_font_chooser_widget_dispose (GObject *object) |
849 | { |
850 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (object); |
851 | |
852 | g_signal_handlers_disconnect_by_func (self->selection, rows_changed_cb, self); |
853 | g_signal_handlers_disconnect_by_func (self->filter_model, rows_changed_cb, self); |
854 | |
855 | self->filter_func = NULL; |
856 | g_clear_pointer (&self->filter_data, self->filter_data_destroy); |
857 | |
858 | g_clear_pointer (&self->stack, gtk_widget_unparent); |
859 | g_clear_pointer (&self->language_table, g_hash_table_unref); |
860 | |
861 | G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->dispose (object); |
862 | } |
863 | |
864 | static void |
865 | gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass) |
866 | { |
867 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
868 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
869 | GParamSpec *pspec; |
870 | |
871 | g_type_ensure (G_TYPE_THEMED_ICON); |
872 | |
873 | widget_class->root = gtk_font_chooser_widget_root; |
874 | widget_class->unroot = gtk_font_chooser_widget_unroot; |
875 | widget_class->map = gtk_font_chooser_widget_map; |
876 | widget_class->unmap = gtk_font_chooser_widget_unmap; |
877 | |
878 | gobject_class->finalize = gtk_font_chooser_widget_finalize; |
879 | gobject_class->dispose = gtk_font_chooser_widget_dispose; |
880 | gobject_class->set_property = gtk_font_chooser_widget_set_property; |
881 | gobject_class->get_property = gtk_font_chooser_widget_get_property; |
882 | |
883 | /** |
884 | * GtkFontChooserWidget:tweak-action: |
885 | * |
886 | * A toggle action that can be used to switch to the tweak page |
887 | * of the font chooser widget, which lets the user tweak the |
888 | * OpenType features and variation axes of the selected font. |
889 | * |
890 | * The action will be enabled or disabled depending on whether |
891 | * the selected font has any features or axes. |
892 | */ |
893 | pspec = g_param_spec_object (name: "tweak-action" , |
894 | P_("The tweak action" ), |
895 | P_("The toggle action to switch to the tweak page" ), |
896 | G_TYPE_ACTION, |
897 | flags: G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); |
898 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TWEAK_ACTION, pspec); |
899 | |
900 | _gtk_font_chooser_install_properties (klass: gobject_class); |
901 | |
902 | /* Bind class to template */ |
903 | gtk_widget_class_set_template_from_resource (widget_class, |
904 | resource_name: "/org/gtk/libgtk/ui/gtkfontchooserwidget.ui" ); |
905 | |
906 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, search_entry); |
907 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, family_face_list); |
908 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, list_stack); |
909 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, filter_model); |
910 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, selection); |
911 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, custom_filter); |
912 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, user_filter); |
913 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview); |
914 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview2); |
915 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_label); |
916 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_spin); |
917 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_slider); |
918 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_slider2); |
919 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, stack); |
920 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, grid); |
921 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, font_name_label); |
922 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, feature_box); |
923 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, axis_grid); |
924 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, language_button); |
925 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, language_frame); |
926 | gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, language_list); |
927 | gtk_widget_class_bind_template_callback (widget_class, get_font_name); |
928 | gtk_widget_class_bind_template_callback (widget_class, get_font_attributes); |
929 | gtk_widget_class_bind_template_callback (widget_class, stop_search_cb); |
930 | gtk_widget_class_bind_template_callback (widget_class, row_activated_cb); |
931 | gtk_widget_class_bind_template_callback (widget_class, rows_changed_cb); |
932 | gtk_widget_class_bind_template_callback (widget_class, size_change_cb); |
933 | gtk_widget_class_bind_template_callback (widget_class, output_cb); |
934 | gtk_widget_class_bind_template_callback (widget_class, selection_changed_cb); |
935 | gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb); |
936 | gtk_widget_class_bind_template_callback (widget_class, monospace_check_changed); |
937 | gtk_widget_class_bind_template_callback (widget_class, language_check_changed); |
938 | |
939 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
940 | gtk_widget_class_set_css_name (widget_class, I_("fontchooser" )); |
941 | } |
942 | |
943 | static void |
944 | change_tweak (GSimpleAction *action, |
945 | GVariant *state, |
946 | gpointer data) |
947 | { |
948 | GtkFontChooserWidget *fontchooser = data; |
949 | gboolean tweak = g_variant_get_boolean (value: state); |
950 | |
951 | if (tweak) |
952 | { |
953 | gtk_entry_grab_focus_without_selecting (GTK_ENTRY (fontchooser->preview2)); |
954 | gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), name: "tweaks" ); |
955 | } |
956 | else |
957 | { |
958 | gtk_widget_grab_focus (widget: fontchooser->search_entry); |
959 | gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), name: "list" ); |
960 | } |
961 | |
962 | g_simple_action_set_state (simple: action, value: state); |
963 | } |
964 | |
965 | typedef struct { |
966 | guint32 tag; |
967 | float default_value; |
968 | GtkAdjustment *adjustment; |
969 | GtkWidget *label; |
970 | GtkWidget *scale; |
971 | GtkWidget *spin; |
972 | GtkWidget *fontchooser; |
973 | } Axis; |
974 | |
975 | static guint |
976 | axis_hash (gconstpointer v) |
977 | { |
978 | const Axis *a = v; |
979 | |
980 | return a->tag; |
981 | } |
982 | |
983 | static gboolean |
984 | axis_equal (gconstpointer v1, gconstpointer v2) |
985 | { |
986 | const Axis *a1 = v1; |
987 | const Axis *a2 = v2; |
988 | |
989 | return a1->tag == a2->tag; |
990 | } |
991 | |
992 | static void |
993 | axis_remove (gpointer key, |
994 | gpointer value, |
995 | gpointer data) |
996 | { |
997 | GtkFontChooserWidget *fontchooser = data; |
998 | Axis *a = value; |
999 | |
1000 | gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), child: a->label); |
1001 | gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), child: a->scale); |
1002 | gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), child: a->spin); |
1003 | } |
1004 | |
1005 | static void |
1006 | axis_free (gpointer v) |
1007 | { |
1008 | Axis *a = v; |
1009 | |
1010 | g_free (mem: a); |
1011 | } |
1012 | |
1013 | static void |
1014 | select_added (GListModel *model, |
1015 | guint position, |
1016 | guint removed, |
1017 | guint added, |
1018 | gpointer data) |
1019 | { |
1020 | GtkSingleSelection *selection = GTK_SINGLE_SELECTION (ptr: model); |
1021 | |
1022 | g_assert (removed == 0); |
1023 | g_assert (added == 1); |
1024 | |
1025 | gtk_single_selection_set_selected (self: selection, position); |
1026 | } |
1027 | |
1028 | static void |
1029 | add_languages_from_font (GtkFontChooserWidget *self, |
1030 | gpointer item) |
1031 | { |
1032 | PangoFontFace *face; |
1033 | PangoFontDescription *desc; |
1034 | PangoFont *font; |
1035 | PangoContext *context; |
1036 | GtkSelectionModel *model = gtk_list_view_get_model (GTK_LIST_VIEW (self->language_list)); |
1037 | PangoLanguage *default_lang = pango_language_get_default (); |
1038 | PangoLanguage **langs; |
1039 | int i; |
1040 | |
1041 | if (PANGO_IS_FONT_FAMILY (item)) |
1042 | face = pango_font_family_get_face (PANGO_FONT_FAMILY (item), NULL); |
1043 | else |
1044 | face = PANGO_FONT_FACE (item); |
1045 | |
1046 | if (!face) |
1047 | return; |
1048 | |
1049 | desc = pango_font_face_describe (face); |
1050 | pango_font_description_set_size (desc, size: 20); |
1051 | |
1052 | context = gtk_widget_get_pango_context (GTK_WIDGET (self)); |
1053 | font = pango_context_load_font (context, desc); |
1054 | |
1055 | langs = pango_font_get_languages (font); |
1056 | if (langs) |
1057 | { |
1058 | for (i = 0; langs[i]; i++) |
1059 | { |
1060 | if (!g_hash_table_contains (hash_table: self->language_table, key: langs[i])) |
1061 | { |
1062 | g_hash_table_add (hash_table: self->language_table, key: langs[i]); |
1063 | if (get_language_name (language: langs[i])) |
1064 | { |
1065 | const char *l = pango_language_to_string (langs[i]); |
1066 | gulong id = 0; |
1067 | |
1068 | /* Pre-select the default language */ |
1069 | if (pango_language_matches (language: default_lang, range_list: l)) |
1070 | id = g_signal_connect (model, "items-changed" , G_CALLBACK (select_added), NULL); |
1071 | |
1072 | gtk_string_list_append (self: self->languages, string: l); |
1073 | |
1074 | if (id) |
1075 | g_signal_handler_disconnect (instance: model, handler_id: id); |
1076 | } |
1077 | } |
1078 | } |
1079 | } |
1080 | |
1081 | g_object_unref (object: font); |
1082 | pango_font_description_free (desc); |
1083 | } |
1084 | |
1085 | static gboolean |
1086 | gtk_font_chooser_widget_ensure_matching_selection (GtkFontChooserWidget *self); |
1087 | |
1088 | /* We incrementally populate our fontlist to prevent blocking |
1089 | * the font chooser for a long time with expensive FcFontSort |
1090 | * calls in pango for every row in the list). |
1091 | */ |
1092 | static gboolean |
1093 | add_to_fontlist (GtkWidget *widget, |
1094 | GdkFrameClock *clock, |
1095 | gpointer user_data) |
1096 | { |
1097 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (widget); |
1098 | GtkSliceListModel *model = user_data; |
1099 | GListModel *child_model; |
1100 | guint i G_GNUC_UNUSED; |
1101 | guint n G_GNUC_UNUSED; |
1102 | |
1103 | if (gtk_filter_list_model_get_model (self: self->filter_model) != G_LIST_MODEL (ptr: model)) |
1104 | return G_SOURCE_REMOVE; |
1105 | |
1106 | child_model = gtk_slice_list_model_get_model (self: model); |
1107 | |
1108 | n = gtk_slice_list_model_get_size (self: model); |
1109 | |
1110 | for (i = n; i < n + 10; i++) |
1111 | { |
1112 | gpointer item = g_list_model_get_item (list: child_model, position: i); |
1113 | if (!item) |
1114 | break; |
1115 | add_languages_from_font (self, item); |
1116 | g_object_unref (object: item); |
1117 | } |
1118 | |
1119 | n += 10; |
1120 | |
1121 | if (n >= g_list_model_get_n_items (list: child_model)) |
1122 | n = G_MAXUINT; |
1123 | |
1124 | gtk_slice_list_model_set_size (self: model, size: n); |
1125 | |
1126 | if (gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: self->selection)) == GTK_INVALID_LIST_POSITION) |
1127 | gtk_font_chooser_widget_ensure_matching_selection (self); |
1128 | |
1129 | if (n == G_MAXUINT) |
1130 | return G_SOURCE_REMOVE; |
1131 | else |
1132 | return G_SOURCE_CONTINUE; |
1133 | } |
1134 | |
1135 | static void |
1136 | update_fontlist (GtkFontChooserWidget *self) |
1137 | { |
1138 | PangoFontMap *fontmap; |
1139 | GListModel *model; |
1140 | |
1141 | fontmap = self->font_map; |
1142 | if (!fontmap) |
1143 | fontmap = pango_cairo_font_map_get_default (); |
1144 | |
1145 | if ((self->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0) |
1146 | model = g_object_ref (G_LIST_MODEL (fontmap)); |
1147 | else |
1148 | model = G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (g_object_ref (fontmap)))); |
1149 | |
1150 | model = G_LIST_MODEL (ptr: gtk_slice_list_model_new (model, offset: 0, size: 20)); |
1151 | gtk_widget_add_tick_callback (GTK_WIDGET (self), callback: add_to_fontlist, g_object_ref (model), notify: g_object_unref); |
1152 | |
1153 | gtk_filter_list_model_set_model (self: self->filter_model, model); |
1154 | g_object_unref (object: model); |
1155 | } |
1156 | |
1157 | static void |
1158 | setup_lang_item (GtkSignalListItemFactory *factory, |
1159 | gpointer item, |
1160 | gpointer data) |
1161 | { |
1162 | GtkWidget *label; |
1163 | |
1164 | label = gtk_label_new (NULL); |
1165 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 0); |
1166 | gtk_list_item_set_child (GTK_LIST_ITEM (item), child: label); |
1167 | } |
1168 | |
1169 | static void |
1170 | bind_lang_item (GtkSignalListItemFactory *factory, |
1171 | gpointer item, |
1172 | gpointer data) |
1173 | { |
1174 | GtkWidget *label; |
1175 | gpointer obj; |
1176 | const char *str; |
1177 | PangoLanguage *language; |
1178 | const char *name; |
1179 | |
1180 | obj = gtk_list_item_get_item (GTK_LIST_ITEM (item)); |
1181 | str = gtk_string_object_get_string (self: GTK_STRING_OBJECT (ptr: obj)); |
1182 | |
1183 | language = pango_language_from_string (language: str); |
1184 | name = get_language_name (language); |
1185 | |
1186 | label = gtk_list_item_get_child (GTK_LIST_ITEM (item)); |
1187 | gtk_label_set_label (GTK_LABEL (label), str: name); |
1188 | } |
1189 | |
1190 | static char * |
1191 | get_lang_name (gpointer this, |
1192 | const char *lang) |
1193 | { |
1194 | return g_strdup (str: get_language_name (language: pango_language_from_string (language: lang))); |
1195 | } |
1196 | |
1197 | static void |
1198 | language_selection_changed (GtkSelectionModel *model, |
1199 | guint position, |
1200 | guint n_items, |
1201 | GtkFontChooserWidget *self) |
1202 | { |
1203 | gpointer obj; |
1204 | |
1205 | obj = gtk_single_selection_get_selected_item (self: GTK_SINGLE_SELECTION (ptr: model)); |
1206 | |
1207 | if (obj) |
1208 | self->filter_language = pango_language_from_string (language: gtk_string_object_get_string (self: obj)); |
1209 | else |
1210 | self->filter_language = NULL; |
1211 | |
1212 | if (self->filter_by_language) |
1213 | gtk_filter_changed (self: GTK_FILTER (ptr: self->user_filter), change: GTK_FILTER_CHANGE_DIFFERENT); |
1214 | } |
1215 | |
1216 | static gboolean |
1217 | setup_language_list (GtkFontChooserWidget *self) |
1218 | { |
1219 | GtkListItemFactory *factory; |
1220 | GtkExpression *expression; |
1221 | GListModel *model; |
1222 | GtkSelectionModel *selection; |
1223 | |
1224 | self->languages = gtk_string_list_new (NULL); |
1225 | self->language_table = g_hash_table_new (NULL, NULL); |
1226 | |
1227 | expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, property_name: "string" ); |
1228 | expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 1, params: &expression, callback_func: (GCallback)get_lang_name, NULL, NULL); |
1229 | |
1230 | model = G_LIST_MODEL (ptr: gtk_sort_list_model_new (model: G_LIST_MODEL (ptr: self->languages), |
1231 | sorter: GTK_SORTER (ptr: gtk_string_sorter_new (expression)))); |
1232 | |
1233 | selection = GTK_SELECTION_MODEL (ptr: gtk_single_selection_new (model)); |
1234 | g_signal_connect (selection, "selection-changed" , G_CALLBACK (language_selection_changed), self); |
1235 | gtk_list_view_set_model (GTK_LIST_VIEW (self->language_list), model: selection); |
1236 | g_object_unref (object: selection); |
1237 | |
1238 | factory = gtk_signal_list_item_factory_new (); |
1239 | g_signal_connect (factory, "setup" , G_CALLBACK (setup_lang_item), self); |
1240 | g_signal_connect (factory, "bind" , G_CALLBACK (bind_lang_item), self); |
1241 | gtk_list_view_set_factory (GTK_LIST_VIEW (self->language_list), factory); |
1242 | g_object_unref (object: factory); |
1243 | |
1244 | return TRUE; |
1245 | } |
1246 | |
1247 | static void |
1248 | gtk_font_chooser_widget_init (GtkFontChooserWidget *self) |
1249 | { |
1250 | gtk_widget_init_template (GTK_WIDGET (self)); |
1251 | |
1252 | self->axes = g_hash_table_new_full (hash_func: axis_hash, key_equal_func: axis_equal, NULL, value_destroy_func: axis_free); |
1253 | |
1254 | /* Default preview string */ |
1255 | self->preview_text = g_strdup (str: pango_language_get_sample_string (NULL)); |
1256 | self->show_preview_entry = TRUE; |
1257 | self->font_desc = pango_font_description_new (); |
1258 | self->level = GTK_FONT_CHOOSER_LEVEL_FAMILY | |
1259 | GTK_FONT_CHOOSER_LEVEL_STYLE | |
1260 | GTK_FONT_CHOOSER_LEVEL_SIZE; |
1261 | self->language = pango_language_get_default (); |
1262 | |
1263 | /* Set default preview text */ |
1264 | gtk_editable_set_text (GTK_EDITABLE (self->preview), text: self->preview_text); |
1265 | |
1266 | gtk_font_chooser_widget_update_preview_attributes (fontchooser: self); |
1267 | |
1268 | /* Set the upper values of the spin/scale with G_MAXINT / PANGO_SCALE */ |
1269 | gtk_spin_button_set_range (GTK_SPIN_BUTTON (self->size_spin), |
1270 | min: 1.0, max: (double)(G_MAXINT / PANGO_SCALE)); |
1271 | gtk_adjustment_set_upper (adjustment: gtk_range_get_adjustment (GTK_RANGE (self->size_slider)), |
1272 | upper: (double)(G_MAXINT / PANGO_SCALE)); |
1273 | |
1274 | self->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak" , NULL, g_variant_new_boolean (FALSE))); |
1275 | g_signal_connect (self->tweak_action, "change-state" , G_CALLBACK (change_tweak), self); |
1276 | |
1277 | update_fontlist (self); |
1278 | |
1279 | /* Load data and set initial style-dependent parameters */ |
1280 | gtk_font_chooser_widget_populate_features (fontchooser: self); |
1281 | |
1282 | gtk_font_chooser_widget_take_font_desc (fontchooser: self, NULL); |
1283 | |
1284 | gtk_custom_filter_set_filter_func (self: self->user_filter, match_func: user_filter_cb, user_data: self, NULL); |
1285 | |
1286 | setup_language_list (self); |
1287 | } |
1288 | |
1289 | /** |
1290 | * gtk_font_chooser_widget_new: |
1291 | * |
1292 | * Creates a new `GtkFontChooserWidget`. |
1293 | * |
1294 | * Returns: a new `GtkFontChooserWidget` |
1295 | */ |
1296 | GtkWidget * |
1297 | gtk_font_chooser_widget_new (void) |
1298 | { |
1299 | return g_object_new (GTK_TYPE_FONT_CHOOSER_WIDGET, NULL); |
1300 | } |
1301 | |
1302 | static void |
1303 | gtk_font_chooser_widget_finalize (GObject *object) |
1304 | { |
1305 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); |
1306 | |
1307 | if (fontchooser->font_desc) |
1308 | pango_font_description_free (desc: fontchooser->font_desc); |
1309 | |
1310 | if (fontchooser->filter_data_destroy) |
1311 | fontchooser->filter_data_destroy (fontchooser->filter_data); |
1312 | |
1313 | g_free (mem: fontchooser->preview_text); |
1314 | |
1315 | g_clear_object (&fontchooser->font_map); |
1316 | |
1317 | g_object_unref (object: fontchooser->tweak_action); |
1318 | |
1319 | g_list_free_full (list: fontchooser->feature_items, free_func: g_free); |
1320 | |
1321 | g_hash_table_unref (hash_table: fontchooser->axes); |
1322 | |
1323 | g_free (mem: fontchooser->font_features); |
1324 | |
1325 | G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->finalize (object); |
1326 | } |
1327 | |
1328 | static gboolean |
1329 | my_pango_font_family_equal (const char *familya, |
1330 | const char *familyb) |
1331 | { |
1332 | return g_ascii_strcasecmp (s1: familya, s2: familyb) == 0; |
1333 | } |
1334 | |
1335 | static gboolean |
1336 | gtk_font_chooser_widget_ensure_matching_selection (GtkFontChooserWidget *self) |
1337 | { |
1338 | const char *desc_family; |
1339 | guint i, n; |
1340 | |
1341 | desc_family = pango_font_description_get_family (desc: self->font_desc); |
1342 | if (desc_family == NULL) |
1343 | { |
1344 | gtk_single_selection_set_selected (self: self->selection, GTK_INVALID_LIST_POSITION); |
1345 | return TRUE; |
1346 | } |
1347 | |
1348 | n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->selection)); |
1349 | for (i = 0; i < n; i++) |
1350 | { |
1351 | gpointer item; |
1352 | PangoFontFace *face; |
1353 | PangoFontFamily *family; |
1354 | PangoFontDescription *merged; |
1355 | |
1356 | item = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->selection), position: i); |
1357 | g_object_unref (object: item); |
1358 | |
1359 | if (PANGO_IS_FONT_FAMILY (item)) |
1360 | { |
1361 | family = item; |
1362 | face = pango_font_family_get_face (family, NULL); |
1363 | } |
1364 | else |
1365 | { |
1366 | face = item; |
1367 | family = pango_font_face_get_family (face); |
1368 | } |
1369 | if (!my_pango_font_family_equal (familya: desc_family, familyb: pango_font_family_get_name (family))) |
1370 | continue; |
1371 | |
1372 | merged = pango_font_face_describe (face); |
1373 | pango_font_description_merge_static (desc: merged, desc_to_merge: self->font_desc, FALSE); |
1374 | |
1375 | if (pango_font_description_equal (desc1: merged, desc2: self->font_desc)) |
1376 | { |
1377 | pango_font_description_free (desc: merged); |
1378 | break; |
1379 | } |
1380 | |
1381 | pango_font_description_free (desc: merged); |
1382 | } |
1383 | |
1384 | if (i < n) |
1385 | { |
1386 | gtk_single_selection_set_selected (self: self->selection, position: i); |
1387 | return TRUE; |
1388 | } |
1389 | |
1390 | return FALSE; |
1391 | } |
1392 | |
1393 | static PangoFontFace * |
1394 | gtk_font_chooser_widget_get_face (GtkFontChooser *chooser) |
1395 | { |
1396 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); |
1397 | gpointer item; |
1398 | |
1399 | item = gtk_single_selection_get_selected_item (self: self->selection); |
1400 | if (PANGO_IS_FONT_FAMILY (item)) |
1401 | return pango_font_family_get_face (family: item, NULL); |
1402 | else |
1403 | return item; |
1404 | } |
1405 | |
1406 | static PangoFontFamily * |
1407 | gtk_font_chooser_widget_get_family (GtkFontChooser *chooser) |
1408 | { |
1409 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); |
1410 | gpointer item; |
1411 | |
1412 | item = gtk_single_selection_get_selected_item (self: self->selection); |
1413 | if (item == NULL) |
1414 | return NULL; |
1415 | |
1416 | if (PANGO_IS_FONT_FAMILY (item)) |
1417 | return item; |
1418 | else |
1419 | return pango_font_face_get_family (face: item); |
1420 | } |
1421 | |
1422 | static int |
1423 | gtk_font_chooser_widget_get_size (GtkFontChooser *chooser) |
1424 | { |
1425 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); |
1426 | PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser); |
1427 | |
1428 | if (desc) |
1429 | return pango_font_description_get_size (desc); |
1430 | |
1431 | return -1; |
1432 | } |
1433 | |
1434 | static char * |
1435 | gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser) |
1436 | { |
1437 | PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser); |
1438 | |
1439 | if (desc) |
1440 | return pango_font_description_to_string (desc); |
1441 | |
1442 | return NULL; |
1443 | } |
1444 | |
1445 | static PangoFontDescription * |
1446 | gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *self) |
1447 | { |
1448 | if (gtk_single_selection_get_selected_item (self: self->selection)) |
1449 | return self->font_desc; |
1450 | |
1451 | return NULL; |
1452 | } |
1453 | |
1454 | static void |
1455 | gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, |
1456 | const char *fontname) |
1457 | { |
1458 | PangoFontDescription *font_desc; |
1459 | |
1460 | font_desc = pango_font_description_from_string (str: fontname); |
1461 | gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); |
1462 | } |
1463 | |
1464 | /* OpenType variations */ |
1465 | |
1466 | static void |
1467 | add_font_variations (GtkFontChooserWidget *fontchooser, |
1468 | GString *s) |
1469 | { |
1470 | GHashTableIter iter; |
1471 | Axis *axis; |
1472 | |
1473 | g_hash_table_iter_init (iter: &iter, hash_table: fontchooser->axes); |
1474 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)NULL, value: (gpointer *)&axis)) |
1475 | { |
1476 | double value; |
1477 | char buf[128]; |
1478 | |
1479 | value = gtk_adjustment_get_value (adjustment: axis->adjustment); |
1480 | |
1481 | if (value == axis->default_value) |
1482 | continue; |
1483 | |
1484 | hb_variation_to_string (variation: &(hb_variation_t) { axis->tag, value }, buf, size: sizeof (buf)); |
1485 | |
1486 | if (s->len > 0) |
1487 | g_string_append_c (s, ','); |
1488 | g_string_append (string: s, val: buf); |
1489 | } |
1490 | } |
1491 | |
1492 | static void |
1493 | adjustment_changed (GtkAdjustment *adjustment, |
1494 | Axis *axis) |
1495 | { |
1496 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (axis->fontchooser); |
1497 | PangoFontDescription *font_desc; |
1498 | GString *s; |
1499 | |
1500 | fontchooser->updating_variations = TRUE; |
1501 | |
1502 | s = g_string_new (init: "" ); |
1503 | add_font_variations (fontchooser, s); |
1504 | |
1505 | font_desc = pango_font_description_new (); |
1506 | pango_font_description_set_variations (desc: font_desc, variations: s->str); |
1507 | gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); |
1508 | |
1509 | g_string_free (string: s, TRUE); |
1510 | |
1511 | fontchooser->updating_variations = FALSE; |
1512 | } |
1513 | |
1514 | static gboolean |
1515 | should_show_axis (hb_ot_var_axis_info_t *ax) |
1516 | { |
1517 | return (ax->flags & HB_OT_VAR_AXIS_FLAG_HIDDEN) == 0; |
1518 | } |
1519 | |
1520 | static gboolean |
1521 | is_named_instance (hb_font_t *font) |
1522 | { |
1523 | /* FIXME */ |
1524 | return FALSE; |
1525 | } |
1526 | |
1527 | static struct { |
1528 | guint32 tag; |
1529 | const char *name; |
1530 | } axis_names[] = { |
1531 | { HB_OT_TAG_VAR_AXIS_WIDTH, NC_("Font variation axis" , "Width" ) }, |
1532 | { HB_OT_TAG_VAR_AXIS_WEIGHT, NC_("Font variation axis" , "Weight" ) }, |
1533 | { HB_OT_TAG_VAR_AXIS_ITALIC, NC_("Font variation axis" , "Italic" ) }, |
1534 | { HB_OT_TAG_VAR_AXIS_SLANT, NC_("Font variation axis" , "Slant" ) }, |
1535 | { HB_OT_TAG_VAR_AXIS_OPTICAL_SIZE, NC_("Font variation axis" , "Optical Size" ) }, |
1536 | }; |
1537 | |
1538 | static gboolean |
1539 | add_axis (GtkFontChooserWidget *fontchooser, |
1540 | hb_font_t *hb_font, |
1541 | hb_ot_var_axis_info_t *ax, |
1542 | int value, |
1543 | int row) |
1544 | { |
1545 | hb_face_t *hb_face; |
1546 | Axis *axis; |
1547 | const char *name; |
1548 | char buffer[20]; |
1549 | unsigned int buffer_len = 20; |
1550 | int i; |
1551 | |
1552 | hb_face = hb_font_get_face (font: hb_font); |
1553 | |
1554 | axis = g_new (Axis, 1); |
1555 | axis->tag = ax->tag; |
1556 | axis->default_value = ax->default_value; |
1557 | axis->fontchooser = GTK_WIDGET (fontchooser); |
1558 | |
1559 | hb_ot_name_get_utf8 (face: hb_face, name_id: ax->name_id, HB_LANGUAGE_INVALID, text_size: &buffer_len, text: buffer); |
1560 | name = buffer; |
1561 | |
1562 | for (i = 0; i < G_N_ELEMENTS (axis_names); i++) |
1563 | { |
1564 | if (axis_names[i].tag == ax->tag) |
1565 | { |
1566 | name = g_dpgettext2 (NULL, context: "Font variation axis" , msgid: axis_names[i].name); |
1567 | break; |
1568 | } |
1569 | } |
1570 | |
1571 | axis->label = gtk_label_new (str: name); |
1572 | |
1573 | gtk_widget_set_halign (widget: axis->label, align: GTK_ALIGN_START); |
1574 | gtk_widget_set_valign (widget: axis->label, align: GTK_ALIGN_BASELINE); |
1575 | gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), child: axis->label, column: 0, row, width: 1, height: 1); |
1576 | axis->adjustment = gtk_adjustment_new (value: (double)value, |
1577 | lower: (double)ax->min_value, |
1578 | upper: (double)ax->max_value, |
1579 | step_increment: 1.0, page_increment: 10.0, page_size: 0.0); |
1580 | axis->scale = gtk_scale_new (orientation: GTK_ORIENTATION_HORIZONTAL, adjustment: axis->adjustment); |
1581 | gtk_scale_add_mark (GTK_SCALE (axis->scale), value: (double)ax->default_value, position: GTK_POS_TOP, NULL); |
1582 | gtk_widget_set_valign (widget: axis->scale, align: GTK_ALIGN_BASELINE); |
1583 | gtk_widget_set_hexpand (widget: axis->scale, TRUE); |
1584 | gtk_widget_set_size_request (widget: axis->scale, width: 100, height: -1); |
1585 | gtk_scale_set_draw_value (GTK_SCALE (axis->scale), FALSE); |
1586 | gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), child: axis->scale, column: 1, row, width: 1, height: 1); |
1587 | axis->spin = gtk_spin_button_new (adjustment: axis->adjustment, climb_rate: 0, digits: 0); |
1588 | g_signal_connect (axis->spin, "output" , G_CALLBACK (output_cb), fontchooser); |
1589 | gtk_widget_set_valign (widget: axis->spin, align: GTK_ALIGN_BASELINE); |
1590 | gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), child: axis->spin, column: 2, row, width: 1, height: 1); |
1591 | |
1592 | g_hash_table_add (hash_table: fontchooser->axes, key: axis); |
1593 | |
1594 | adjustment_changed (adjustment: axis->adjustment, axis); |
1595 | g_signal_connect (axis->adjustment, "value-changed" , G_CALLBACK (adjustment_changed), axis); |
1596 | if (is_named_instance (font: hb_font) || !should_show_axis (ax)) |
1597 | { |
1598 | gtk_widget_hide (widget: axis->label); |
1599 | gtk_widget_hide (widget: axis->scale); |
1600 | gtk_widget_hide (widget: axis->spin); |
1601 | |
1602 | return FALSE; |
1603 | } |
1604 | |
1605 | return TRUE; |
1606 | } |
1607 | |
1608 | #if HB_VERSION_ATLEAST (3, 3, 0) |
1609 | static void |
1610 | get_axes_and_values (hb_font_t *font, |
1611 | unsigned int n_axes, |
1612 | hb_ot_var_axis_info_t *axes, |
1613 | float *coords) |
1614 | { |
1615 | const float *dcoords; |
1616 | unsigned int length = n_axes; |
1617 | |
1618 | hb_ot_var_get_axis_infos (hb_font_get_face (font), 0, &length, axes); |
1619 | |
1620 | dcoords = hb_font_get_var_coords_design (font, &length); |
1621 | if (dcoords) |
1622 | memcpy (coords, dcoords, sizeof (float) * length); |
1623 | else |
1624 | { |
1625 | for (int i = 0; i < n_axes; i++) |
1626 | { |
1627 | hb_ot_var_axis_info_t *axis = &axes[i]; |
1628 | coords[axis->axis_index] = axis->default_value; |
1629 | } |
1630 | } |
1631 | } |
1632 | |
1633 | #else |
1634 | |
1635 | /* FIXME: This doesn't work if the font has an avar table */ |
1636 | static float |
1637 | denorm_coord (hb_ot_var_axis_info_t *axis, int coord) |
1638 | { |
1639 | float r = coord / 16384.0; |
1640 | |
1641 | if (coord < 0) |
1642 | return axis->default_value + r * (axis->default_value - axis->min_value); |
1643 | else |
1644 | return axis->default_value + r * (axis->max_value - axis->default_value); |
1645 | } |
1646 | |
1647 | static void |
1648 | get_axes_and_values (hb_font_t *font, |
1649 | unsigned int n_axes, |
1650 | hb_ot_var_axis_info_t *axes, |
1651 | float *coords) |
1652 | { |
1653 | const int *ncoords; |
1654 | unsigned int length = n_axes; |
1655 | |
1656 | hb_ot_var_get_axis_infos (face: hb_font_get_face (font), start_offset: 0, axes_count: &length, axes_array: axes); |
1657 | |
1658 | ncoords = hb_font_get_var_coords_normalized (font, length: &length); |
1659 | |
1660 | for (int i = 0; i < n_axes; i++) |
1661 | { |
1662 | hb_ot_var_axis_info_t *axis = &axes[i]; |
1663 | int idx = axis->axis_index; |
1664 | if (ncoords) |
1665 | coords[idx] = denorm_coord (axis, coord: ncoords[idx]); |
1666 | else |
1667 | coords[idx] = axis->default_value; |
1668 | } |
1669 | } |
1670 | #endif |
1671 | |
1672 | static gboolean |
1673 | gtk_font_chooser_widget_update_font_variations (GtkFontChooserWidget *fontchooser) |
1674 | { |
1675 | PangoFont *pango_font; |
1676 | hb_font_t *hb_font; |
1677 | hb_face_t *hb_face; |
1678 | unsigned int n_axes; |
1679 | hb_ot_var_axis_info_t *axes; |
1680 | float *coords; |
1681 | gboolean has_axis = FALSE; |
1682 | int i; |
1683 | |
1684 | if (fontchooser->updating_variations) |
1685 | return FALSE; |
1686 | |
1687 | g_hash_table_foreach (hash_table: fontchooser->axes, func: axis_remove, user_data: fontchooser); |
1688 | g_hash_table_remove_all (hash_table: fontchooser->axes); |
1689 | |
1690 | if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_VARIATIONS) == 0) |
1691 | return FALSE; |
1692 | |
1693 | pango_font = pango_context_load_font (context: gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)), |
1694 | desc: fontchooser->font_desc); |
1695 | hb_font = pango_font_get_hb_font (font: pango_font); |
1696 | hb_face = hb_font_get_face (font: hb_font); |
1697 | |
1698 | if (!hb_ot_var_has_data (face: hb_face)) |
1699 | return FALSE; |
1700 | |
1701 | n_axes = hb_ot_var_get_axis_count (face: hb_face); |
1702 | axes = g_alloca (sizeof (hb_ot_var_axis_info_t) * n_axes); |
1703 | coords = g_alloca (sizeof (float) * n_axes); |
1704 | get_axes_and_values (font: hb_font, n_axes, axes, coords); |
1705 | |
1706 | for (i = 0; i < n_axes; i++) |
1707 | { |
1708 | if (add_axis (fontchooser, hb_font, ax: &axes[i], value: coords[axes[i].axis_index], row: i + 4)) |
1709 | has_axis = TRUE; |
1710 | } |
1711 | |
1712 | g_object_unref (object: pango_font); |
1713 | |
1714 | return has_axis; |
1715 | } |
1716 | |
1717 | /* OpenType features */ |
1718 | |
1719 | /* look for a lang / script combination that matches the |
1720 | * language property and is supported by the hb_face. If |
1721 | * none is found, return the default lang / script tags. |
1722 | */ |
1723 | static void |
1724 | find_language_and_script (GtkFontChooserWidget *fontchooser, |
1725 | hb_face_t *hb_face, |
1726 | hb_tag_t *lang_tag, |
1727 | hb_tag_t *script_tag) |
1728 | { |
1729 | int i, j, k; |
1730 | hb_tag_t scripts[80]; |
1731 | unsigned int n_scripts; |
1732 | unsigned int count; |
1733 | hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS }; |
1734 | hb_language_t lang; |
1735 | const char *langname, *p; |
1736 | |
1737 | langname = pango_language_to_string (fontchooser->language); |
1738 | p = strchr (s: langname, c: '-'); |
1739 | lang = hb_language_from_string (str: langname, len: p ? p - langname : -1); |
1740 | |
1741 | n_scripts = 0; |
1742 | for (i = 0; i < 2; i++) |
1743 | { |
1744 | count = G_N_ELEMENTS (scripts); |
1745 | hb_ot_layout_table_get_script_tags (face: hb_face, table_tag: table[i], start_offset: n_scripts, script_count: &count, script_tags: scripts); |
1746 | n_scripts += count; |
1747 | } |
1748 | |
1749 | for (j = 0; j < n_scripts; j++) |
1750 | { |
1751 | hb_tag_t languages[80]; |
1752 | unsigned int n_languages; |
1753 | |
1754 | n_languages = 0; |
1755 | for (i = 0; i < 2; i++) |
1756 | { |
1757 | count = G_N_ELEMENTS (languages); |
1758 | hb_ot_layout_script_get_language_tags (face: hb_face, table_tag: table[i], script_index: j, start_offset: n_languages, language_count: &count, language_tags: languages); |
1759 | n_languages += count; |
1760 | } |
1761 | |
1762 | for (k = 0; k < n_languages; k++) |
1763 | { |
1764 | if (lang == hb_ot_tag_to_language (tag: languages[k])) |
1765 | { |
1766 | *script_tag = scripts[j]; |
1767 | *lang_tag = languages[k]; |
1768 | return; |
1769 | } |
1770 | } |
1771 | } |
1772 | |
1773 | *lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE; |
1774 | *script_tag = HB_OT_TAG_DEFAULT_SCRIPT; |
1775 | } |
1776 | |
1777 | typedef struct { |
1778 | hb_tag_t tag; |
1779 | const char *name; |
1780 | GtkWidget *top; |
1781 | GtkWidget *feat; |
1782 | GtkWidget *example; |
1783 | } FeatureItem; |
1784 | |
1785 | static const char * |
1786 | get_feature_display_name (hb_tag_t tag) |
1787 | { |
1788 | int i; |
1789 | |
1790 | for (i = 0; i < G_N_ELEMENTS (open_type_layout_features); i++) |
1791 | { |
1792 | if (tag == open_type_layout_features[i].tag) |
1793 | return g_dpgettext2 (NULL, context: "OpenType layout" , msgid: open_type_layout_features[i].name); |
1794 | } |
1795 | |
1796 | return NULL; |
1797 | } |
1798 | |
1799 | static void |
1800 | set_inconsistent (GtkCheckButton *button, |
1801 | gboolean inconsistent) |
1802 | { |
1803 | gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (button), inconsistent); |
1804 | gtk_widget_set_opacity (widget: gtk_widget_get_first_child (GTK_WIDGET (button)), opacity: inconsistent ? 0.0 : 1.0); |
1805 | } |
1806 | |
1807 | static void |
1808 | feat_pressed (GtkGestureClick *gesture, |
1809 | int n_press, |
1810 | double x, |
1811 | double y, |
1812 | GtkWidget *feat) |
1813 | { |
1814 | const guint button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
1815 | |
1816 | if (button == GDK_BUTTON_PRIMARY) |
1817 | { |
1818 | g_signal_handlers_block_by_func (feat, feat_pressed, NULL); |
1819 | |
1820 | if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat))) |
1821 | { |
1822 | set_inconsistent (GTK_CHECK_BUTTON (feat), FALSE); |
1823 | gtk_check_button_set_active (GTK_CHECK_BUTTON (feat), TRUE); |
1824 | } |
1825 | |
1826 | g_signal_handlers_unblock_by_func (feat, feat_pressed, NULL); |
1827 | } |
1828 | else if (button == GDK_BUTTON_SECONDARY) |
1829 | { |
1830 | gboolean inconsistent = gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat)); |
1831 | set_inconsistent (GTK_CHECK_BUTTON (feat), inconsistent: !inconsistent); |
1832 | } |
1833 | } |
1834 | |
1835 | static char * |
1836 | find_affected_text (GtkFontChooserWidget *fontchooser, |
1837 | hb_tag_t feature_tag, |
1838 | hb_font_t *hb_font, |
1839 | hb_tag_t script_tag, |
1840 | hb_tag_t lang_tag, |
1841 | int max_chars) |
1842 | { |
1843 | hb_face_t *hb_face; |
1844 | unsigned int script_index = 0; |
1845 | unsigned int lang_index = 0; |
1846 | unsigned int feature_index = 0; |
1847 | GString *chars; |
1848 | |
1849 | hb_face = hb_font_get_face (font: hb_font); |
1850 | |
1851 | chars = g_string_new (init: "" ); |
1852 | |
1853 | hb_ot_layout_table_find_script (face: hb_face, HB_OT_TAG_GSUB, script_tag, script_index: &script_index); |
1854 | |
1855 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
1856 | hb_ot_layout_script_find_language (face: hb_face, HB_OT_TAG_GSUB, script_index, language_tag: lang_tag, language_index: &lang_index); |
1857 | G_GNUC_END_IGNORE_DEPRECATIONS |
1858 | |
1859 | if (hb_ot_layout_language_find_feature (face: hb_face, HB_OT_TAG_GSUB, script_index, language_index: lang_index, feature_tag, feature_index: &feature_index)) |
1860 | { |
1861 | unsigned int lookup_indexes[32]; |
1862 | unsigned int lookup_count = 32; |
1863 | int count; |
1864 | int n_chars = 0; |
1865 | |
1866 | count = hb_ot_layout_feature_get_lookups (face: hb_face, |
1867 | HB_OT_TAG_GSUB, |
1868 | feature_index, |
1869 | start_offset: 0, |
1870 | lookup_count: &lookup_count, |
1871 | lookup_indexes); |
1872 | if (count > 0) |
1873 | { |
1874 | hb_set_t* glyphs_before = NULL; |
1875 | hb_set_t* glyphs_input = NULL; |
1876 | hb_set_t* glyphs_after = NULL; |
1877 | hb_set_t* glyphs_output = NULL; |
1878 | hb_codepoint_t gid; |
1879 | |
1880 | glyphs_input = hb_set_create (); |
1881 | |
1882 | // XXX For now, just look at first index |
1883 | hb_ot_layout_lookup_collect_glyphs (face: hb_face, |
1884 | HB_OT_TAG_GSUB, |
1885 | lookup_index: lookup_indexes[0], |
1886 | glyphs_before, |
1887 | glyphs_input, |
1888 | glyphs_after, |
1889 | glyphs_output); |
1890 | |
1891 | if (!fontchooser->glyphmap) |
1892 | { |
1893 | fontchooser->glyphmap = hb_map_create (); |
1894 | for (hb_codepoint_t ch = 0; ch < 0xffff; ch++) |
1895 | { |
1896 | hb_codepoint_t glyph = 0; |
1897 | if (hb_font_get_nominal_glyph (font: hb_font, unicode: ch, glyph: &glyph) && |
1898 | !hb_map_has (map: fontchooser->glyphmap, key: glyph)) |
1899 | hb_map_set (map: fontchooser->glyphmap, key: glyph, value: ch); |
1900 | } |
1901 | } |
1902 | |
1903 | while (hb_set_next (set: glyphs_input, codepoint: &gid)) |
1904 | { |
1905 | hb_codepoint_t ch; |
1906 | |
1907 | if (n_chars == max_chars) |
1908 | { |
1909 | g_string_append (string: chars, val: "…" ); |
1910 | break; |
1911 | } |
1912 | ch = hb_map_get (map: fontchooser->glyphmap, key: gid); |
1913 | if (ch != HB_MAP_VALUE_INVALID) |
1914 | { |
1915 | g_string_append_unichar (string: chars, wc: (gunichar)ch); |
1916 | n_chars++; |
1917 | } |
1918 | } |
1919 | |
1920 | hb_set_destroy (set: glyphs_input); |
1921 | } |
1922 | } |
1923 | |
1924 | return g_string_free (string: chars, FALSE); |
1925 | } |
1926 | |
1927 | static void |
1928 | update_feature_example (GtkFontChooserWidget *fontchooser, |
1929 | FeatureItem *item, |
1930 | hb_font_t *hb_font, |
1931 | hb_tag_t script_tag, |
1932 | hb_tag_t lang_tag, |
1933 | PangoFontDescription *font_desc) |
1934 | { |
1935 | const char *letter_case[] = { "smcp" , "c2sc" , "pcap" , "c2pc" , "unic" , "cpsp" , "case" , NULL }; |
1936 | const char *number_case[] = { "xxxx" , "lnum" , "onum" , NULL }; |
1937 | const char *number_spacing[] = { "xxxx" , "pnum" , "tnum" , NULL }; |
1938 | const char *number_formatting[] = { "zero" , "nalt" , "frac" , NULL }; |
1939 | const char *char_variants[] = { |
1940 | "swsh" , "cswh" , "calt" , "falt" , "hist" , "salt" , "jalt" , "titl" , "rand" , |
1941 | "ss01" , "ss02" , "ss03" , "ss04" , "ss05" , "ss06" , "ss07" , "ss08" , "ss09" , "ss10" , |
1942 | "ss11" , "ss12" , "ss13" , "ss14" , "ss15" , "ss16" , "ss17" , "ss18" , "ss19" , "ss20" , |
1943 | NULL }; |
1944 | |
1945 | if (g_strv_contains (strv: number_case, str: item->name) || |
1946 | g_strv_contains (strv: number_spacing, str: item->name)) |
1947 | { |
1948 | PangoAttrList *attrs; |
1949 | PangoAttribute *attr; |
1950 | PangoFontDescription *desc; |
1951 | char *str; |
1952 | |
1953 | attrs = pango_attr_list_new (); |
1954 | |
1955 | desc = pango_font_description_copy (desc: font_desc); |
1956 | pango_font_description_unset_fields (desc, to_unset: PANGO_FONT_MASK_SIZE); |
1957 | pango_attr_list_insert (list: attrs, attr: pango_attr_font_desc_new (desc)); |
1958 | pango_font_description_free (desc); |
1959 | str = g_strconcat (string1: item->name, " 1" , NULL); |
1960 | attr = pango_attr_font_features_new (features: str); |
1961 | pango_attr_list_insert (list: attrs, attr); |
1962 | |
1963 | gtk_label_set_text (GTK_LABEL (item->example), str: "0123456789" ); |
1964 | gtk_label_set_attributes (GTK_LABEL (item->example), attrs); |
1965 | |
1966 | pango_attr_list_unref (list: attrs); |
1967 | } |
1968 | else if (g_strv_contains (strv: letter_case, str: item->name) || |
1969 | g_strv_contains (strv: number_formatting, str: item->name) || |
1970 | g_strv_contains (strv: char_variants, str: item->name)) |
1971 | { |
1972 | char *input = NULL; |
1973 | char *text; |
1974 | |
1975 | if (strcmp (s1: item->name, s2: "case" ) == 0) |
1976 | input = g_strdup (str: "A-B[Cq]" ); |
1977 | else if (g_strv_contains (strv: letter_case, str: item->name)) |
1978 | input = g_strdup (str: "AaBbCc…" ); |
1979 | else if (strcmp (s1: item->name, s2: "zero" ) == 0) |
1980 | input = g_strdup (str: "0" ); |
1981 | else if (strcmp (s1: item->name, s2: "frac" ) == 0) |
1982 | input = g_strdup (str: "1/2 2/3 7/8" ); |
1983 | else if (strcmp (s1: item->name, s2: "nalt" ) == 0) |
1984 | input = find_affected_text (fontchooser, feature_tag: item->tag, hb_font, script_tag, lang_tag, max_chars: 3); |
1985 | else |
1986 | input = find_affected_text (fontchooser, feature_tag: item->tag, hb_font, script_tag, lang_tag, max_chars: 10); |
1987 | |
1988 | if (input[0] != '\0') |
1989 | { |
1990 | PangoAttrList *attrs; |
1991 | PangoAttribute *attr; |
1992 | PangoFontDescription *desc; |
1993 | char *str; |
1994 | |
1995 | text = g_strconcat (string1: input, " ⟶ " , input, NULL); |
1996 | |
1997 | attrs = pango_attr_list_new (); |
1998 | |
1999 | desc = pango_font_description_copy (desc: font_desc); |
2000 | pango_font_description_unset_fields (desc, to_unset: PANGO_FONT_MASK_SIZE); |
2001 | pango_attr_list_insert (list: attrs, attr: pango_attr_font_desc_new (desc)); |
2002 | pango_font_description_free (desc); |
2003 | str = g_strconcat (string1: item->name, " 0" , NULL); |
2004 | attr = pango_attr_font_features_new (features: str); |
2005 | attr->start_index = 0; |
2006 | attr->end_index = strlen (s: input); |
2007 | pango_attr_list_insert (list: attrs, attr); |
2008 | str = g_strconcat (string1: item->name, " 1" , NULL); |
2009 | attr = pango_attr_font_features_new (features: str); |
2010 | attr->start_index = strlen (s: input) + strlen (s: " ⟶ " ); |
2011 | attr->end_index = attr->start_index + strlen (s: input); |
2012 | pango_attr_list_insert (list: attrs, attr); |
2013 | |
2014 | gtk_label_set_text (GTK_LABEL (item->example), str: text); |
2015 | gtk_label_set_attributes (GTK_LABEL (item->example), attrs); |
2016 | |
2017 | g_free (mem: text); |
2018 | pango_attr_list_unref (list: attrs); |
2019 | } |
2020 | else |
2021 | gtk_label_set_markup (GTK_LABEL (item->example), str: "" ); |
2022 | g_free (mem: input); |
2023 | } |
2024 | } |
2025 | |
2026 | static void |
2027 | font_feature_toggled_cb (GtkCheckButton *check_button, |
2028 | gpointer user_data) |
2029 | { |
2030 | GtkFontChooserWidget *fontchooser = user_data; |
2031 | |
2032 | set_inconsistent (button: check_button, FALSE); |
2033 | update_font_features (fontchooser); |
2034 | } |
2035 | |
2036 | static void |
2037 | add_check_group (GtkFontChooserWidget *fontchooser, |
2038 | const char *title, |
2039 | const char **tags) |
2040 | { |
2041 | GtkWidget *label; |
2042 | GtkWidget *group; |
2043 | PangoAttrList *attrs; |
2044 | int i; |
2045 | |
2046 | group = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
2047 | gtk_widget_set_halign (widget: group, align: GTK_ALIGN_FILL); |
2048 | |
2049 | label = gtk_label_new (str: title); |
2050 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0); |
2051 | gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START); |
2052 | g_object_set (object: label, first_property_name: "margin-top" , 10, "margin-bottom" , 10, NULL); |
2053 | attrs = pango_attr_list_new (); |
2054 | pango_attr_list_insert (list: attrs, attr: pango_attr_weight_new (weight: PANGO_WEIGHT_BOLD)); |
2055 | gtk_label_set_attributes (GTK_LABEL (label), attrs); |
2056 | pango_attr_list_unref (list: attrs); |
2057 | gtk_box_append (GTK_BOX (group), child: label); |
2058 | |
2059 | for (i = 0; tags[i]; i++) |
2060 | { |
2061 | hb_tag_t tag; |
2062 | GtkWidget *feat; |
2063 | FeatureItem *item; |
2064 | GtkGesture *gesture; |
2065 | GtkWidget *box; |
2066 | GtkWidget *example; |
2067 | |
2068 | tag = hb_tag_from_string (str: tags[i], len: -1); |
2069 | |
2070 | feat = gtk_check_button_new_with_label (label: get_feature_display_name (tag)); |
2071 | set_inconsistent (GTK_CHECK_BUTTON (feat), TRUE); |
2072 | g_signal_connect (feat, "toggled" , G_CALLBACK (font_feature_toggled_cb), fontchooser); |
2073 | g_signal_connect_swapped (feat, "notify::inconsistent" , G_CALLBACK (update_font_features), fontchooser); |
2074 | |
2075 | gesture = gtk_gesture_click_new (); |
2076 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY); |
2077 | g_signal_connect (gesture, "pressed" , G_CALLBACK (feat_pressed), feat); |
2078 | gtk_widget_add_controller (widget: feat, GTK_EVENT_CONTROLLER (gesture)); |
2079 | |
2080 | example = gtk_label_new (str: "" ); |
2081 | gtk_label_set_selectable (GTK_LABEL (example), TRUE); |
2082 | gtk_widget_set_halign (widget: example, align: GTK_ALIGN_START); |
2083 | |
2084 | box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10); |
2085 | gtk_box_set_homogeneous (GTK_BOX (box), TRUE); |
2086 | gtk_box_append (GTK_BOX (box), child: feat); |
2087 | gtk_box_append (GTK_BOX (box), child: example); |
2088 | gtk_box_append (GTK_BOX (group), child: box); |
2089 | |
2090 | item = g_new (FeatureItem, 1); |
2091 | item->name = tags[i]; |
2092 | item->tag = tag; |
2093 | item->top = box; |
2094 | item->feat = feat; |
2095 | item->example = example; |
2096 | |
2097 | fontchooser->feature_items = g_list_prepend (list: fontchooser->feature_items, data: item); |
2098 | } |
2099 | |
2100 | gtk_box_append (GTK_BOX (fontchooser->feature_box), child: group); |
2101 | } |
2102 | |
2103 | static void |
2104 | add_radio_group (GtkFontChooserWidget *fontchooser, |
2105 | const char *title, |
2106 | const char **tags) |
2107 | { |
2108 | GtkWidget *label; |
2109 | GtkWidget *group; |
2110 | int i; |
2111 | GtkWidget *group_button = NULL; |
2112 | PangoAttrList *attrs; |
2113 | |
2114 | group = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
2115 | gtk_widget_set_halign (widget: group, align: GTK_ALIGN_FILL); |
2116 | |
2117 | label = gtk_label_new (str: title); |
2118 | gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0); |
2119 | gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START); |
2120 | g_object_set (object: label, first_property_name: "margin-top" , 10, "margin-bottom" , 10, NULL); |
2121 | attrs = pango_attr_list_new (); |
2122 | pango_attr_list_insert (list: attrs, attr: pango_attr_weight_new (weight: PANGO_WEIGHT_BOLD)); |
2123 | gtk_label_set_attributes (GTK_LABEL (label), attrs); |
2124 | pango_attr_list_unref (list: attrs); |
2125 | gtk_box_append (GTK_BOX (group), child: label); |
2126 | |
2127 | for (i = 0; tags[i]; i++) |
2128 | { |
2129 | hb_tag_t tag; |
2130 | GtkWidget *feat; |
2131 | FeatureItem *item; |
2132 | const char *name; |
2133 | GtkWidget *box; |
2134 | GtkWidget *example; |
2135 | |
2136 | tag = hb_tag_from_string (str: tags[i], len: -1); |
2137 | name = get_feature_display_name (tag); |
2138 | |
2139 | feat = gtk_check_button_new_with_label (label: name ? name : _("Default" )); |
2140 | if (group_button == NULL) |
2141 | group_button = feat; |
2142 | else |
2143 | gtk_check_button_set_group (GTK_CHECK_BUTTON (feat), GTK_CHECK_BUTTON (group_button)); |
2144 | |
2145 | g_signal_connect_swapped (feat, "notify::active" , G_CALLBACK (update_font_features), fontchooser); |
2146 | g_object_set_data (G_OBJECT (feat), key: "default" , data: group_button); |
2147 | |
2148 | example = gtk_label_new (str: "" ); |
2149 | gtk_label_set_selectable (GTK_LABEL (example), TRUE); |
2150 | gtk_widget_set_halign (widget: example, align: GTK_ALIGN_START); |
2151 | |
2152 | box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10); |
2153 | gtk_box_set_homogeneous (GTK_BOX (box), TRUE); |
2154 | gtk_box_append (GTK_BOX (box), child: feat); |
2155 | gtk_box_append (GTK_BOX (box), child: example); |
2156 | gtk_box_append (GTK_BOX (group), child: box); |
2157 | |
2158 | item = g_new (FeatureItem, 1); |
2159 | item->name = tags[i]; |
2160 | item->tag = tag; |
2161 | item->top = box; |
2162 | item->feat = feat; |
2163 | item->example = example; |
2164 | |
2165 | fontchooser->feature_items = g_list_prepend (list: fontchooser->feature_items, data: item); |
2166 | } |
2167 | |
2168 | gtk_box_append (GTK_BOX (fontchooser->feature_box), child: group); |
2169 | } |
2170 | |
2171 | static void |
2172 | gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser) |
2173 | { |
2174 | const char *ligatures[] = { "liga" , "dlig" , "hlig" , "clig" , NULL }; |
2175 | const char *letter_case[] = { "smcp" , "c2sc" , "pcap" , "c2pc" , "unic" , "cpsp" , "case" , NULL }; |
2176 | const char *number_case[] = { "xxxx" , "lnum" , "onum" , NULL }; |
2177 | const char *number_spacing[] = { "xxxx" , "pnum" , "tnum" , NULL }; |
2178 | const char *number_formatting[] = { "zero" , "nalt" , "frac" , NULL }; |
2179 | const char *char_variants[] = { |
2180 | "swsh" , "cswh" , "calt" , "falt" , "hist" , "salt" , "jalt" , "titl" , "rand" , |
2181 | "ss01" , "ss02" , "ss03" , "ss04" , "ss05" , "ss06" , "ss07" , "ss08" , "ss09" , "ss10" , |
2182 | "ss11" , "ss12" , "ss13" , "ss14" , "ss15" , "ss16" , "ss17" , "ss18" , "ss19" , "ss20" , |
2183 | NULL }; |
2184 | |
2185 | add_check_group (fontchooser, _("Ligatures" ), tags: ligatures); |
2186 | add_check_group (fontchooser, _("Letter Case" ), tags: letter_case); |
2187 | add_radio_group (fontchooser, _("Number Case" ), tags: number_case); |
2188 | add_radio_group (fontchooser, _("Number Spacing" ), tags: number_spacing); |
2189 | add_check_group (fontchooser, _("Number Formatting" ), tags: number_formatting); |
2190 | add_check_group (fontchooser, _("Character Variants" ), tags: char_variants); |
2191 | |
2192 | update_font_features (fontchooser); |
2193 | } |
2194 | |
2195 | static gboolean |
2196 | gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser) |
2197 | { |
2198 | PangoFont *pango_font; |
2199 | hb_font_t *hb_font; |
2200 | hb_tag_t script_tag; |
2201 | hb_tag_t lang_tag; |
2202 | guint script_index = 0; |
2203 | guint lang_index = 0; |
2204 | int i, j; |
2205 | GList *l; |
2206 | gboolean has_feature = FALSE; |
2207 | |
2208 | for (l = fontchooser->feature_items; l; l = l->next) |
2209 | { |
2210 | FeatureItem *item = l->data; |
2211 | gtk_widget_hide (widget: item->top); |
2212 | gtk_widget_hide (widget: gtk_widget_get_parent (widget: item->top)); |
2213 | } |
2214 | |
2215 | if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_FEATURES) == 0) |
2216 | return FALSE; |
2217 | |
2218 | pango_font = pango_context_load_font (context: gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)), |
2219 | desc: fontchooser->font_desc); |
2220 | hb_font = pango_font_get_hb_font (font: pango_font); |
2221 | |
2222 | if (hb_font) |
2223 | { |
2224 | hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS }; |
2225 | hb_face_t *hb_face; |
2226 | hb_tag_t features[80]; |
2227 | unsigned int count; |
2228 | unsigned int n_features; |
2229 | |
2230 | hb_face = hb_font_get_face (font: hb_font); |
2231 | |
2232 | find_language_and_script (fontchooser, hb_face, lang_tag: &lang_tag, script_tag: &script_tag); |
2233 | |
2234 | n_features = 0; |
2235 | for (i = 0; i < 2; i++) |
2236 | { |
2237 | hb_ot_layout_table_find_script (face: hb_face, table_tag: table[i], script_tag, script_index: &script_index); |
2238 | |
2239 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
2240 | hb_ot_layout_script_find_language (face: hb_face, table_tag: table[i], script_index, language_tag: lang_tag, language_index: &lang_index); |
2241 | G_GNUC_END_IGNORE_DEPRECATIONS |
2242 | |
2243 | count = G_N_ELEMENTS (features); |
2244 | hb_ot_layout_language_get_feature_tags (face: hb_face, |
2245 | table_tag: table[i], |
2246 | script_index, |
2247 | language_index: lang_index, |
2248 | start_offset: n_features, |
2249 | feature_count: &count, |
2250 | feature_tags: features); |
2251 | n_features += count; |
2252 | } |
2253 | |
2254 | for (j = 0; j < n_features; j++) |
2255 | { |
2256 | for (l = fontchooser->feature_items; l; l = l->next) |
2257 | { |
2258 | FeatureItem *item = l->data; |
2259 | if (item->tag != features[j]) |
2260 | continue; |
2261 | |
2262 | has_feature = TRUE; |
2263 | gtk_widget_show (widget: item->top); |
2264 | gtk_widget_show (widget: gtk_widget_get_parent (widget: item->top)); |
2265 | |
2266 | update_feature_example (fontchooser, item, hb_font, script_tag, lang_tag, font_desc: fontchooser->font_desc); |
2267 | |
2268 | if (GTK_IS_CHECK_BUTTON (item->feat)) |
2269 | { |
2270 | GtkWidget *def = GTK_WIDGET (g_object_get_data (G_OBJECT (item->feat), "default" )); |
2271 | if (def) |
2272 | { |
2273 | gtk_widget_show (widget: def); |
2274 | gtk_widget_show (widget: gtk_widget_get_parent (widget: def)); |
2275 | gtk_check_button_set_active (GTK_CHECK_BUTTON (def), TRUE); |
2276 | } |
2277 | else |
2278 | set_inconsistent (GTK_CHECK_BUTTON (item->feat), TRUE); |
2279 | } |
2280 | } |
2281 | } |
2282 | |
2283 | if (fontchooser->glyphmap) |
2284 | { |
2285 | hb_map_destroy (map: fontchooser->glyphmap); |
2286 | fontchooser->glyphmap = NULL; |
2287 | } |
2288 | } |
2289 | |
2290 | g_object_unref (object: pango_font); |
2291 | |
2292 | return has_feature; |
2293 | } |
2294 | |
2295 | static void |
2296 | update_font_features (GtkFontChooserWidget *fontchooser) |
2297 | { |
2298 | GString *s; |
2299 | GList *l; |
2300 | char buf[128]; |
2301 | |
2302 | s = g_string_new (init: "" ); |
2303 | |
2304 | for (l = fontchooser->feature_items; l; l = l->next) |
2305 | { |
2306 | FeatureItem *item = l->data; |
2307 | |
2308 | if (!gtk_widget_is_sensitive (widget: item->feat)) |
2309 | continue; |
2310 | |
2311 | if (GTK_IS_CHECK_BUTTON (item->feat) && g_object_get_data (G_OBJECT (item->feat), key: "default" )) |
2312 | { |
2313 | if (gtk_check_button_get_active (GTK_CHECK_BUTTON (item->feat)) && |
2314 | strcmp (s1: item->name, s2: "xxxx" ) != 0) |
2315 | { |
2316 | hb_feature_to_string (feature: &(hb_feature_t) { item->tag, 1, 0, -1 }, buf, size: sizeof (buf)); |
2317 | if (s->len > 0) |
2318 | g_string_append_c (s, ','); |
2319 | g_string_append (string: s, val: buf); |
2320 | } |
2321 | } |
2322 | else if (GTK_IS_CHECK_BUTTON (item->feat)) |
2323 | { |
2324 | guint32 value; |
2325 | |
2326 | if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (item->feat))) |
2327 | continue; |
2328 | |
2329 | value = gtk_check_button_get_active (GTK_CHECK_BUTTON (item->feat)); |
2330 | hb_feature_to_string (feature: &(hb_feature_t) { item->tag, value, 0, -1 }, buf, size: sizeof (buf)); |
2331 | if (s->len > 0) |
2332 | g_string_append_c (s, ','); |
2333 | g_string_append (string: s, val: buf); |
2334 | } |
2335 | } |
2336 | |
2337 | if (g_strcmp0 (str1: fontchooser->font_features, str2: s->str) != 0) |
2338 | { |
2339 | g_free (mem: fontchooser->font_features); |
2340 | fontchooser->font_features = g_string_free (string: s, FALSE); |
2341 | g_object_notify (G_OBJECT (fontchooser), property_name: "font-features" ); |
2342 | } |
2343 | else |
2344 | g_string_free (string: s, TRUE); |
2345 | |
2346 | gtk_font_chooser_widget_update_preview_attributes (fontchooser); |
2347 | } |
2348 | |
2349 | static void |
2350 | gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser, |
2351 | const PangoFontDescription *font_desc) |
2352 | { |
2353 | PangoFontMask mask; |
2354 | |
2355 | g_assert (font_desc != NULL); |
2356 | /* iter may be NULL if the font doesn't exist on the list */ |
2357 | |
2358 | mask = pango_font_description_get_set_fields (desc: font_desc); |
2359 | |
2360 | /* sucky test, because we can't restrict the comparison to |
2361 | * only the parts that actually do get merged */ |
2362 | if (pango_font_description_equal (desc1: font_desc, desc2: fontchooser->font_desc)) |
2363 | return; |
2364 | |
2365 | pango_font_description_merge (desc: fontchooser->font_desc, desc_to_merge: font_desc, TRUE); |
2366 | |
2367 | if (mask & PANGO_FONT_MASK_SIZE) |
2368 | { |
2369 | double font_size = (double) pango_font_description_get_size (desc: fontchooser->font_desc) / PANGO_SCALE; |
2370 | /* XXX: This clamps, which can cause it to reloop into here, do we need |
2371 | * to block its signal handler? |
2372 | */ |
2373 | gtk_range_set_value (GTK_RANGE (fontchooser->size_slider), value: font_size); |
2374 | gtk_spin_button_set_value (GTK_SPIN_BUTTON (fontchooser->size_spin), value: font_size); |
2375 | } |
2376 | if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT | |
2377 | PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH)) |
2378 | { |
2379 | gboolean has_tweak = FALSE; |
2380 | |
2381 | gtk_font_chooser_widget_update_marks (self: fontchooser); |
2382 | |
2383 | if (gtk_font_chooser_widget_update_font_features (fontchooser)) |
2384 | has_tweak = TRUE; |
2385 | if (gtk_font_chooser_widget_update_font_variations (fontchooser)) |
2386 | has_tweak = TRUE; |
2387 | |
2388 | g_simple_action_set_enabled (G_SIMPLE_ACTION (fontchooser->tweak_action), enabled: has_tweak); |
2389 | } |
2390 | |
2391 | if (mask & PANGO_FONT_MASK_VARIATIONS) |
2392 | { |
2393 | if (pango_font_description_get_variations (desc: fontchooser->font_desc)[0] == '\0') |
2394 | pango_font_description_unset_fields (desc: fontchooser->font_desc, to_unset: PANGO_FONT_MASK_VARIANT); |
2395 | } |
2396 | |
2397 | gtk_font_chooser_widget_update_preview_attributes (fontchooser); |
2398 | |
2399 | g_object_notify (G_OBJECT (fontchooser), property_name: "font" ); |
2400 | g_object_notify (G_OBJECT (fontchooser), property_name: "font-desc" ); |
2401 | } |
2402 | |
2403 | static void |
2404 | gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, |
2405 | PangoFontDescription *font_desc) |
2406 | { |
2407 | PangoFontMask mask; |
2408 | |
2409 | if (font_desc == NULL) |
2410 | font_desc = pango_font_description_from_string (GTK_FONT_CHOOSER_DEFAULT_FONT_NAME); |
2411 | |
2412 | mask = pango_font_description_get_set_fields (desc: font_desc); |
2413 | gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc); |
2414 | |
2415 | if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT | |
2416 | PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH)) |
2417 | { |
2418 | gtk_single_selection_set_selected (self: fontchooser->selection, GTK_INVALID_LIST_POSITION); |
2419 | gtk_font_chooser_widget_ensure_matching_selection (self: fontchooser); |
2420 | } |
2421 | |
2422 | pango_font_description_free (desc: font_desc); |
2423 | } |
2424 | |
2425 | static const char * |
2426 | gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser) |
2427 | { |
2428 | |
2429 | return fontchooser->preview_text; |
2430 | } |
2431 | |
2432 | static void |
2433 | gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser, |
2434 | const char *text) |
2435 | { |
2436 | if (fontchooser->preview_text == text) |
2437 | return; |
2438 | |
2439 | g_free (mem: fontchooser->preview_text); |
2440 | fontchooser->preview_text = g_strdup (str: text); |
2441 | |
2442 | gtk_editable_set_text (GTK_EDITABLE (fontchooser->preview), text); |
2443 | |
2444 | g_object_notify (G_OBJECT (fontchooser), property_name: "preview-text" ); |
2445 | } |
2446 | |
2447 | static gboolean |
2448 | gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser) |
2449 | { |
2450 | return fontchooser->show_preview_entry; |
2451 | } |
2452 | |
2453 | static void |
2454 | gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser, |
2455 | gboolean show_preview_entry) |
2456 | { |
2457 | if (fontchooser->show_preview_entry != show_preview_entry) |
2458 | { |
2459 | fontchooser->show_preview_entry = show_preview_entry; |
2460 | |
2461 | if (show_preview_entry) |
2462 | gtk_widget_show (widget: fontchooser->preview); |
2463 | else |
2464 | gtk_widget_hide (widget: fontchooser->preview); |
2465 | |
2466 | g_object_notify (G_OBJECT (fontchooser), property_name: "show-preview-entry" ); |
2467 | } |
2468 | } |
2469 | |
2470 | static void |
2471 | gtk_font_chooser_widget_set_font_map (GtkFontChooser *chooser, |
2472 | PangoFontMap *fontmap) |
2473 | { |
2474 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); |
2475 | |
2476 | if (g_set_object (&fontchooser->font_map, fontmap)) |
2477 | { |
2478 | PangoContext *context; |
2479 | |
2480 | if (!fontmap) |
2481 | fontmap = pango_cairo_font_map_get_default (); |
2482 | |
2483 | context = gtk_widget_get_pango_context (widget: fontchooser->family_face_list); |
2484 | pango_context_set_font_map (context, font_map: fontmap); |
2485 | |
2486 | context = gtk_widget_get_pango_context (widget: fontchooser->preview); |
2487 | pango_context_set_font_map (context, font_map: fontmap); |
2488 | |
2489 | update_fontlist (self: fontchooser); |
2490 | } |
2491 | } |
2492 | |
2493 | static PangoFontMap * |
2494 | gtk_font_chooser_widget_get_font_map (GtkFontChooser *chooser) |
2495 | { |
2496 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); |
2497 | |
2498 | return fontchooser->font_map; |
2499 | } |
2500 | |
2501 | static gboolean |
2502 | gtk_font_chooser_widget_filter_cb (gpointer item, |
2503 | gpointer data) |
2504 | { |
2505 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (data); |
2506 | PangoFontFamily *family; |
2507 | PangoFontFace *face; |
2508 | |
2509 | if (PANGO_IS_FONT_FAMILY (item)) |
2510 | { |
2511 | family = item; |
2512 | face = pango_font_family_get_face (family, NULL); |
2513 | } |
2514 | else |
2515 | { |
2516 | face = item; |
2517 | family = pango_font_face_get_family (face); |
2518 | } |
2519 | |
2520 | return self->filter_func (family, face, self->filter_data); |
2521 | } |
2522 | |
2523 | static void |
2524 | gtk_font_chooser_widget_set_filter_func (GtkFontChooser *chooser, |
2525 | GtkFontFilterFunc filter, |
2526 | gpointer data, |
2527 | GDestroyNotify destroy) |
2528 | { |
2529 | GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser); |
2530 | |
2531 | if (self->filter_data_destroy) |
2532 | self->filter_data_destroy (self->filter_data); |
2533 | |
2534 | self->filter_func = filter; |
2535 | self->filter_data = data; |
2536 | self->filter_data_destroy = destroy; |
2537 | |
2538 | if (filter) |
2539 | { |
2540 | gtk_custom_filter_set_filter_func (self: self->custom_filter, |
2541 | match_func: gtk_font_chooser_widget_filter_cb, |
2542 | user_data: self, |
2543 | NULL); |
2544 | } |
2545 | else |
2546 | { |
2547 | gtk_custom_filter_set_filter_func (self: self->custom_filter, NULL, NULL, NULL); |
2548 | } |
2549 | } |
2550 | |
2551 | static void |
2552 | gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser, |
2553 | GtkFontChooserLevel level) |
2554 | { |
2555 | if (fontchooser->level == level) |
2556 | return; |
2557 | |
2558 | fontchooser->level = level; |
2559 | |
2560 | if ((level & GTK_FONT_CHOOSER_LEVEL_SIZE) != 0) |
2561 | { |
2562 | gtk_widget_show (widget: fontchooser->size_label); |
2563 | gtk_widget_show (widget: fontchooser->size_slider); |
2564 | gtk_widget_show (widget: fontchooser->size_spin); |
2565 | } |
2566 | else |
2567 | { |
2568 | gtk_widget_hide (widget: fontchooser->size_label); |
2569 | gtk_widget_hide (widget: fontchooser->size_slider); |
2570 | gtk_widget_hide (widget: fontchooser->size_spin); |
2571 | } |
2572 | |
2573 | update_fontlist (self: fontchooser); |
2574 | |
2575 | g_object_notify (G_OBJECT (fontchooser), property_name: "level" ); |
2576 | } |
2577 | |
2578 | static GtkFontChooserLevel |
2579 | gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser) |
2580 | { |
2581 | return fontchooser->level; |
2582 | } |
2583 | |
2584 | static void |
2585 | gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser, |
2586 | const char *language) |
2587 | { |
2588 | PangoLanguage *lang; |
2589 | |
2590 | lang = pango_language_from_string (language); |
2591 | if (fontchooser->language == lang) |
2592 | return; |
2593 | |
2594 | fontchooser->language = lang; |
2595 | g_object_notify (G_OBJECT (fontchooser), property_name: "language" ); |
2596 | |
2597 | gtk_font_chooser_widget_update_preview_attributes (fontchooser); |
2598 | } |
2599 | |
2600 | static void |
2601 | gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface) |
2602 | { |
2603 | iface->get_font_family = gtk_font_chooser_widget_get_family; |
2604 | iface->get_font_face = gtk_font_chooser_widget_get_face; |
2605 | iface->get_font_size = gtk_font_chooser_widget_get_size; |
2606 | iface->set_filter_func = gtk_font_chooser_widget_set_filter_func; |
2607 | iface->set_font_map = gtk_font_chooser_widget_set_font_map; |
2608 | iface->get_font_map = gtk_font_chooser_widget_get_font_map; |
2609 | } |
2610 | |
2611 | GAction * |
2612 | gtk_font_chooser_widget_get_tweak_action (GtkWidget *widget) |
2613 | { |
2614 | GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget); |
2615 | |
2616 | return fontchooser->tweak_action; |
2617 | } |
2618 | |