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
93typedef struct _GtkFontChooserWidgetClass GtkFontChooserWidgetClass;
94
95struct _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
158struct _GtkFontChooserWidgetClass
159{
160 GtkWidgetClass parent_class;
161};
162
163enum {
164 PROP_ZERO,
165 PROP_TWEAK_ACTION
166};
167
168static void gtk_font_chooser_widget_set_property (GObject *object,
169 guint prop_id,
170 const GValue *value,
171 GParamSpec *pspec);
172static void gtk_font_chooser_widget_get_property (GObject *object,
173 guint prop_id,
174 GValue *value,
175 GParamSpec *pspec);
176static void gtk_font_chooser_widget_finalize (GObject *object);
177
178static char *gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser);
179static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser,
180 const char *fontname);
181
182static PangoFontDescription *gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser);
183static void gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget *fontchooser,
184 const PangoFontDescription *font_desc);
185static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser,
186 PangoFontDescription *font_desc);
187
188
189static const char *gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser);
190static void gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser,
191 const char *text);
192
193static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser);
194static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser,
195 gboolean show_preview_entry);
196
197static void gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser);
198static void gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser,
199 GtkFontChooserLevel level);
200static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser);
201static void gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser,
202 const char *language);
203static void update_font_features (GtkFontChooserWidget *fontchooser);
204
205static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface);
206
207G_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
211static void
212gtk_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
246static void
247gtk_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
286static void
287stop_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
306static void
307size_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
323static gboolean
324output_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
340static gboolean
341user_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
402static void
403monospace_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
413static void
414language_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
424static void
425gtk_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
509static void
510row_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
522static void
523resize_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
536static void
537maybe_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
647found:
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
657static void
658selection_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
693static char *
694get_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
713static PangoAttrList *
714get_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
743static void
744gtk_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
765static void
766rows_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
780static void
781update_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
807static void
808gtk_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
821static void
822gtk_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
829static void
830gtk_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
838static void
839gtk_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
847static void
848gtk_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
864static void
865gtk_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
943static void
944change_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
965typedef 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
975static guint
976axis_hash (gconstpointer v)
977{
978 const Axis *a = v;
979
980 return a->tag;
981}
982
983static gboolean
984axis_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
992static void
993axis_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
1005static void
1006axis_free (gpointer v)
1007{
1008 Axis *a = v;
1009
1010 g_free (mem: a);
1011}
1012
1013static void
1014select_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
1028static void
1029add_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
1085static gboolean
1086gtk_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 */
1092static gboolean
1093add_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
1135static void
1136update_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
1157static void
1158setup_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
1169static void
1170bind_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
1190static char *
1191get_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
1197static void
1198language_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
1216static gboolean
1217setup_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
1247static void
1248gtk_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 */
1296GtkWidget *
1297gtk_font_chooser_widget_new (void)
1298{
1299 return g_object_new (GTK_TYPE_FONT_CHOOSER_WIDGET, NULL);
1300}
1301
1302static void
1303gtk_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
1328static gboolean
1329my_pango_font_family_equal (const char *familya,
1330 const char *familyb)
1331{
1332 return g_ascii_strcasecmp (s1: familya, s2: familyb) == 0;
1333}
1334
1335static gboolean
1336gtk_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
1393static PangoFontFace *
1394gtk_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
1406static PangoFontFamily *
1407gtk_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
1422static int
1423gtk_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
1434static char *
1435gtk_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
1445static PangoFontDescription *
1446gtk_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
1454static void
1455gtk_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
1466static void
1467add_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
1492static void
1493adjustment_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
1514static gboolean
1515should_show_axis (hb_ot_var_axis_info_t *ax)
1516{
1517 return (ax->flags & HB_OT_VAR_AXIS_FLAG_HIDDEN) == 0;
1518}
1519
1520static gboolean
1521is_named_instance (hb_font_t *font)
1522{
1523 /* FIXME */
1524 return FALSE;
1525}
1526
1527static 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
1538static gboolean
1539add_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)
1609static void
1610get_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 */
1636static float
1637denorm_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
1647static void
1648get_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
1672static gboolean
1673gtk_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 */
1723static void
1724find_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
1777typedef struct {
1778 hb_tag_t tag;
1779 const char *name;
1780 GtkWidget *top;
1781 GtkWidget *feat;
1782 GtkWidget *example;
1783} FeatureItem;
1784
1785static const char *
1786get_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
1799static void
1800set_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
1807static void
1808feat_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
1835static char *
1836find_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
1927static void
1928update_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
2026static void
2027font_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
2036static void
2037add_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
2103static void
2104add_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
2171static void
2172gtk_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
2195static gboolean
2196gtk_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
2295static void
2296update_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
2349static void
2350gtk_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
2403static void
2404gtk_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
2425static const char *
2426gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser)
2427{
2428
2429 return fontchooser->preview_text;
2430}
2431
2432static void
2433gtk_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
2447static gboolean
2448gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser)
2449{
2450 return fontchooser->show_preview_entry;
2451}
2452
2453static void
2454gtk_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
2470static void
2471gtk_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
2493static PangoFontMap *
2494gtk_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
2501static gboolean
2502gtk_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
2523static void
2524gtk_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
2551static void
2552gtk_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
2578static GtkFontChooserLevel
2579gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser)
2580{
2581 return fontchooser->level;
2582}
2583
2584static void
2585gtk_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
2600static void
2601gtk_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
2611GAction *
2612gtk_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

source code of gtk/gtk/gtkfontchooserwidget.c