1 /* gtkemojichooser.c: An Emoji chooser widget
2  * Copyright 2017, Red Hat, Inc.
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 "gtkemojichooser.h"
21 
22 #include "gtkadjustmentprivate.h"
23 #include "gtkbox.h"
24 #include "gtkbutton.h"
25 #include "gtkcssprovider.h"
26 #include "gtkentry.h"
27 #include "gtkflowbox.h"
28 #include "gtkstack.h"
29 #include "gtklabel.h"
30 #include "gtkmain.h"
31 #include "gtkgesturelongpress.h"
32 #include "gtkgesturemultipress.h"
33 #include "gtkpopover.h"
34 #include "gtkscrolledwindow.h"
35 #include "gtkeventbox.h"
36 #include "gtkintl.h"
37 #include "gtkprivate.h"
38 
39 #define BOX_SPACE 6
40 
41 typedef struct {
42   GtkWidget *box;
43   GtkWidget *heading;
44   GtkWidget *button;
45   int group;
46   gunichar label;
47   gboolean empty;
48 } EmojiSection;
49 
50 struct _GtkEmojiChooser
51 {
52   GtkPopover parent_instance;
53 
54   GtkWidget *search_entry;
55   GtkWidget *stack;
56   GtkWidget *scrolled_window;
57 
58   int emoji_max_width;
59 
60   EmojiSection recent;
61   EmojiSection people;
62   EmojiSection body;
63   EmojiSection nature;
64   EmojiSection food;
65   EmojiSection travel;
66   EmojiSection activities;
67   EmojiSection objects;
68   EmojiSection symbols;
69   EmojiSection flags;
70 
71   GtkGesture *recent_long_press;
72   GtkGesture *recent_multi_press;
73   GtkGesture *people_long_press;
74   GtkGesture *people_multi_press;
75   GtkGesture *body_long_press;
76   GtkGesture *body_multi_press;
77 
78   GVariant *data;
79   GtkWidget *box;
80   GVariantIter *iter;
81   guint populate_idle;
82 
83   GSettings *settings;
84 };
85 
86 struct _GtkEmojiChooserClass {
87   GtkPopoverClass parent_class;
88 };
89 
90 enum {
91   EMOJI_PICKED,
92   LAST_SIGNAL
93 };
94 
95 static int signals[LAST_SIGNAL];
96 
G_DEFINE_TYPE(GtkEmojiChooser,gtk_emoji_chooser,GTK_TYPE_POPOVER)97 G_DEFINE_TYPE (GtkEmojiChooser, gtk_emoji_chooser, GTK_TYPE_POPOVER)
98 
99 static void
100 gtk_emoji_chooser_finalize (GObject *object)
101 {
102   GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (object);
103 
104   if (chooser->populate_idle)
105     g_source_remove (chooser->populate_idle);
106 
107   g_variant_unref (chooser->data);
108   g_object_unref (chooser->settings);
109 
110   g_clear_object (&chooser->recent_long_press);
111   g_clear_object (&chooser->recent_multi_press);
112   g_clear_object (&chooser->people_long_press);
113   g_clear_object (&chooser->people_multi_press);
114   g_clear_object (&chooser->body_long_press);
115   g_clear_object (&chooser->body_multi_press);
116 
117   G_OBJECT_CLASS (gtk_emoji_chooser_parent_class)->finalize (object);
118 }
119 
120 static void
scroll_to_section(GtkButton * button,gpointer data)121 scroll_to_section (GtkButton *button,
122                    gpointer   data)
123 {
124   EmojiSection *section = data;
125   GtkEmojiChooser *chooser;
126   GtkAdjustment *adj;
127   GtkAllocation alloc = { 0, 0, 0, 0 };
128 
129   chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER));
130 
131   adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
132 
133   if (section->heading)
134     gtk_widget_get_allocation (section->heading, &alloc);
135 
136   gtk_adjustment_animate_to_value (adj, alloc.y - BOX_SPACE);
137 }
138 
139 static void
140 add_emoji (GtkWidget    *box,
141            gboolean      prepend,
142            GVariant     *item,
143            gunichar      modifier,
144            GtkEmojiChooser *chooser);
145 
146 #define MAX_RECENT (7*3)
147 
148 static void
populate_recent_section(GtkEmojiChooser * chooser)149 populate_recent_section (GtkEmojiChooser *chooser)
150 {
151   GVariant *variant;
152   GVariant *item;
153   GVariantIter iter;
154   gboolean empty = FALSE;
155 
156   variant = g_settings_get_value (chooser->settings, "recent-emoji");
157   g_variant_iter_init (&iter, variant);
158   while ((item = g_variant_iter_next_value (&iter)))
159     {
160       GVariant *emoji_data;
161       gunichar modifier;
162 
163       emoji_data = g_variant_get_child_value (item, 0);
164       g_variant_get_child (item, 1, "u", &modifier);
165       add_emoji (chooser->recent.box, FALSE, emoji_data, modifier, chooser);
166       g_variant_unref (emoji_data);
167       g_variant_unref (item);
168       empty = FALSE;
169     }
170 
171   gtk_widget_set_visible (chooser->recent.box, !empty);
172   gtk_widget_set_sensitive (chooser->recent.button, !empty);
173 
174   g_variant_unref (variant);
175 }
176 
177 static GVariant *
get_recent_emoji_data(GtkWidget * widget)178 get_recent_emoji_data (GtkWidget *widget)
179 {
180   GVariant *emoji_data = g_object_get_data (G_OBJECT (widget), "emoji-data");
181   GVariantIter *codes_iter;
182   GVariantIter *keywords_iter;
183   GVariantBuilder codes_builder;
184   const char *name;
185   const char *shortname;
186   guint code;
187   guint group;
188 
189   g_assert (emoji_data);
190 
191   if (g_variant_is_of_type (emoji_data, G_VARIANT_TYPE ("(auss)")))
192     return emoji_data;
193 
194   g_variant_get (emoji_data, "(au&sasu)", &codes_iter, &name, &keywords_iter, &group);
195 
196   g_variant_builder_init (&codes_builder, G_VARIANT_TYPE ("au"));
197   while (g_variant_iter_loop (codes_iter, "u", &code))
198     g_variant_builder_add (&codes_builder, "u", code);
199 
200   g_variant_iter_free (codes_iter);
201   g_variant_iter_free (keywords_iter);
202 
203   shortname = "";
204 
205   return g_variant_new ("(auss)", &codes_builder, name, shortname);
206 }
207 
208 static void
add_recent_item(GtkEmojiChooser * chooser,GVariant * item,gunichar modifier)209 add_recent_item (GtkEmojiChooser *chooser,
210                  GVariant        *item,
211                  gunichar         modifier)
212 {
213   GList *children, *l;
214   int i;
215   GVariantBuilder builder;
216 
217   g_variant_ref (item);
218 
219   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)"));
220   g_variant_builder_add (&builder, "(@(auss)u)", item, modifier);
221 
222   children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box));
223   for (l = children, i = 1; l; l = l->next, i++)
224     {
225       GVariant *item2 = get_recent_emoji_data (GTK_WIDGET (l->data));
226       gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier"));
227 
228       if (modifier == modifier2 && g_variant_equal (item, item2))
229         {
230           gtk_widget_destroy (GTK_WIDGET (l->data));
231           i--;
232           continue;
233         }
234       if (i >= MAX_RECENT)
235         {
236           gtk_widget_destroy (GTK_WIDGET (l->data));
237           continue;
238         }
239 
240       g_variant_builder_add (&builder, "(@(auss)u)", item2, modifier2);
241     }
242   g_list_free (children);
243 
244   add_emoji (chooser->recent.box, TRUE, item, modifier, chooser);
245 
246   /* Enable recent */
247   gtk_widget_show (chooser->recent.box);
248   gtk_widget_set_sensitive (chooser->recent.button, TRUE);
249 
250   g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder));
251 
252   g_variant_unref (item);
253 }
254 
255 static gboolean
should_close(GtkEmojiChooser * chooser)256 should_close (GtkEmojiChooser *chooser)
257 {
258   GdkDisplay *display;
259   GdkSeat *seat;
260   GdkDevice *device;
261   GdkModifierType state;
262 
263   display = gtk_widget_get_display (GTK_WIDGET (chooser));
264   seat = gdk_display_get_default_seat (display);
265   device = gdk_seat_get_pointer (seat);
266   gdk_device_get_state (device, gtk_widget_get_window (GTK_WIDGET (chooser)),
267                         NULL, &state);
268 
269   return (state & GDK_CONTROL_MASK) == 0;
270 }
271 
272 static void
emoji_activated(GtkFlowBox * box,GtkFlowBoxChild * child,gpointer data)273 emoji_activated (GtkFlowBox      *box,
274                  GtkFlowBoxChild *child,
275                  gpointer         data)
276 {
277   GtkEmojiChooser *chooser = data;
278   char *text;
279   GtkWidget *ebox;
280   GtkWidget *label;
281   GVariant *item;
282   gunichar modifier;
283 
284   if (should_close (chooser))
285     gtk_popover_popdown (GTK_POPOVER (chooser));
286   else
287     {
288       GtkWidget *popover;
289 
290       popover = gtk_widget_get_ancestor (GTK_WIDGET (box), GTK_TYPE_POPOVER);
291       if (popover != GTK_WIDGET (chooser))
292         gtk_popover_popdown (GTK_POPOVER (popover));
293     }
294 
295   ebox = gtk_bin_get_child (GTK_BIN (child));
296   label = gtk_bin_get_child (GTK_BIN (ebox));
297   text = g_strdup (gtk_label_get_label (GTK_LABEL (label)));
298 
299   item = get_recent_emoji_data (GTK_WIDGET (child));
300   modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier"));
301   add_recent_item (chooser, item, modifier);
302 
303   g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
304   g_free (text);
305 }
306 
307 static gboolean
has_variations(GVariant * emoji_data)308 has_variations (GVariant *emoji_data)
309 {
310   GVariant *codes;
311   int i;
312   gboolean has_variations;
313 
314   has_variations = FALSE;
315   codes = g_variant_get_child_value (emoji_data, 0);
316   for (i = 0; i < g_variant_n_children (codes); i++)
317     {
318       gunichar code;
319       g_variant_get_child (codes, i, "u", &code);
320       if (code == 0)
321         {
322           has_variations = TRUE;
323           break;
324         }
325     }
326   g_variant_unref (codes);
327 
328   return has_variations;
329 }
330 
331 static void
show_variations(GtkEmojiChooser * chooser,GtkWidget * child)332 show_variations (GtkEmojiChooser *chooser,
333                  GtkWidget       *child)
334 {
335   GtkWidget *popover;
336   GtkWidget *view;
337   GtkWidget *box;
338   GVariant *emoji_data;
339   GtkWidget *parent_popover;
340   gunichar modifier;
341 
342   if (!child)
343     return;
344 
345   emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
346   if (!emoji_data)
347     return;
348 
349   if (!has_variations (emoji_data))
350     return;
351 
352   parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER);
353   popover = gtk_popover_new (child);
354   view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
355   gtk_style_context_add_class (gtk_widget_get_style_context (view), "view");
356   box = gtk_flow_box_new ();
357   gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE);
358   gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 6);
359   gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 6);
360   gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE);
361   gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE);
362   gtk_container_add (GTK_CONTAINER (popover), view);
363   gtk_container_add (GTK_CONTAINER (view), box);
364 
365   g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover);
366 
367   add_emoji (box, FALSE, emoji_data, 0, chooser);
368   for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++)
369     add_emoji (box, FALSE, emoji_data, modifier, chooser);
370 
371   gtk_widget_show_all (view);
372   gtk_popover_popup (GTK_POPOVER (popover));
373 }
374 
375 static void
update_hover(GtkWidget * widget,GdkEvent * event,gpointer data)376 update_hover (GtkWidget *widget,
377               GdkEvent  *event,
378               gpointer   data)
379 {
380   if (event->type == GDK_ENTER_NOTIFY)
381     gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
382   else
383     gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
384 }
385 
386 static void
long_pressed_cb(GtkGesture * gesture,double x,double y,gpointer data)387 long_pressed_cb (GtkGesture *gesture,
388                  double      x,
389                  double      y,
390                  gpointer    data)
391 {
392   GtkEmojiChooser *chooser = data;
393   GtkWidget *box;
394   GtkWidget *child;
395 
396   box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
397   child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
398   show_variations (chooser, child);
399 }
400 
401 static void
pressed_cb(GtkGesture * gesture,int n_press,double x,double y,gpointer data)402 pressed_cb (GtkGesture *gesture,
403             int         n_press,
404             double      x,
405             double      y,
406             gpointer    data)
407 {
408   GtkEmojiChooser *chooser = data;
409   GtkWidget *box;
410   GtkWidget *child;
411 
412   box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
413   child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
414   show_variations (chooser, child);
415 }
416 
417 static gboolean
popup_menu(GtkWidget * widget,gpointer data)418 popup_menu (GtkWidget *widget,
419             gpointer   data)
420 {
421   GtkEmojiChooser *chooser = data;
422 
423   show_variations (chooser, widget);
424   return TRUE;
425 }
426 
427 static void
add_emoji(GtkWidget * box,gboolean prepend,GVariant * item,gunichar modifier,GtkEmojiChooser * chooser)428 add_emoji (GtkWidget    *box,
429            gboolean      prepend,
430            GVariant     *item,
431            gunichar      modifier,
432            GtkEmojiChooser *chooser)
433 {
434   GtkWidget *child;
435   GtkWidget *ebox;
436   GtkWidget *label;
437   PangoAttrList *attrs;
438   GVariant *codes;
439   char text[64];
440   char *p = text;
441   int i;
442   PangoLayout *layout;
443   PangoRectangle rect;
444 
445   codes = g_variant_get_child_value (item, 0);
446   for (i = 0; i < g_variant_n_children (codes); i++)
447     {
448       gunichar code;
449 
450       g_variant_get_child (codes, i, "u", &code);
451       if (code == 0)
452         code = modifier;
453       if (code != 0)
454         p += g_unichar_to_utf8 (code, p);
455     }
456   g_variant_unref (codes);
457   p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */
458   p[0] = 0;
459 
460   label = gtk_label_new (text);
461   attrs = pango_attr_list_new ();
462   pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
463   gtk_label_set_attributes (GTK_LABEL (label), attrs);
464   pango_attr_list_unref (attrs);
465 
466   layout = gtk_label_get_layout (GTK_LABEL (label));
467   pango_layout_get_extents (layout, &rect, NULL);
468 
469   /* Check for fallback rendering that generates too wide items */
470   if (pango_layout_get_unknown_glyphs_count (layout) > 0 ||
471       rect.width >= 1.5 * chooser->emoji_max_width)
472     {
473       gtk_widget_destroy (label);
474       return;
475     }
476 
477   child = gtk_flow_box_child_new ();
478   gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji");
479   g_object_set_data_full (G_OBJECT (child), "emoji-data",
480                           g_variant_ref (item),
481                           (GDestroyNotify)g_variant_unref);
482   if (modifier != 0)
483     g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier));
484 
485   ebox = gtk_event_box_new ();
486   gtk_widget_add_events (ebox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
487   g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (update_hover), FALSE);
488   g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (update_hover), FALSE);
489   gtk_container_add (GTK_CONTAINER (child), ebox);
490   gtk_container_add (GTK_CONTAINER (ebox), label);
491   gtk_widget_show_all (child);
492 
493   if (chooser)
494     g_signal_connect (child, "popup-menu", G_CALLBACK (popup_menu), chooser);
495 
496   gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1);
497 }
498 
499 GBytes *
get_emoji_data(void)500 get_emoji_data (void)
501 {
502   GBytes *bytes;
503   const char *lang;
504   char q[10];
505   char *path;
506   GError *error = NULL;
507 
508   lang = pango_language_to_string (gtk_get_default_language ());
509   if (strchr (lang, '-'))
510     {
511       int i;
512       for (i = 0; lang[i] != '-' && i < 9; i++)
513         q[i] = lang[i];
514       q[i] = '\0';
515       lang = q;
516     }
517 
518   path = g_strconcat ("/org/gtk/libgtk/emoji/", lang, ".data", NULL);
519   bytes = g_resources_lookup_data (path, 0, &error);
520   if (bytes)
521     {
522       g_debug ("Found emoji data for %s in resource %s", lang, path);
523       g_free (path);
524       return bytes;
525     }
526 
527   if (g_error_matches (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND))
528     {
529       char *filename;
530       char *gresource_name;
531       GMappedFile *file;
532 
533       g_clear_error (&error);
534 
535       gresource_name = g_strconcat (lang, ".gresource", NULL);
536       filename = g_build_filename (_gtk_get_data_prefix (), "share", "gtk-3.0",
537                                    "emoji", gresource_name, NULL);
538       g_clear_pointer (&gresource_name, g_free);
539       file = g_mapped_file_new (filename, FALSE, NULL);
540 
541       if (file)
542         {
543           GBytes *data;
544           GResource *resource;
545 
546           data = g_mapped_file_get_bytes (file);
547           g_mapped_file_unref (file);
548 
549           resource = g_resource_new_from_data (data, NULL);
550           g_bytes_unref (data);
551 
552           g_debug ("Registering resource for Emoji data for %s from file %s", lang, filename);
553           g_resources_register (resource);
554           g_resource_unref (resource);
555 
556           bytes = g_resources_lookup_data (path, 0, NULL);
557           if (bytes)
558             {
559               g_debug ("Found emoji data for %s in resource %s", lang, path);
560               g_free (path);
561               g_free (filename);
562               return bytes;
563             }
564         }
565 
566       g_free (filename);
567     }
568 
569   g_clear_error (&error);
570 
571   g_free (path);
572 
573   return g_resources_lookup_data ("/org/gtk/libgtk/emoji/en.data", 0, NULL);
574 }
575 
576 static gboolean
populate_emoji_chooser(gpointer data)577 populate_emoji_chooser (gpointer data)
578 {
579   GtkEmojiChooser *chooser = data;
580   GVariant *item;
581   guint64 start, now;
582 
583   start = g_get_monotonic_time ();
584 
585   if (!chooser->data)
586     {
587       GBytes *bytes;
588 
589       bytes = get_emoji_data ();
590 
591       chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(ausasu)"), bytes, TRUE));
592       g_bytes_unref (bytes);
593     }
594 
595   if (!chooser->iter)
596     {
597       chooser->iter = g_variant_iter_new (chooser->data);
598       chooser->box = chooser->people.box;
599     }
600   while ((item = g_variant_iter_next_value (chooser->iter)))
601     {
602       guint group;
603 
604       g_variant_get_child (item, 3, "u", &group);
605 
606       if (group == chooser->people.group)
607         chooser->box = chooser->people.box;
608       else if (group == chooser->body.group)
609         chooser->box = chooser->body.box;
610       else if (group == chooser->nature.group)
611         chooser->box = chooser->nature.box;
612       else if (group == chooser->food.group)
613         chooser->box = chooser->food.box;
614       else if (group == chooser->travel.group)
615         chooser->box = chooser->travel.box;
616       else if (group == chooser->activities.group)
617         chooser->box = chooser->activities.box;
618       else if (group == chooser->objects.group)
619         chooser->box = chooser->objects.box;
620       else if (group == chooser->symbols.group)
621         chooser->box = chooser->symbols.box;
622       else if (group == chooser->flags.group)
623         chooser->box = chooser->flags.box;
624 
625       add_emoji (chooser->box, FALSE, item, 0, chooser);
626       g_variant_unref (item);
627 
628       now = g_get_monotonic_time ();
629       if (now > start + 8000)
630         return G_SOURCE_CONTINUE;
631     }
632 
633   g_variant_iter_free (chooser->iter);
634   chooser->iter = NULL;
635   chooser->box = NULL;
636   chooser->populate_idle = 0;
637 
638   return G_SOURCE_REMOVE;
639 }
640 
641 static void
adj_value_changed(GtkAdjustment * adj,gpointer data)642 adj_value_changed (GtkAdjustment *adj,
643                    gpointer       data)
644 {
645   GtkEmojiChooser *chooser = data;
646   double value = gtk_adjustment_get_value (adj);
647   EmojiSection const *sections[] = {
648     &chooser->recent,
649     &chooser->people,
650     &chooser->body,
651     &chooser->nature,
652     &chooser->food,
653     &chooser->travel,
654     &chooser->activities,
655     &chooser->objects,
656     &chooser->symbols,
657     &chooser->flags,
658   };
659   EmojiSection const *select_section = sections[0];
660   gsize i;
661 
662   /* Figure out which section the current scroll position is within */
663   for (i = 0; i < G_N_ELEMENTS (sections); ++i)
664     {
665       EmojiSection const *section = sections[i];
666       GtkAllocation alloc;
667 
668       if (!gtk_widget_get_visible (section->box))
669         continue;
670 
671       if (section->heading)
672         gtk_widget_get_allocation (section->heading, &alloc);
673       else
674         gtk_widget_get_allocation (section->box, &alloc);
675 
676       if (value < alloc.y - BOX_SPACE)
677         break;
678 
679       select_section = section;
680     }
681 
682   /* Un/Check the section buttons accordingly */
683   for (i = 0; i < G_N_ELEMENTS (sections); ++i)
684     {
685       EmojiSection const *section = sections[i];
686 
687       if (section == select_section)
688         gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE);
689       else
690         gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED);
691     }
692 }
693 
694 static gboolean
match_tokens(const char ** term_tokens,const char ** hit_tokens)695 match_tokens (const char **term_tokens,
696               const char **hit_tokens)
697 {
698   int i, j;
699   gboolean matched;
700 
701   matched = TRUE;
702 
703   for (i = 0; term_tokens[i]; i++)
704     {
705       for (j = 0; hit_tokens[j]; j++)
706         if (g_str_has_prefix (hit_tokens[j], term_tokens[i]))
707           goto one_matched;
708 
709       matched = FALSE;
710       break;
711 
712 one_matched:
713       continue;
714     }
715 
716   return matched;
717 }
718 
719 static gboolean
filter_func(GtkFlowBoxChild * child,gpointer data)720 filter_func (GtkFlowBoxChild *child,
721              gpointer         data)
722 {
723   EmojiSection *section = data;
724   GtkEmojiChooser *chooser;
725   GVariant *emoji_data;
726   const char *text;
727   const char *name;
728   char **term_tokens;
729   char **name_tokens;
730   gboolean res;
731 
732   res = TRUE;
733 
734   chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER));
735   text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry));
736   emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data");
737 
738   if (text[0] == 0)
739     goto out;
740 
741   if (!emoji_data)
742     goto out;
743 
744   term_tokens = g_str_tokenize_and_fold (text, "en", NULL);
745 
746   g_variant_get_child (emoji_data, 1, "&s", &name);
747   name_tokens = g_str_tokenize_and_fold (name, "en", NULL);
748 
749   res = match_tokens ((const char **)term_tokens, (const char **)name_tokens);
750 
751   if (g_variant_is_of_type (emoji_data, G_VARIANT_TYPE ("(ausasu)")))
752     {
753       const char **keywords;
754 
755       g_variant_get_child (emoji_data, 2, "^a&s", &keywords);
756       res |= match_tokens ((const char **)term_tokens, keywords);
757     }
758 
759   g_strfreev (term_tokens);
760   g_strfreev (name_tokens);
761 
762 out:
763   if (res)
764     section->empty = FALSE;
765 
766   return res;
767 }
768 
769 static void
invalidate_section(EmojiSection * section)770 invalidate_section (EmojiSection *section)
771 {
772   section->empty = TRUE;
773   gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
774 }
775 
776 static void
update_headings(GtkEmojiChooser * chooser)777 update_headings (GtkEmojiChooser *chooser)
778 {
779   gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty);
780   gtk_widget_set_visible (chooser->people.box, !chooser->people.empty);
781   gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty);
782   gtk_widget_set_visible (chooser->body.box, !chooser->body.empty);
783   gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty);
784   gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty);
785   gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty);
786   gtk_widget_set_visible (chooser->food.box, !chooser->food.empty);
787   gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty);
788   gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty);
789   gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty);
790   gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty);
791   gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty);
792   gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty);
793   gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty);
794   gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty);
795   gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty);
796   gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty);
797 
798   if (chooser->recent.empty && chooser->people.empty &&
799       chooser->body.empty && chooser->nature.empty &&
800       chooser->food.empty && chooser->travel.empty &&
801       chooser->activities.empty && chooser->objects.empty &&
802       chooser->symbols.empty && chooser->flags.empty)
803     gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty");
804   else
805     gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list");
806 }
807 
808 static void
search_changed(GtkEntry * entry,gpointer data)809 search_changed (GtkEntry *entry,
810                 gpointer  data)
811 {
812   GtkEmojiChooser *chooser = data;
813 
814   invalidate_section (&chooser->recent);
815   invalidate_section (&chooser->people);
816   invalidate_section (&chooser->body);
817   invalidate_section (&chooser->nature);
818   invalidate_section (&chooser->food);
819   invalidate_section (&chooser->travel);
820   invalidate_section (&chooser->activities);
821   invalidate_section (&chooser->objects);
822   invalidate_section (&chooser->symbols);
823   invalidate_section (&chooser->flags);
824 
825   update_headings (chooser);
826 }
827 
828 static void
setup_section(GtkEmojiChooser * chooser,EmojiSection * section,int group,const char * icon)829 setup_section (GtkEmojiChooser *chooser,
830                EmojiSection    *section,
831                int              group,
832                const char      *icon)
833 {
834   GtkAdjustment *adj;
835   GtkWidget *image;
836 
837   section->group = group;
838 
839   image = gtk_bin_get_child (GTK_BIN (section->button));
840   gtk_image_set_from_icon_name (GTK_IMAGE (image), icon, GTK_ICON_SIZE_BUTTON);
841 
842   adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
843 
844   gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj);
845   gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL);
846   g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section);
847 }
848 
849 static void
gtk_emoji_chooser_init(GtkEmojiChooser * chooser)850 gtk_emoji_chooser_init (GtkEmojiChooser *chooser)
851 {
852   GtkAdjustment *adj;
853 
854   chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser");
855 
856   gtk_widget_init_template (GTK_WIDGET (chooser));
857 
858   /* Get a reasonable maximum width for an emoji. We do this to
859    * skip overly wide fallback rendering for certain emojis the
860    * font does not contain and therefore end up being rendered
861    * as multiply glyphs.
862    */
863   {
864     PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), "��");
865     PangoAttrList *attrs;
866     PangoRectangle rect;
867 
868     attrs = pango_attr_list_new ();
869     pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
870     pango_layout_set_attributes (layout, attrs);
871     pango_attr_list_unref (attrs);
872 
873     pango_layout_get_extents (layout, &rect, NULL);
874     chooser->emoji_max_width = rect.width;
875 
876     g_object_unref (layout);
877   }
878 
879   chooser->recent_long_press = gtk_gesture_long_press_new (chooser->recent.box);
880   g_signal_connect (chooser->recent_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
881   chooser->recent_multi_press = gtk_gesture_multi_press_new (chooser->recent.box);
882   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->recent_multi_press), GDK_BUTTON_SECONDARY);
883   g_signal_connect (chooser->recent_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
884 
885   chooser->people_long_press = gtk_gesture_long_press_new (chooser->people.box);
886   g_signal_connect (chooser->people_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
887   chooser->people_multi_press = gtk_gesture_multi_press_new (chooser->people.box);
888   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->people_multi_press), GDK_BUTTON_SECONDARY);
889   g_signal_connect (chooser->people_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
890 
891   chooser->body_long_press = gtk_gesture_long_press_new (chooser->body.box);
892   g_signal_connect (chooser->body_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
893   chooser->body_multi_press = gtk_gesture_multi_press_new (chooser->body.box);
894   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->body_multi_press), GDK_BUTTON_SECONDARY);
895   g_signal_connect (chooser->body_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
896 
897   adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
898   g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser);
899 
900   setup_section (chooser, &chooser->recent, -1, "emoji-recent-symbolic");
901   setup_section (chooser, &chooser->people, 0, "emoji-people-symbolic");
902   setup_section (chooser, &chooser->body, 1, "emoji-body-symbolic");
903   setup_section (chooser, &chooser->nature, 3, "emoji-nature-symbolic");
904   setup_section (chooser, &chooser->food, 4, "emoji-food-symbolic");
905   setup_section (chooser, &chooser->travel, 5, "emoji-travel-symbolic");
906   setup_section (chooser, &chooser->activities, 6, "emoji-activities-symbolic");
907   setup_section (chooser, &chooser->objects, 7, "emoji-objects-symbolic");
908   setup_section (chooser, &chooser->symbols, 8, "emoji-symbols-symbolic");
909   setup_section (chooser, &chooser->flags, 9, "emoji-flags-symbolic");
910 
911   populate_recent_section (chooser);
912 
913   chooser->populate_idle = g_idle_add (populate_emoji_chooser, chooser);
914   g_source_set_name_by_id (chooser->populate_idle, "[gtk] populate_emoji_chooser");
915 }
916 
917 static void
gtk_emoji_chooser_show(GtkWidget * widget)918 gtk_emoji_chooser_show (GtkWidget *widget)
919 {
920   GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
921   GtkAdjustment *adj;
922 
923   GTK_WIDGET_CLASS (gtk_emoji_chooser_parent_class)->show (widget);
924 
925   adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
926   gtk_adjustment_set_value (adj, 0);
927   adj_value_changed (adj, chooser);
928 
929   gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), "");
930 }
931 
932 static void
gtk_emoji_chooser_class_init(GtkEmojiChooserClass * klass)933 gtk_emoji_chooser_class_init (GtkEmojiChooserClass *klass)
934 {
935   GObjectClass *object_class = G_OBJECT_CLASS (klass);
936   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
937 
938   object_class->finalize = gtk_emoji_chooser_finalize;
939   widget_class->show = gtk_emoji_chooser_show;
940 
941   signals[EMOJI_PICKED] = g_signal_new ("emoji-picked",
942                                         G_OBJECT_CLASS_TYPE (object_class),
943                                         G_SIGNAL_RUN_LAST,
944                                         0,
945                                         NULL, NULL,
946                                         NULL,
947                                         G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE);
948 
949   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkemojichooser.ui");
950 
951   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, search_entry);
952   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, stack);
953   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, scrolled_window);
954 
955   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.box);
956   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.button);
957 
958   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.box);
959   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.heading);
960   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.button);
961 
962   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.box);
963   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.heading);
964   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.button);
965 
966   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.box);
967   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.heading);
968   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.button);
969 
970   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.box);
971   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.heading);
972   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.button);
973 
974   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.box);
975   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.heading);
976   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.button);
977 
978   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.box);
979   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.heading);
980   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.button);
981 
982   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.box);
983   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.heading);
984   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.button);
985 
986   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.box);
987   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.heading);
988   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.button);
989 
990   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.box);
991   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.heading);
992   gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.button);
993 
994   gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
995   gtk_widget_class_bind_template_callback (widget_class, search_changed);
996 }
997 
998 GtkWidget *
gtk_emoji_chooser_new(void)999 gtk_emoji_chooser_new (void)
1000 {
1001   return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL));
1002 }
1003