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