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 "gtkflowboxprivate.h"
28#include "gtkstack.h"
29#include "gtklabel.h"
30#include "gtkgesturelongpress.h"
31#include "gtkpopover.h"
32#include "gtkscrolledwindow.h"
33#include "gtkintl.h"
34#include "gtksearchentryprivate.h"
35#include "gtktext.h"
36#include "gtknative.h"
37#include "gtkwidgetprivate.h"
38#include "gdk/gdkprofilerprivate.h"
39#include "gtkmain.h"
40#include "gtkprivate.h"
41
42/**
43 * GtkEmojiChooser:
44 *
45 * The `GtkEmojiChooser` is used by text widgets such as `GtkEntry` or
46 * `GtkTextView` to let users insert Emoji characters.
47 *
48 * ![An example GtkEmojiChooser](emojichooser.png)
49 *
50 * `GtkEmojiChooser` emits the [signal@Gtk.EmojiChooser::emoji-picked]
51 * signal when an Emoji is selected.
52 *
53 * # CSS nodes
54 *
55 * ```
56 * popover
57 * ├── box.emoji-searchbar
58 * │ ╰── entry.search
59 * ╰── box.emoji-toolbar
60 * ├── button.image-button.emoji-section
61 * ├── ...
62 * ╰── button.image-button.emoji-section
63 * ```
64 *
65 * Every `GtkEmojiChooser` consists of a main node called popover.
66 * The contents of the popover are largely implementation defined
67 * and supposed to inherit general styles.
68 * The top searchbar used to search emoji and gets the .emoji-searchbar
69 * style class itself.
70 * The bottom toolbar used to switch between different emoji categories
71 * consists of buttons with the .emoji-section style class and gets the
72 * .emoji-toolbar style class itself.
73 */
74
75#define BOX_SPACE 6
76
77GType gtk_emoji_chooser_child_get_type (void);
78
79#define GTK_TYPE_EMOJI_CHOOSER_CHILD (gtk_emoji_chooser_child_get_type ())
80
81typedef struct
82{
83 GtkFlowBoxChild parent;
84 GtkWidget *variations;
85} GtkEmojiChooserChild;
86
87typedef struct
88{
89 GtkFlowBoxChildClass parent_class;
90} GtkEmojiChooserChildClass;
91
92G_DEFINE_TYPE (GtkEmojiChooserChild, gtk_emoji_chooser_child, GTK_TYPE_FLOW_BOX_CHILD)
93
94static void
95gtk_emoji_chooser_child_init (GtkEmojiChooserChild *child)
96{
97}
98
99static void
100gtk_emoji_chooser_child_dispose (GObject *object)
101{
102 GtkEmojiChooserChild *child = (GtkEmojiChooserChild *)object;
103
104 g_clear_pointer (&child->variations, gtk_widget_unparent);
105
106 G_OBJECT_CLASS (gtk_emoji_chooser_child_parent_class)->dispose (object);
107}
108
109static void
110gtk_emoji_chooser_child_size_allocate (GtkWidget *widget,
111 int width,
112 int height,
113 int baseline)
114{
115 GtkEmojiChooserChild *child = (GtkEmojiChooserChild *)widget;
116
117 GTK_WIDGET_CLASS (gtk_emoji_chooser_child_parent_class)->size_allocate (widget, width, height, baseline);
118 if (child->variations)
119 gtk_popover_present (GTK_POPOVER (child->variations));
120}
121
122static gboolean
123gtk_emoji_chooser_child_focus (GtkWidget *widget,
124 GtkDirectionType direction)
125{
126 GtkEmojiChooserChild *child = (GtkEmojiChooserChild *)widget;
127
128 if (child->variations && gtk_widget_is_visible (widget: child->variations))
129 {
130 if (gtk_widget_child_focus (widget: child->variations, direction))
131 return TRUE;
132 }
133
134 return GTK_WIDGET_CLASS (gtk_emoji_chooser_child_parent_class)->focus (widget, direction);
135}
136
137static void scroll_to_child (GtkWidget *child);
138
139static gboolean
140gtk_emoji_chooser_child_grab_focus (GtkWidget *widget)
141{
142 gtk_widget_grab_focus_self (widget);
143 scroll_to_child (child: widget);
144 return TRUE;
145}
146
147static void show_variations (GtkEmojiChooser *chooser,
148 GtkWidget *child);
149
150static void
151gtk_emoji_chooser_child_popup_menu (GtkWidget *widget,
152 const char *action_name,
153 GVariant *parameters)
154{
155 GtkWidget *chooser;
156
157 chooser = gtk_widget_get_ancestor (widget, GTK_TYPE_EMOJI_CHOOSER);
158
159 show_variations (GTK_EMOJI_CHOOSER (chooser), child: widget);
160}
161
162static void
163gtk_emoji_chooser_child_class_init (GtkEmojiChooserChildClass *class)
164{
165 GObjectClass *object_class = G_OBJECT_CLASS (class);
166 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
167
168 object_class->dispose = gtk_emoji_chooser_child_dispose;
169 widget_class->size_allocate = gtk_emoji_chooser_child_size_allocate;
170 widget_class->focus = gtk_emoji_chooser_child_focus;
171 widget_class->grab_focus = gtk_emoji_chooser_child_grab_focus;
172
173 gtk_widget_class_install_action (widget_class, action_name: "menu.popup", NULL, activate: gtk_emoji_chooser_child_popup_menu);
174
175 gtk_widget_class_add_binding_action (widget_class,
176 GDK_KEY_F10, mods: GDK_SHIFT_MASK,
177 action_name: "menu.popup",
178 NULL);
179 gtk_widget_class_add_binding_action (widget_class,
180 GDK_KEY_Menu, mods: 0,
181 action_name: "menu.popup",
182 NULL);
183
184 gtk_widget_class_set_css_name (widget_class, name: "emoji");
185}
186
187typedef struct {
188 GtkWidget *box;
189 GtkWidget *heading;
190 GtkWidget *button;
191 int group;
192 gunichar label;
193 gboolean empty;
194} EmojiSection;
195
196struct _GtkEmojiChooser
197{
198 GtkPopover parent_instance;
199
200 GtkWidget *search_entry;
201 GtkWidget *stack;
202 GtkWidget *scrolled_window;
203
204 int emoji_max_width;
205
206 EmojiSection recent;
207 EmojiSection people;
208 EmojiSection body;
209 EmojiSection nature;
210 EmojiSection food;
211 EmojiSection travel;
212 EmojiSection activities;
213 EmojiSection objects;
214 EmojiSection symbols;
215 EmojiSection flags;
216
217 GVariant *data;
218 GtkWidget *box;
219 GVariantIter *iter;
220 guint populate_idle;
221
222 GSettings *settings;
223};
224
225struct _GtkEmojiChooserClass {
226 GtkPopoverClass parent_class;
227};
228
229enum {
230 EMOJI_PICKED,
231 LAST_SIGNAL
232};
233
234static int signals[LAST_SIGNAL];
235
236G_DEFINE_TYPE (GtkEmojiChooser, gtk_emoji_chooser, GTK_TYPE_POPOVER)
237
238static void
239gtk_emoji_chooser_finalize (GObject *object)
240{
241 GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (object);
242
243 if (chooser->populate_idle)
244 g_source_remove (tag: chooser->populate_idle);
245
246 g_clear_pointer (&chooser->data, g_variant_unref);
247 g_clear_object (&chooser->settings);
248
249 G_OBJECT_CLASS (gtk_emoji_chooser_parent_class)->finalize (object);
250}
251
252static void
253scroll_to_section (EmojiSection *section)
254{
255 GtkEmojiChooser *chooser;
256 GtkAdjustment *adj;
257 GtkAllocation alloc = { 0, 0, 0, 0 };
258
259 chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (section->box, GTK_TYPE_EMOJI_CHOOSER));
260
261 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
262 if (section->heading)
263 gtk_widget_get_allocation (widget: section->heading, allocation: &alloc);
264 gtk_adjustment_animate_to_value (adjustment: adj, value: alloc.y - BOX_SPACE);
265}
266
267static void
268scroll_to_child (GtkWidget *child)
269{
270 GtkEmojiChooser *chooser;
271 GtkAdjustment *adj;
272 GtkAllocation alloc;
273 double pos;
274 double value;
275 double page_size;
276
277 chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (child, GTK_TYPE_EMOJI_CHOOSER));
278
279 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
280
281 gtk_widget_get_allocation (widget: child, allocation: &alloc);
282
283 value = gtk_adjustment_get_value (adjustment: adj);
284 page_size = gtk_adjustment_get_page_size (adjustment: adj);
285
286 gtk_widget_translate_coordinates (src_widget: child, dest_widget: gtk_widget_get_parent (widget: chooser->recent.box), src_x: 0, src_y: 0, NULL, dest_y: &pos);
287
288 if (pos < value)
289 gtk_adjustment_animate_to_value (adjustment: adj, value: pos);
290 else if (pos + alloc.height >= value + page_size)
291 gtk_adjustment_animate_to_value (adjustment: adj, value: value + ((pos + alloc.height) - (value + page_size)));
292}
293
294static void
295add_emoji (GtkWidget *box,
296 gboolean prepend,
297 GVariant *item,
298 gunichar modifier,
299 GtkEmojiChooser *chooser);
300
301#define MAX_RECENT (7*3)
302
303static void
304populate_recent_section (GtkEmojiChooser *chooser)
305{
306 GVariant *variant;
307 GVariant *item;
308 GVariantIter iter;
309 gboolean empty = FALSE;
310
311 variant = g_settings_get_value (settings: chooser->settings, key: "recent-emoji");
312 g_variant_iter_init (iter: &iter, value: variant);
313 while ((item = g_variant_iter_next_value (iter: &iter)))
314 {
315 GVariant *emoji_data;
316 gunichar modifier;
317
318 emoji_data = g_variant_get_child_value (value: item, index_: 0);
319 g_variant_get_child (value: item, index_: 1, format_string: "u", &modifier);
320 add_emoji (box: chooser->recent.box, FALSE, item: emoji_data, modifier, chooser);
321 g_variant_unref (value: emoji_data);
322 g_variant_unref (value: item);
323 empty = FALSE;
324 }
325
326 gtk_widget_set_visible (widget: chooser->recent.box, visible: !empty);
327 gtk_widget_set_sensitive (widget: chooser->recent.button, sensitive: !empty);
328
329 g_variant_unref (value: variant);
330}
331
332static void
333add_recent_item (GtkEmojiChooser *chooser,
334 GVariant *item,
335 gunichar modifier)
336{
337 GList *children, *l;
338 int i;
339 GVariantBuilder builder;
340 GtkWidget *child;
341
342 g_variant_ref (value: item);
343
344 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE ("a((ausasu)u)"));
345 g_variant_builder_add (builder: &builder, format_string: "(@(ausasu)u)", item, modifier);
346
347 children = NULL;
348 for (child = gtk_widget_get_last_child (widget: chooser->recent.box);
349 child != NULL;
350 child = gtk_widget_get_prev_sibling (widget: child))
351 children = g_list_prepend (list: children, data: child);
352
353 for (l = children, i = 1; l; l = l->next, i++)
354 {
355 GVariant *item2 = g_object_get_data (G_OBJECT (l->data), key: "emoji-data");
356 gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier"));
357
358 if (modifier == modifier2 && g_variant_equal (one: item, two: item2))
359 {
360 gtk_flow_box_remove (GTK_FLOW_BOX (chooser->recent.box), widget: l->data);
361 i--;
362 continue;
363 }
364 if (i >= MAX_RECENT)
365 {
366 gtk_flow_box_remove (GTK_FLOW_BOX (chooser->recent.box), widget: l->data);
367 continue;
368 }
369
370 g_variant_builder_add (builder: &builder, format_string: "(@(ausasu)u)", item2, modifier2);
371 }
372 g_list_free (list: children);
373
374 add_emoji (box: chooser->recent.box, TRUE, item, modifier, chooser);
375
376 /* Enable recent */
377 gtk_widget_show (widget: chooser->recent.box);
378 gtk_widget_set_sensitive (widget: chooser->recent.button, TRUE);
379
380 g_settings_set_value (settings: chooser->settings, key: "recent-emoji", value: g_variant_builder_end (builder: &builder));
381
382 g_variant_unref (value: item);
383}
384
385static gboolean
386should_close (GtkEmojiChooser *chooser)
387{
388 GdkDisplay *display;
389 GdkSeat *seat;
390 GdkDevice *device;
391 GdkModifierType state;
392
393 display = gtk_widget_get_display (GTK_WIDGET (chooser));
394 seat = gdk_display_get_default_seat (display);
395 device = gdk_seat_get_keyboard (seat);
396 state = gdk_device_get_modifier_state (device);
397
398 return (state & GDK_CONTROL_MASK) == 0;
399}
400
401static void
402emoji_activated (GtkFlowBox *box,
403 GtkFlowBoxChild *child,
404 gpointer data)
405{
406 GtkEmojiChooser *chooser = data;
407 char *text;
408 GtkWidget *label;
409 GVariant *item;
410 gunichar modifier;
411
412 if (should_close (chooser))
413 gtk_popover_popdown (GTK_POPOVER (chooser));
414 else
415 {
416 GtkWidget *popover;
417
418 popover = gtk_widget_get_ancestor (GTK_WIDGET (box), GTK_TYPE_POPOVER);
419 if (popover != GTK_WIDGET (chooser))
420 gtk_popover_popdown (GTK_POPOVER (popover));
421 }
422
423 label = gtk_flow_box_child_get_child (self: child);
424 text = g_strdup (str: gtk_label_get_label (GTK_LABEL (label)));
425
426 item = (GVariant*) g_object_get_data (G_OBJECT (child), key: "emoji-data");
427 modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier"));
428 add_recent_item (chooser, item, modifier);
429
430 g_signal_emit (instance: data, signal_id: signals[EMOJI_PICKED], detail: 0, text);
431 g_free (mem: text);
432}
433
434static gboolean
435has_variations (GVariant *emoji_data)
436{
437 GVariant *codes;
438 int i;
439 gboolean has_variations;
440
441 has_variations = FALSE;
442 codes = g_variant_get_child_value (value: emoji_data, index_: 0);
443 for (i = 0; i < g_variant_n_children (value: codes); i++)
444 {
445 gunichar code;
446 g_variant_get_child (value: codes, index_: i, format_string: "u", &code);
447 if (code == 0)
448 {
449 has_variations = TRUE;
450 break;
451 }
452 }
453 g_variant_unref (value: codes);
454
455 return has_variations;
456}
457
458static void
459show_variations (GtkEmojiChooser *chooser,
460 GtkWidget *child)
461{
462 GtkWidget *popover;
463 GtkWidget *view;
464 GtkWidget *box;
465 GVariant *emoji_data;
466 GtkWidget *parent_popover;
467 gunichar modifier;
468 GtkEmojiChooserChild *ch = (GtkEmojiChooserChild *)child;
469
470 if (!child)
471 return;
472
473 emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), key: "emoji-data");
474 if (!emoji_data)
475 return;
476
477 if (!has_variations (emoji_data))
478 return;
479
480 parent_popover = gtk_widget_get_ancestor (widget: child, GTK_TYPE_POPOVER);
481 g_clear_pointer (&ch->variations, gtk_widget_unparent);
482 popover = ch->variations = gtk_popover_new ();
483 gtk_popover_set_autohide (GTK_POPOVER (popover), TRUE);
484 gtk_widget_set_parent (widget: popover, parent: child);
485 view = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
486 gtk_widget_add_css_class (widget: view, css_class: "view");
487 box = gtk_flow_box_new ();
488 gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE);
489 gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), n_children: 6);
490 gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), n_children: 6);
491 gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE);
492 gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), mode: GTK_SELECTION_NONE);
493 g_object_set (object: box, first_property_name: "accept-unpaired-release", TRUE, NULL);
494 gtk_popover_set_child (GTK_POPOVER (popover), child: view);
495 gtk_box_append (GTK_BOX (view), child: box);
496
497 g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover);
498
499 add_emoji (box, FALSE, item: emoji_data, modifier: 0, chooser);
500 for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++)
501 add_emoji (box, FALSE, item: emoji_data, modifier, chooser);
502
503 gtk_popover_popup (GTK_POPOVER (popover));
504}
505
506static void
507long_pressed_cb (GtkGesture *gesture,
508 double x,
509 double y,
510 gpointer data)
511{
512 GtkEmojiChooser *chooser = data;
513 GtkWidget *box;
514 GtkWidget *child;
515
516 box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
517 child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
518 show_variations (chooser, child);
519}
520
521static void
522pressed_cb (GtkGesture *gesture,
523 int n_press,
524 double x,
525 double y,
526 gpointer data)
527{
528 GtkEmojiChooser *chooser = data;
529 GtkWidget *box;
530 GtkWidget *child;
531
532 box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
533 child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
534 show_variations (chooser, child);
535}
536
537static void
538add_emoji (GtkWidget *box,
539 gboolean prepend,
540 GVariant *item,
541 gunichar modifier,
542 GtkEmojiChooser *chooser)
543{
544 GtkWidget *child;
545 GtkWidget *label;
546 PangoAttrList *attrs;
547 GVariant *codes;
548 char text[64];
549 char *p = text;
550 int i;
551 PangoLayout *layout;
552 PangoRectangle rect;
553
554 codes = g_variant_get_child_value (value: item, index_: 0);
555 for (i = 0; i < g_variant_n_children (value: codes); i++)
556 {
557 gunichar code;
558
559 g_variant_get_child (value: codes, index_: i, format_string: "u", &code);
560 if (code == 0)
561 code = modifier;
562 if (code != 0)
563 p += g_unichar_to_utf8 (c: code, outbuf: p);
564 }
565 g_variant_unref (value: codes);
566 p += g_unichar_to_utf8 (c: 0xFE0F, outbuf: p); /* U+FE0F is the Emoji variation selector */
567 p[0] = 0;
568
569 label = gtk_label_new (str: text);
570 attrs = pango_attr_list_new ();
571 pango_attr_list_insert (list: attrs, attr: pango_attr_scale_new (PANGO_SCALE_X_LARGE));
572 gtk_label_set_attributes (GTK_LABEL (label), attrs);
573 pango_attr_list_unref (list: attrs);
574
575 layout = gtk_label_get_layout (GTK_LABEL (label));
576 pango_layout_get_extents (layout, ink_rect: &rect, NULL);
577
578 /* Check for fallback rendering that generates too wide items */
579 if (pango_layout_get_unknown_glyphs_count (layout) > 0 ||
580 rect.width >= 1.5 * chooser->emoji_max_width)
581 {
582 g_object_ref_sink (label);
583 g_object_unref (object: label);
584 return;
585 }
586
587 child = g_object_new (GTK_TYPE_EMOJI_CHOOSER_CHILD, NULL);
588 g_object_set_data_full (G_OBJECT (child), key: "emoji-data",
589 data: g_variant_ref (value: item),
590 destroy: (GDestroyNotify)g_variant_unref);
591 if (modifier != 0)
592 g_object_set_data (G_OBJECT (child), key: "modifier", GUINT_TO_POINTER (modifier));
593
594 gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (child), child: label);
595 gtk_flow_box_insert (GTK_FLOW_BOX (box), widget: child, position: prepend ? 0 : -1);
596}
597
598static GBytes *
599get_emoji_data_by_language (const char *lang)
600{
601 GBytes *bytes;
602 char *path;
603 GError *error = NULL;
604
605 path = g_strconcat (string1: "/org/gtk/libgtk/emoji/", lang, ".data", NULL);
606 bytes = g_resources_lookup_data (path, lookup_flags: 0, error: &error);
607 if (bytes)
608 {
609 g_debug ("Found emoji data for %s in resource %s", lang, path);
610 g_free (mem: path);
611 return bytes;
612 }
613
614 if (g_error_matches (error, G_RESOURCE_ERROR, code: G_RESOURCE_ERROR_NOT_FOUND))
615 {
616 char *filename;
617 char *gresource_name;
618 GMappedFile *file;
619
620 g_clear_error (err: &error);
621
622 gresource_name = g_strconcat (string1: lang, ".gresource", NULL);
623 filename = g_build_filename (first_element: _gtk_get_data_prefix (), "share", "gtk-4.0",
624 "emoji", gresource_name, NULL);
625 g_clear_pointer (&gresource_name, g_free);
626 file = g_mapped_file_new (filename, FALSE, NULL);
627
628 if (file)
629 {
630 GBytes *data;
631 GResource *resource;
632
633 data = g_mapped_file_get_bytes (file);
634 g_mapped_file_unref (file);
635
636 resource = g_resource_new_from_data (data, NULL);
637 g_bytes_unref (bytes: data);
638
639 g_debug ("Registering resource for Emoji data for %s from file %s", lang, filename);
640 g_resources_register (resource);
641 g_resource_unref (resource);
642
643 bytes = g_resources_lookup_data (path, lookup_flags: 0, NULL);
644 if (bytes)
645 {
646 g_debug ("Found emoji data for %s in resource %s", lang, path);
647 g_free (mem: path);
648 g_free (mem: filename);
649 return bytes;
650 }
651 }
652
653 g_free (mem: filename);
654 }
655
656 g_clear_error (err: &error);
657 g_free (mem: path);
658
659 return NULL;
660}
661
662GBytes *
663get_emoji_data (void)
664{
665 GBytes *bytes;
666 const char *lang;
667
668 lang = pango_language_to_string (gtk_get_default_language ());
669 bytes = get_emoji_data_by_language (lang);
670 if (bytes)
671 return bytes;
672
673 if (strchr (s: lang, c: '-'))
674 {
675 char q[5];
676 int i;
677
678 for (i = 0; lang[i] != '-' && i < 4; i++)
679 q[i] = lang[i];
680 q[i] = '\0';
681
682 bytes = get_emoji_data_by_language (lang: q);
683 if (bytes)
684 return bytes;
685 }
686
687 bytes = get_emoji_data_by_language (lang: "en");
688 g_assert (bytes);
689
690 return bytes;
691}
692
693static gboolean
694populate_emoji_chooser (gpointer data)
695{
696 GtkEmojiChooser *chooser = data;
697 GVariant *item;
698 gint64 start, now;
699
700 start = g_get_monotonic_time ();
701
702 if (!chooser->data)
703 {
704 GBytes *bytes;
705
706 bytes = get_emoji_data ();
707
708 chooser->data = g_variant_ref_sink (value: g_variant_new_from_bytes (G_VARIANT_TYPE ("a(ausasu)"), bytes, TRUE));
709 g_bytes_unref (bytes);
710 }
711
712 if (!chooser->iter)
713 {
714 chooser->iter = g_variant_iter_new (value: chooser->data);
715 chooser->box = chooser->people.box;
716 }
717
718 while ((item = g_variant_iter_next_value (iter: chooser->iter)))
719 {
720 guint group;
721
722 g_variant_get_child (value: item, index_: 3, format_string: "u", &group);
723
724 if (group == chooser->people.group)
725 chooser->box = chooser->people.box;
726 else if (group == chooser->body.group)
727 chooser->box = chooser->body.box;
728 else if (group == chooser->nature.group)
729 chooser->box = chooser->nature.box;
730 else if (group == chooser->food.group)
731 chooser->box = chooser->food.box;
732 else if (group == chooser->travel.group)
733 chooser->box = chooser->travel.box;
734 else if (group == chooser->activities.group)
735 chooser->box = chooser->activities.box;
736 else if (group == chooser->objects.group)
737 chooser->box = chooser->objects.box;
738 else if (group == chooser->symbols.group)
739 chooser->box = chooser->symbols.box;
740 else if (group == chooser->flags.group)
741 chooser->box = chooser->flags.box;
742
743 add_emoji (box: chooser->box, FALSE, item, modifier: 0, chooser);
744 g_variant_unref (value: item);
745
746 now = g_get_monotonic_time ();
747 if (now > start + 200) /* 2 ms */
748 {
749 gdk_profiler_add_mark (start * 1000, (now - start) * 1000, "emojichooser", "populate");
750 return G_SOURCE_CONTINUE;
751 }
752 }
753
754 g_variant_iter_free (iter: chooser->iter);
755 chooser->iter = NULL;
756 chooser->box = NULL;
757 chooser->populate_idle = 0;
758
759 gdk_profiler_end_mark (start, "emojichooser", "populate (finish)");
760
761 return G_SOURCE_REMOVE;
762}
763
764static void
765adj_value_changed (GtkAdjustment *adj,
766 gpointer data)
767{
768 GtkEmojiChooser *chooser = data;
769 double value = gtk_adjustment_get_value (adjustment: adj);
770 EmojiSection const *sections[] = {
771 &chooser->recent,
772 &chooser->people,
773 &chooser->body,
774 &chooser->nature,
775 &chooser->food,
776 &chooser->travel,
777 &chooser->activities,
778 &chooser->objects,
779 &chooser->symbols,
780 &chooser->flags,
781 };
782 EmojiSection const *select_section = sections[0];
783 gsize i;
784
785 /* Figure out which section the current scroll position is within */
786 for (i = 0; i < G_N_ELEMENTS (sections); ++i)
787 {
788 EmojiSection const *section = sections[i];
789 GtkAllocation alloc;
790
791 if (!gtk_widget_get_visible (widget: section->box))
792 continue;
793
794 if (section->heading)
795 gtk_widget_get_allocation (widget: section->heading, allocation: &alloc);
796 else
797 gtk_widget_get_allocation (widget: section->box, allocation: &alloc);
798
799 if (value < alloc.y - BOX_SPACE)
800 break;
801
802 select_section = section;
803 }
804
805 /* Un/Check the section buttons accordingly */
806 for (i = 0; i < G_N_ELEMENTS (sections); ++i)
807 {
808 EmojiSection const *section = sections[i];
809
810 if (section == select_section)
811 gtk_widget_set_state_flags (widget: section->button, flags: GTK_STATE_FLAG_CHECKED, FALSE);
812 else
813 gtk_widget_unset_state_flags (widget: section->button, flags: GTK_STATE_FLAG_CHECKED);
814 }
815}
816
817static gboolean
818match_tokens (const char **term_tokens,
819 const char **hit_tokens)
820{
821 int i, j;
822 gboolean matched;
823
824 matched = TRUE;
825
826 for (i = 0; term_tokens[i]; i++)
827 {
828 for (j = 0; hit_tokens[j]; j++)
829 if (g_str_has_prefix (str: hit_tokens[j], prefix: term_tokens[i]))
830 goto one_matched;
831
832 matched = FALSE;
833 break;
834
835one_matched:
836 continue;
837 }
838
839 return matched;
840}
841
842static gboolean
843filter_func (GtkFlowBoxChild *child,
844 gpointer data)
845{
846 EmojiSection *section = data;
847 GtkEmojiChooser *chooser;
848 GVariant *emoji_data;
849 const char *text;
850 const char *name;
851 const char **keywords;
852 char **term_tokens;
853 char **name_tokens;
854 gboolean res;
855
856 res = TRUE;
857
858 chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER));
859 text = gtk_editable_get_text (GTK_EDITABLE (chooser->search_entry));
860 emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), key: "emoji-data");
861
862 if (text[0] == 0)
863 goto out;
864
865 if (!emoji_data)
866 goto out;
867
868 term_tokens = g_str_tokenize_and_fold (string: text, translit_locale: "en", NULL);
869
870 g_variant_get_child (value: emoji_data, index_: 1, format_string: "&s", &name);
871 name_tokens = g_str_tokenize_and_fold (string: name, translit_locale: "en", NULL);
872 g_variant_get_child (value: emoji_data, index_: 2, format_string: "^a&s", &keywords);
873
874 res = match_tokens (term_tokens: (const char **)term_tokens, hit_tokens: (const char **)name_tokens) ||
875 match_tokens (term_tokens: (const char **)term_tokens, hit_tokens: keywords);
876
877 g_strfreev (str_array: term_tokens);
878 g_strfreev (str_array: name_tokens);
879
880out:
881 if (res)
882 section->empty = FALSE;
883
884 return res;
885}
886
887static void
888invalidate_section (EmojiSection *section)
889{
890 section->empty = TRUE;
891 gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
892}
893
894static void
895update_headings (GtkEmojiChooser *chooser)
896{
897 gtk_widget_set_visible (widget: chooser->people.heading, visible: !chooser->people.empty);
898 gtk_widget_set_visible (widget: chooser->people.box, visible: !chooser->people.empty);
899 gtk_widget_set_visible (widget: chooser->body.heading, visible: !chooser->body.empty);
900 gtk_widget_set_visible (widget: chooser->body.box, visible: !chooser->body.empty);
901 gtk_widget_set_visible (widget: chooser->nature.heading, visible: !chooser->nature.empty);
902 gtk_widget_set_visible (widget: chooser->nature.box, visible: !chooser->nature.empty);
903 gtk_widget_set_visible (widget: chooser->food.heading, visible: !chooser->food.empty);
904 gtk_widget_set_visible (widget: chooser->food.box, visible: !chooser->food.empty);
905 gtk_widget_set_visible (widget: chooser->travel.heading, visible: !chooser->travel.empty);
906 gtk_widget_set_visible (widget: chooser->travel.box, visible: !chooser->travel.empty);
907 gtk_widget_set_visible (widget: chooser->activities.heading, visible: !chooser->activities.empty);
908 gtk_widget_set_visible (widget: chooser->activities.box, visible: !chooser->activities.empty);
909 gtk_widget_set_visible (widget: chooser->objects.heading, visible: !chooser->objects.empty);
910 gtk_widget_set_visible (widget: chooser->objects.box, visible: !chooser->objects.empty);
911 gtk_widget_set_visible (widget: chooser->symbols.heading, visible: !chooser->symbols.empty);
912 gtk_widget_set_visible (widget: chooser->symbols.box, visible: !chooser->symbols.empty);
913 gtk_widget_set_visible (widget: chooser->flags.heading, visible: !chooser->flags.empty);
914 gtk_widget_set_visible (widget: chooser->flags.box, visible: !chooser->flags.empty);
915
916 if (chooser->recent.empty && chooser->people.empty &&
917 chooser->body.empty && chooser->nature.empty &&
918 chooser->food.empty && chooser->travel.empty &&
919 chooser->activities.empty && chooser->objects.empty &&
920 chooser->symbols.empty && chooser->flags.empty)
921 gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), name: "empty");
922 else
923 gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), name: "list");
924}
925
926static void
927search_changed (GtkEntry *entry,
928 gpointer data)
929{
930 GtkEmojiChooser *chooser = data;
931
932 invalidate_section (section: &chooser->recent);
933 invalidate_section (section: &chooser->people);
934 invalidate_section (section: &chooser->body);
935 invalidate_section (section: &chooser->nature);
936 invalidate_section (section: &chooser->food);
937 invalidate_section (section: &chooser->travel);
938 invalidate_section (section: &chooser->activities);
939 invalidate_section (section: &chooser->objects);
940 invalidate_section (section: &chooser->symbols);
941 invalidate_section (section: &chooser->flags);
942
943 update_headings (chooser);
944}
945
946static void
947stop_search (GtkEntry *entry,
948 gpointer data)
949{
950 gtk_popover_popdown (GTK_POPOVER (data));
951}
952
953static void
954setup_section (GtkEmojiChooser *chooser,
955 EmojiSection *section,
956 int group,
957 const char *icon)
958{
959 section->group = group;
960
961 gtk_button_set_icon_name (GTK_BUTTON (section->button), icon_name: icon);
962
963 gtk_flow_box_disable_move_cursor (GTK_FLOW_BOX (section->box));
964 gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, user_data: section, NULL);
965 g_signal_connect_swapped (section->button, "clicked", G_CALLBACK (scroll_to_section), section);
966}
967
968static void
969gtk_emoji_chooser_init (GtkEmojiChooser *chooser)
970{
971 GtkAdjustment *adj;
972 GtkText *text;
973
974 chooser->settings = g_settings_new (schema_id: "org.gtk.gtk4.Settings.EmojiChooser");
975
976 gtk_widget_init_template (GTK_WIDGET (chooser));
977
978 text = gtk_search_entry_get_text_widget (GTK_SEARCH_ENTRY (chooser->search_entry));
979 gtk_text_set_input_hints (self: text, hints: GTK_INPUT_HINT_NO_EMOJI);
980
981 /* Get a reasonable maximum width for an emoji. We do this to
982 * skip overly wide fallback rendering for certain emojis the
983 * font does not contain and therefore end up being rendered
984 * as multiply glyphs.
985 */
986 {
987 PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), text: "🙂");
988 PangoAttrList *attrs;
989 PangoRectangle rect;
990
991 attrs = pango_attr_list_new ();
992 pango_attr_list_insert (list: attrs, attr: pango_attr_scale_new (PANGO_SCALE_X_LARGE));
993 pango_layout_set_attributes (layout, attrs);
994 pango_attr_list_unref (list: attrs);
995
996 pango_layout_get_extents (layout, ink_rect: &rect, NULL);
997 chooser->emoji_max_width = rect.width;
998
999 g_object_unref (object: layout);
1000 }
1001
1002 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
1003 g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser);
1004
1005 setup_section (chooser, section: &chooser->recent, group: -1, icon: "emoji-recent-symbolic");
1006 setup_section (chooser, section: &chooser->people, group: 0, icon: "emoji-people-symbolic");
1007 setup_section (chooser, section: &chooser->body, group: 1, icon: "emoji-body-symbolic");
1008 setup_section (chooser, section: &chooser->nature, group: 3, icon: "emoji-nature-symbolic");
1009 setup_section (chooser, section: &chooser->food, group: 4, icon: "emoji-food-symbolic");
1010 setup_section (chooser, section: &chooser->travel, group: 5, icon: "emoji-travel-symbolic");
1011 setup_section (chooser, section: &chooser->activities, group: 6, icon: "emoji-activities-symbolic");
1012 setup_section (chooser, section: &chooser->objects, group: 7, icon: "emoji-objects-symbolic");
1013 setup_section (chooser, section: &chooser->symbols, group: 8, icon: "emoji-symbols-symbolic");
1014 setup_section (chooser, section: &chooser->flags, group: 9, icon: "emoji-flags-symbolic");
1015
1016 populate_recent_section (chooser);
1017
1018 chooser->populate_idle = g_idle_add (function: populate_emoji_chooser, data: chooser);
1019 gdk_source_set_static_name_by_id (tag: chooser->populate_idle, name: "[gtk] populate_emoji_chooser");
1020}
1021
1022static void
1023gtk_emoji_chooser_show (GtkWidget *widget)
1024{
1025 GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
1026 GtkAdjustment *adj;
1027
1028 GTK_WIDGET_CLASS (gtk_emoji_chooser_parent_class)->show (widget);
1029
1030 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
1031 gtk_adjustment_set_value (adjustment: adj, value: 0);
1032 adj_value_changed (adj, data: chooser);
1033
1034 gtk_editable_set_text (GTK_EDITABLE (chooser->search_entry), text: "");
1035}
1036
1037static EmojiSection *
1038find_section (GtkEmojiChooser *chooser,
1039 GtkWidget *box)
1040{
1041 if (box == chooser->recent.box)
1042 return &chooser->recent;
1043 else if (box == chooser->people.box)
1044 return &chooser->people;
1045 else if (box == chooser->body.box)
1046 return &chooser->body;
1047 else if (box == chooser->nature.box)
1048 return &chooser->nature;
1049 else if (box == chooser->food.box)
1050 return &chooser->food;
1051 else if (box == chooser->travel.box)
1052 return &chooser->travel;
1053 else if (box == chooser->activities.box)
1054 return &chooser->activities;
1055 else if (box == chooser->objects.box)
1056 return &chooser->objects;
1057 else if (box == chooser->symbols.box)
1058 return &chooser->symbols;
1059 else if (box == chooser->flags.box)
1060 return &chooser->flags;
1061 else
1062 return NULL;
1063}
1064
1065static EmojiSection *
1066find_next_section (GtkEmojiChooser *chooser,
1067 GtkWidget *box,
1068 gboolean down)
1069{
1070 EmojiSection *next;
1071
1072 if (box == chooser->recent.box)
1073 next = down ? &chooser->people : NULL;
1074 else if (box == chooser->people.box)
1075 next = down ? &chooser->body : &chooser->recent;
1076 else if (box == chooser->body.box)
1077 next = down ? &chooser->nature : &chooser->people;
1078 else if (box == chooser->nature.box)
1079 next = down ? &chooser->food : &chooser->body;
1080 else if (box == chooser->food.box)
1081 next = down ? &chooser->travel : &chooser->nature;
1082 else if (box == chooser->travel.box)
1083 next = down ? &chooser->activities : &chooser->food;
1084 else if (box == chooser->activities.box)
1085 next = down ? &chooser->objects : &chooser->travel;
1086 else if (box == chooser->objects.box)
1087 next = down ? &chooser->symbols : &chooser->activities;
1088 else if (box == chooser->symbols.box)
1089 next = down ? &chooser->flags : &chooser->objects;
1090 else if (box == chooser->flags.box)
1091 next = down ? NULL : &chooser->symbols;
1092 else
1093 next = NULL;
1094
1095 return next;
1096}
1097
1098static void
1099gtk_emoji_chooser_scroll_section (GtkWidget *widget,
1100 const char *action_name,
1101 GVariant *parameter)
1102{
1103 GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
1104 int direction = g_variant_get_int32 (value: parameter);
1105 GtkWidget *focus;
1106 GtkWidget *box;
1107 EmojiSection *next;
1108
1109 focus = gtk_root_get_focus (self: gtk_widget_get_root (widget));
1110 if (focus == NULL)
1111 return;
1112
1113 if (gtk_widget_is_ancestor (widget: focus, ancestor: chooser->search_entry))
1114 box = chooser->recent.box;
1115 else
1116 box = gtk_widget_get_ancestor (widget: focus, GTK_TYPE_FLOW_BOX);
1117
1118 next = find_next_section (chooser, box, down: direction > 0);
1119
1120 if (next)
1121 {
1122 gtk_widget_child_focus (widget: next->box, direction: GTK_DIR_TAB_FORWARD);
1123 scroll_to_section (section: next);
1124 }
1125}
1126
1127static gboolean
1128keynav_failed (GtkWidget *box,
1129 GtkDirectionType direction,
1130 GtkEmojiChooser *chooser)
1131{
1132 EmojiSection *next;
1133 GtkWidget *focus;
1134 GtkWidget *child;
1135 GtkWidget *sibling;
1136 GtkAllocation alloc;
1137 int i;
1138 int column;
1139 int child_x;
1140
1141 focus = gtk_root_get_focus (self: gtk_widget_get_root (widget: box));
1142 if (focus == NULL)
1143 return FALSE;
1144
1145 child = gtk_widget_get_ancestor (widget: focus, GTK_TYPE_EMOJI_CHOOSER_CHILD);
1146
1147 column = 0;
1148 child_x = G_MAXINT;
1149 for (sibling = gtk_widget_get_first_child (widget: box);
1150 sibling;
1151 sibling = gtk_widget_get_next_sibling (widget: sibling))
1152 {
1153 if (!gtk_widget_get_child_visible (widget: sibling))
1154 continue;
1155
1156 gtk_widget_get_allocation (widget: sibling, allocation: &alloc);
1157
1158 if (alloc.x < child_x)
1159 column = 0;
1160 else
1161 column++;
1162
1163 child_x = alloc.x;
1164
1165 if (sibling == child)
1166 break;
1167 }
1168
1169 if (direction == GTK_DIR_DOWN)
1170 {
1171 next = find_section (chooser, box);
1172 while (TRUE)
1173 {
1174 next = find_next_section (chooser, box: next->box, TRUE);
1175 if (next == NULL)
1176 return FALSE;
1177
1178 i = 0;
1179 child_x = G_MAXINT;
1180 for (sibling = gtk_widget_get_first_child (widget: next->box);
1181 sibling;
1182 sibling = gtk_widget_get_next_sibling (widget: sibling))
1183 {
1184 if (!gtk_widget_get_child_visible (widget: sibling))
1185 continue;
1186
1187 gtk_widget_get_allocation (widget: sibling, allocation: &alloc);
1188
1189 if (alloc.x < child_x)
1190 i = 0;
1191 else
1192 i++;
1193
1194 child_x = alloc.x;
1195
1196 if (i == column)
1197 {
1198 gtk_widget_grab_focus (widget: sibling);
1199 return TRUE;
1200 }
1201 }
1202 }
1203 }
1204 else if (direction == GTK_DIR_UP)
1205 {
1206 next = find_section (chooser, box);
1207 while (TRUE)
1208 {
1209 next = find_next_section (chooser, box: next->box, FALSE);
1210 if (next == NULL)
1211 return FALSE;
1212
1213 i = 0;
1214 child_x = G_MAXINT;
1215 child = NULL;
1216 for (sibling = gtk_widget_get_first_child (widget: next->box);
1217 sibling;
1218 sibling = gtk_widget_get_next_sibling (widget: sibling))
1219 {
1220 if (!gtk_widget_get_child_visible (widget: sibling))
1221 continue;
1222
1223 gtk_widget_get_allocation (widget: sibling, allocation: &alloc);
1224
1225 if (alloc.x < child_x)
1226 i = 0;
1227 else
1228 i++;
1229
1230 child_x = alloc.x;
1231
1232 if (i == column)
1233 child = sibling;
1234 }
1235
1236 if (child)
1237 {
1238 gtk_widget_grab_focus (widget: child);
1239 return TRUE;
1240 }
1241 }
1242 }
1243
1244 return FALSE;
1245}
1246
1247static void
1248gtk_emoji_chooser_map (GtkWidget *widget)
1249{
1250 GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
1251
1252 GTK_WIDGET_CLASS (gtk_emoji_chooser_parent_class)->map (widget);
1253
1254 gtk_widget_grab_focus (widget: chooser->search_entry);
1255}
1256
1257static void
1258gtk_emoji_chooser_class_init (GtkEmojiChooserClass *klass)
1259{
1260 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1261 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1262
1263 object_class->finalize = gtk_emoji_chooser_finalize;
1264 widget_class->show = gtk_emoji_chooser_show;
1265 widget_class->map = gtk_emoji_chooser_map;
1266
1267 /**
1268 * GtkEmojiChooser::emoji-picked:
1269 * @chooser: the `GtkEmojiChooser`
1270 * @text: the Unicode sequence for the picked Emoji, in UTF-8
1271 *
1272 * Emitted when the user selects an Emoji.
1273 */
1274 signals[EMOJI_PICKED] = g_signal_new (signal_name: "emoji-picked",
1275 G_OBJECT_CLASS_TYPE (object_class),
1276 signal_flags: G_SIGNAL_RUN_LAST,
1277 class_offset: 0,
1278 NULL, NULL,
1279 NULL,
1280 G_TYPE_NONE, n_params: 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE);
1281
1282 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtkemojichooser.ui");
1283
1284 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, search_entry);
1285 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, stack);
1286 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, scrolled_window);
1287
1288 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.box);
1289 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.button);
1290
1291 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.box);
1292 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.heading);
1293 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.button);
1294
1295 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.box);
1296 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.heading);
1297 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.button);
1298
1299 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.box);
1300 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.heading);
1301 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.button);
1302
1303 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.box);
1304 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.heading);
1305 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.button);
1306
1307 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.box);
1308 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.heading);
1309 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.button);
1310
1311 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.box);
1312 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.heading);
1313 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.button);
1314
1315 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.box);
1316 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.heading);
1317 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.button);
1318
1319 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.box);
1320 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.heading);
1321 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.button);
1322
1323 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.box);
1324 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.heading);
1325 gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.button);
1326
1327 gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
1328 gtk_widget_class_bind_template_callback (widget_class, search_changed);
1329 gtk_widget_class_bind_template_callback (widget_class, stop_search);
1330 gtk_widget_class_bind_template_callback (widget_class, pressed_cb);
1331 gtk_widget_class_bind_template_callback (widget_class, long_pressed_cb);
1332 gtk_widget_class_bind_template_callback (widget_class, keynav_failed);
1333
1334 /**
1335 * GtkEmojiChooser|scroll.section:
1336 * @direction: 1 to scroll forward, -1 to scroll back
1337 *
1338 * Scrolls to the next or previous section.
1339 */
1340 gtk_widget_class_install_action (widget_class, action_name: "scroll.section", parameter_type: "i",
1341 activate: gtk_emoji_chooser_scroll_section);
1342
1343 gtk_widget_class_add_binding_action (widget_class, GDK_KEY_n, mods: GDK_CONTROL_MASK,
1344 action_name: "scroll.section", format_string: "i", 1);
1345 gtk_widget_class_add_binding_action (widget_class, GDK_KEY_p, mods: GDK_CONTROL_MASK,
1346 action_name: "scroll.section", format_string: "i", -1);
1347}
1348
1349/**
1350 * gtk_emoji_chooser_new:
1351 *
1352 * Creates a new `GtkEmojiChooser`.
1353 *
1354 * Returns: a new `GtkEmojiChooser`
1355 */
1356GtkWidget *
1357gtk_emoji_chooser_new (void)
1358{
1359 return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL));
1360}
1361

source code of gtk/gtk/gtkemojichooser.c