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