[gtk+/wip/matthiasc/emoji-picker] Move the chooser to GTK+ proper



commit 104fd3c75ceeef47abd8f7138740aa2bf842eedf
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Aug 10 23:25:50 2017 -0400

    Move the chooser to GTK+ proper
    
    We keep it private for now, and just expose it via
    a new GtkEntry::emoji-input property. The recent list
    is now persisted in a GSetting.

 gtk/Makefile.am                               |    7 +-
 gtk/emoji.data                                |  Bin 0 -> 53562 bytes
 gtk/gtkemojichooser.c                         |  595 +++++++++++++++++++++++++
 gtk/gtkemojichooser.h                         |   41 ++
 gtk/gtkentry.c                                |  110 +++++
 gtk/org.gtk.Settings.EmojiChooser.gschema.xml |   16 +
 gtk/theme/Adwaita/_common.scss                |   49 ++
 gtk/theme/Adwaita/gtk-contained-dark.css      |   26 ++
 gtk/theme/Adwaita/gtk-contained.css           |   26 ++
 gtk/ui/gtkemojichooser.ui                     |  256 +++++++++++
 tests/Makefile.am                             |    1 -
 tests/testentryicons.c                        |   48 +--
 12 files changed, 1126 insertions(+), 49 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index eca8907..563888b 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -451,6 +451,7 @@ gtk_private_h_sources =             \
        gtkdebugupdatesprivate.h        \
        gtkdialogprivate.h      \
        gtkdndprivate.h         \
+       gtkemojichooser.h       \
        gtkentryprivate.h       \
        gtkeventcontrollerprivate.h     \
        gtkfilechooserembed.h   \
@@ -722,6 +723,7 @@ gtk_base_c_sources =                \
        gtkdragsource.c         \
        gtkdrawingarea.c        \
        gtkeditable.c           \
+       gtkemojichooser.c       \
        gtkentry.c              \
        gtkentrybuffer.c        \
        gtkentrycompletion.c    \
@@ -1147,7 +1149,8 @@ templates =                               \
        ui/gtkcolorchooserdialog.ui     \
        ui/gtkcoloreditor.ui            \
        ui/gtkcombobox.ui               \
-       ui/gtkdialog.ui         \
+       ui/gtkdialog.ui                 \
+       ui/gtkemojichooser.ui           \
        ui/gtkfilechooserwidget.ui      \
        ui/gtkfilechooserdialog.ui      \
        ui/gtkfontchooserdialog.ui      \
@@ -1277,6 +1280,7 @@ gtk.gresource.xml: Makefile.am inspector/Makefile.inc
          echo "    <file preprocess='xml-stripblanks'>inspector/$$n</file>" >> $@; \
        done; \
        echo "    <file>inspector/logo.png</file>" >> $@; \
+       echo "    <file>emoji.data</file>" >> $@; \
        echo "  </gresource>" >> $@; \
        echo "</gresources>" >> $@;
 
@@ -1689,6 +1693,7 @@ files:
 gsettings_SCHEMAS = \
        org.gtk.Settings.FileChooser.gschema.xml \
        org.gtk.Settings.ColorChooser.gschema.xml \
+       org.gtk.Settings.EmojiChooser.gschema.xml \
        org.gtk.Settings.Debug.gschema.xml
 
 @GSETTINGS_RULES@
diff --git a/gtk/emoji.data b/gtk/emoji.data
new file mode 100644
index 0000000..61d1af0
Binary files /dev/null and b/gtk/emoji.data differ
diff --git a/gtk/gtkemojichooser.c b/gtk/gtkemojichooser.c
new file mode 100644
index 0000000..0dab683
--- /dev/null
+++ b/gtk/gtkemojichooser.c
@@ -0,0 +1,595 @@
+/* gtkemojichooser.c: An Emoji chooser widget
+ * Copyright 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkemojichooser.h"
+
+#include "gtkadjustment.h"
+#include "gtkbox.h"
+#include "gtkbutton.h"
+#include "gtkcssprovider.h"
+#include "gtkentry.h"
+#include "gtkflowbox.h"
+#include "gtklabel.h"
+#include "gtkgesturelongpress.h"
+#include "gtkpopover.h"
+#include "gtkscrolledwindow.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+typedef struct {
+  GtkWidget *box;
+  GtkWidget *heading;
+  GtkWidget *button;
+  const char *first;
+  gunichar label;
+} EmojiSection;
+
+struct _GtkEmojiChooser
+{
+  GtkPopover parent_instance;
+
+  GtkWidget *scrolled_window;
+  GtkWidget *search_entry;
+
+  EmojiSection recent;
+  EmojiSection people;
+  EmojiSection body;
+  EmojiSection nature;
+  EmojiSection food;
+  EmojiSection travel;
+  EmojiSection activities;
+  EmojiSection objects;
+  EmojiSection symbols;
+  EmojiSection flags;
+
+  EmojiSection *scroll_to_section;
+
+  GtkGesture *recent_press;
+  GtkGesture *people_press;
+  GtkGesture *body_press;
+
+  GVariant *data;
+
+  GSettings *settings;
+};
+
+struct _GtkEmojiChooserClass {
+  GtkPopoverClass parent_class;
+};
+
+enum {
+  EMOJI_PICKED,
+  LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GtkEmojiChooser, gtk_emoji_chooser, GTK_TYPE_POPOVER)
+
+static void
+gtk_emoji_chooser_finalize (GObject *object)
+{
+  GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (object);
+
+  g_variant_unref (chooser->data);
+  g_object_unref (chooser->settings);
+
+  G_OBJECT_CLASS (gtk_emoji_chooser_parent_class)->finalize (object);
+}
+
+static gboolean
+scroll_in_idle (gpointer data)
+{
+  GtkEmojiChooser *chooser = data;
+  GtkAdjustment *adj;
+  GtkAllocation alloc = { 0, 0, 0, 0 };
+  double page_increment, value;
+  gboolean dummy;
+
+  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+  if (chooser->scroll_to_section->heading)
+    gtk_widget_get_allocation (chooser->scroll_to_section->heading, &alloc);
+  page_increment = gtk_adjustment_get_page_increment (adj);
+  value = gtk_adjustment_get_value (adj);
+  gtk_adjustment_set_page_increment (adj, alloc.y - value);
+  g_signal_emit_by_name (chooser->scrolled_window, "scroll-child", GTK_SCROLL_PAGE_FORWARD, FALSE, &dummy);
+  gtk_adjustment_set_page_increment (adj, page_increment);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+scroll_to_section (GtkButton *button,
+                   gpointer   data)
+{
+  EmojiSection *section = data;
+  GtkEmojiChooser *chooser;
+
+  chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER));
+
+  if (chooser->scroll_to_section == section)
+    return;
+
+  chooser->scroll_to_section = section;
+  g_idle_add (scroll_in_idle, chooser);
+}
+
+static void
+add_emoji (GtkWidget    *box,
+           gboolean      prepend,
+           GVariantIter *iter,
+           GVariant     *data);
+
+#define MAX_RECENT (7*3)
+
+static void
+add_recent_item (GtkEmojiChooser *chooser,
+                 GVariant       *item)
+{
+  GList *children, *l;
+  GVariantIter *codes;
+  const char *name;
+  int i;
+  GVariantBuilder builder;
+
+  g_variant_ref (item);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ausaau)"));
+  g_variant_builder_add_value (&builder, item);
+
+  g_variant_get_child (item, 1, "&s", &name);
+
+  children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box));
+  for (l = children, i = 1; l; l = l->next, i++)
+    {
+      GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data");
+      const char *name2;
+
+      g_variant_get_child (item2, 1, "&s", &name2);
+      if (strcmp (name, name2) == 0)
+        {
+          gtk_widget_destroy (GTK_WIDGET (l->data));
+          i--;
+          continue;
+        }
+      if (i >= MAX_RECENT)
+        {
+          gtk_widget_destroy (GTK_WIDGET (l->data));
+          continue;
+        }
+
+      g_variant_builder_add_value (&builder, item2);
+    }
+  g_list_free (children);
+
+  g_variant_get_child (item, 0, "au", &codes);
+  add_emoji (chooser->recent.box, TRUE, codes, item);
+  g_variant_iter_free (codes);
+
+  g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder));
+
+  g_variant_unref (item);
+}
+
+static void
+emoji_activated (GtkFlowBox      *box,
+                 GtkFlowBoxChild *child,
+                 gpointer         data)
+{
+  GtkEmojiChooser *chooser = data;
+  char *text;
+  GtkWidget *label;
+  GVariant *item;
+
+  gtk_popover_popdown (GTK_POPOVER (chooser));
+
+  label = gtk_bin_get_child (GTK_BIN (child));
+  text = g_strdup (gtk_label_get_label (GTK_LABEL (label)));
+
+  item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
+  add_recent_item (chooser, item);
+
+  g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
+  g_free (text);
+}
+
+static void
+long_pressed_cb (GtkGesture *gesture,
+                 double      x,
+                 double      y,
+                 gpointer    data)
+{
+  GtkWidget *child;
+  GtkWidget *popover;
+  GtkWidget *view;
+  GtkWidget *box;
+  GVariant *emoji_data;
+  GtkWidget *parent_popover;
+  GVariantIter *iter;
+  GVariantIter *codes;
+
+  box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+  child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
+  if (!child)
+    return;
+
+  emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
+  if (!emoji_data)
+    return;
+
+  g_variant_get_child (emoji_data, 2, "aau", &iter);
+  if (g_variant_iter_n_children (iter) == 0)
+    {
+      g_variant_iter_free (iter);
+      return;
+    }
+
+  parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER);
+  popover = gtk_popover_new (child);
+  view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_style_context_add_class (gtk_widget_get_style_context (view), "view");
+  box = gtk_flow_box_new ();
+  gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE);
+  gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 6);
+  gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 6);
+  gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE);
+  gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE);
+  gtk_container_add (GTK_CONTAINER (popover), view);
+  gtk_container_add (GTK_CONTAINER (view), box);
+
+  g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover);
+
+  g_variant_get_child (emoji_data, 0, "au", &codes);
+  add_emoji (box, FALSE, codes, emoji_data);
+  g_variant_iter_free (codes);
+  while (g_variant_iter_next (iter, "au", &codes))
+    {
+      add_emoji (box, FALSE, codes, emoji_data);
+      g_variant_iter_free (codes);
+    }
+
+  gtk_popover_popup (GTK_POPOVER (popover));
+}
+
+static void
+add_emoji (GtkWidget    *box,
+           gboolean      prepend,
+           GVariantIter *iter,
+           GVariant     *data)
+{
+  GtkWidget *child;
+  GtkWidget *label;
+  PangoAttrList *attrs;
+  char text[64];
+  char *p = text;
+  gunichar code;
+
+  while (g_variant_iter_next (iter, "u", &code))
+    p += g_unichar_to_utf8 (code, p);
+  p[0] = 0;
+
+  label = gtk_label_new (text);
+  attrs = pango_attr_list_new ();
+  pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
+  gtk_label_set_attributes (GTK_LABEL (label), attrs);
+  pango_attr_list_unref (attrs);
+
+  child = gtk_flow_box_child_new ();
+  gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji");
+  g_object_set_data_full (G_OBJECT (child), "emoji-data",
+                          g_variant_ref (data),
+                          (GDestroyNotify)g_variant_unref);
+
+  gtk_container_add (GTK_CONTAINER (child), label);
+  gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1);
+}
+
+static void
+populate_emoji_chooser (GtkEmojiChooser *chooser)
+{
+  g_autoptr(GBytes) bytes = NULL;
+  GVariantIter iter;
+  GVariant *item;
+  GtkWidget *box;
+
+  bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji.data", 0, NULL);
+  chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(ausaau)"), bytes, TRUE));
+
+  g_variant_iter_init (&iter, chooser->data);
+  box = chooser->people.box;
+  while ((item = g_variant_iter_next_value (&iter)))
+    {
+      GVariantIter *codes;
+      const char *name;
+
+      g_variant_get_child (item, 0, "au", &codes);
+      g_variant_get_child (item, 1, "&s", &name);
+
+      if (strcmp (name, chooser->body.first) == 0)
+        box = chooser->body.box;
+      else if (strcmp (name, chooser->nature.first) == 0)
+        box = chooser->nature.box;
+      else if (strcmp (name, chooser->food.first) == 0)
+        box = chooser->food.box;
+      else if (strcmp (name, chooser->travel.first) == 0)
+        box = chooser->travel.box;
+      else if (strcmp (name, chooser->activities.first) == 0)
+        box = chooser->activities.box;
+      else if (strcmp (name, chooser->objects.first) == 0)
+        box = chooser->objects.box;
+      else if (strcmp (name, chooser->symbols.first) == 0)
+        box = chooser->symbols.box;
+      else if (strcmp (name, chooser->flags.first) == 0)
+        box = chooser->flags.box;
+
+      add_emoji (box, FALSE, codes, item);
+      g_variant_iter_free (codes);
+    }
+}
+
+static void
+update_state (EmojiSection *section,
+              double        value)
+{
+  GtkAllocation alloc = { 0, 0, 0, 20 };
+
+  if (section->heading)
+    gtk_widget_get_allocation (section->heading, &alloc);
+
+  if (alloc.y <= value && value < alloc.y + alloc.height)
+    gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE);
+  else
+    gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED);
+}
+
+static void
+adj_value_changed (GtkAdjustment *adj,
+                   gpointer       data)
+{
+  GtkEmojiChooser *chooser = data;
+  double value = gtk_adjustment_get_value (adj);
+
+  update_state (&chooser->recent, value);
+  update_state (&chooser->people, value);
+  update_state (&chooser->body, value);
+  update_state (&chooser->nature, value);
+  update_state (&chooser->food, value);
+  update_state (&chooser->travel, value);
+  update_state (&chooser->activities, value);
+  update_state (&chooser->objects, value);
+  update_state (&chooser->symbols, value);
+  update_state (&chooser->flags, value);
+}
+
+static gboolean
+filter_func (GtkFlowBoxChild *child,
+             gpointer         data)
+{
+  EmojiSection *section = data;
+  GtkEmojiChooser *chooser;
+  GVariant *emoji_data;
+  const char *text;
+  const char *name;
+  gboolean res;
+
+  res = TRUE;
+
+  chooser = GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER));
+  text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry));
+  emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data");
+
+  if (text[0] == 0 || text[1] == 0)
+    goto out;
+
+  if (!emoji_data)
+    goto out;
+
+  g_variant_get_child (emoji_data, 1, "&s", &name);
+  res = strstr (name, text) != NULL;
+
+out:
+  if (res && section->heading)
+    gtk_widget_show (section->heading);
+
+  return res;
+}
+
+static void
+invalidate_section (EmojiSection *section)
+{
+  if (section->heading)
+    gtk_widget_hide (section->heading);
+  gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
+}
+
+static void
+search_changed (GtkEntry *entry,
+                gpointer  data)
+{
+  GtkEmojiChooser *chooser = data;
+
+  invalidate_section (&chooser->recent);
+  invalidate_section (&chooser->people);
+  invalidate_section (&chooser->body);
+  invalidate_section (&chooser->nature);
+  invalidate_section (&chooser->food);
+  invalidate_section (&chooser->travel);
+  invalidate_section (&chooser->activities);
+  invalidate_section (&chooser->objects);
+  invalidate_section (&chooser->symbols);
+  invalidate_section (&chooser->flags);
+}
+
+static void
+setup_section (GtkEmojiChooser *chooser,
+               EmojiSection   *section,
+               const char     *first,
+               gunichar        label)
+{
+  char text[14];
+  char *p;
+  GtkAdjustment *adj;
+
+  section->first = first;
+
+  p = text;
+  p += g_unichar_to_utf8 (label, p);
+  p += g_unichar_to_utf8 (0xfe0e, p);
+  p[0] = 0;
+  gtk_button_set_label (GTK_BUTTON (section->button), text);
+
+  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+
+  gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj);
+  gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL);
+  g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section);
+}
+
+static void
+gtk_emoji_chooser_init (GtkEmojiChooser *chooser)
+{
+  GtkAdjustment *adj;
+  GVariant *variant;
+  GVariantIter iter;
+  GVariant *item;
+
+  gtk_widget_init_template (GTK_WIDGET (chooser));
+
+  chooser->recent_press = gtk_gesture_long_press_new (chooser->recent.box);
+  g_signal_connect (chooser->recent_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+
+  chooser->people_press = gtk_gesture_long_press_new (chooser->people.box);
+  g_signal_connect (chooser->people_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+
+  chooser->body_press = gtk_gesture_long_press_new (chooser->body.box);
+  g_signal_connect (chooser->body_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+
+  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+  g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser);
+
+  setup_section (chooser, &chooser->recent, NULL, 0x1f557);
+  setup_section (chooser, &chooser->people, "grinning face", 0x1f642);
+  setup_section (chooser, &chooser->body, "selfie", 0x1f44d);
+  setup_section (chooser, &chooser->nature, "monkey face", 0x1f337);
+  setup_section (chooser, &chooser->food, "grapes", 0x1f374);
+  setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", 0x2708);
+  setup_section (chooser, &chooser->activities, "jack-o-lantern", 0x1f3c3);
+  setup_section (chooser, &chooser->objects, "muted speaker", 0x1f514);
+  setup_section (chooser, &chooser->symbols, "ATM sign", 0x2764);
+  setup_section (chooser, &chooser->flags, "chequered flag", 0x1f3f4);
+
+  populate_emoji_chooser (chooser);
+
+  chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser");
+  variant = g_settings_get_value (chooser->settings, "recent-emoji");
+  g_variant_iter_init (&iter, variant);
+  while ((item = g_variant_iter_next_value (&iter)))
+    {
+      GVariantIter *codes;
+
+      g_variant_get_child (item, 0, "au", &codes);
+      add_emoji (chooser->recent.box, FALSE, codes, item);
+      g_variant_iter_free (codes);
+    }
+}
+
+static void
+gtk_emoji_chooser_show (GtkWidget *widget)
+{
+  GtkEmojiChooser *chooser = GTK_EMOJI_CHOOSER (widget);
+  GtkAdjustment *adj;
+
+  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+  gtk_adjustment_set_value (adj, 0);
+
+  gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), "");
+
+  GTK_WIDGET_CLASS (gtk_emoji_chooser_parent_class)->show (widget);
+}
+
+static void
+gtk_emoji_chooser_class_init (GtkEmojiChooserClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gtk_emoji_chooser_finalize;
+  widget_class->show = gtk_emoji_chooser_show;
+
+  signals[EMOJI_PICKED] = g_signal_new ("emoji-picked",
+                                        G_OBJECT_CLASS_TYPE (object_class),
+                                        G_SIGNAL_RUN_LAST,
+                                        0,
+                                        NULL, NULL,
+                                        NULL,
+                                        G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkemojichooser.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, scrolled_window);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, search_entry);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, recent.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, people.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, body.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, nature.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, food.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, travel.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, activities.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, objects.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, symbols.button);
+
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.box);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.heading);
+  gtk_widget_class_bind_template_child (widget_class, GtkEmojiChooser, flags.button);
+
+  gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
+  gtk_widget_class_bind_template_callback (widget_class, search_changed);
+}
+
+GtkWidget *
+gtk_emoji_chooser_new (void)
+{
+  return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL));
+}
diff --git a/gtk/gtkemojichooser.h b/gtk/gtkemojichooser.h
new file mode 100644
index 0000000..63670b5
--- /dev/null
+++ b/gtk/gtkemojichooser.h
@@ -0,0 +1,41 @@
+/* gtkemojichooser.h: An Emoji chooser widget
+ * Copyright 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_EMOJI_CHOOSER                 (gtk_emoji_chooser_get_type ())
+#define GTK_EMOJI_CHOOSER(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_CHOOSER, 
GtkEmojiChooser))
+#define GTK_EMOJI_CHOOSER_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_CHOOSER, 
GtkEmojiChooserClass))
+#define GTK_IS_EMOJI_CHOOSER(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_CHOOSER))
+#define GTK_IS_EMOJI_CHOOSER_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_CHOOSER))
+#define GTK_EMOJI_CHOOSER_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_CHOOSER, 
GtkEmojiChooserClass))
+
+typedef struct _GtkEmojiChooser      GtkEmojiChooser;
+typedef struct _GtkEmojiChooserClass GtkEmojiChooserClass;
+
+GType      gtk_emoji_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget *gtk_emoji_chooser_new      (void);
+
+G_END_DECLS
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index b33f4fd..fd3a19c 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -68,6 +68,7 @@
 #include "gtkmagnifierprivate.h"
 #include "gtkcssnodeprivate.h"
 #include "gtkimageprivate.h"
+#include "gtkemojichooser.h"
 
 #include "a11y/gtkentryaccessible.h"
 
@@ -236,6 +237,7 @@ struct _GtkEntryPrivate
   gint64        handle_place_time;
 
   guint         editable                : 1;
+  guint         emoji_input             : 1;
   guint         in_drag                 : 1;
   guint         overwrite_mode          : 1;
   guint         visible                 : 1;
@@ -355,6 +357,7 @@ enum {
   PROP_ATTRIBUTES,
   PROP_POPULATE_ALL,
   PROP_TABS,
+  PROP_EMOJI_INPUT,
   PROP_EDITING_CANCELED,
   NUM_PROPERTIES = PROP_EDITING_CANCELED
 };
@@ -646,6 +649,8 @@ static void         buffer_notify_max_length           (GtkEntryBuffer *buffer,
 static void         buffer_connect_signals             (GtkEntry       *entry);
 static void         buffer_disconnect_signals          (GtkEntry       *entry);
 static GtkEntryBuffer *get_buffer                      (GtkEntry       *entry);
+static void         set_emoji_input                    (GtkEntry       *entry,
+                                                        gboolean        value);
 
 static void     gtk_entry_measure (GtkWidget           *widget,
                                    GtkOrientation       orientation,
@@ -1376,6 +1381,13 @@ gtk_entry_class_init (GtkEntryClass *class)
                           PANGO_TYPE_TAB_ARRAY,
                           GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
 
+  entry_props[PROP_EMOJI_INPUT] =
+      g_param_spec_boolean ("emoji-input",
+                            P_("Emoji input"),
+                            P_("Whether to support Emoji input"),
+                            FALSE,
+                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
   g_object_class_install_properties (gobject_class, NUM_PROPERTIES, entry_props);
 
   /**
@@ -2131,6 +2143,10 @@ gtk_entry_set_property (GObject         *object,
       gtk_entry_set_tabs (entry, g_value_get_boxed (value));
       break;
 
+    case PROP_EMOJI_INPUT:
+      set_emoji_input (entry, g_value_get_boolean (value));
+      break;
+
     case PROP_SCROLL_OFFSET:
     case PROP_CURSOR_POSITION:
     default:
@@ -2359,6 +2375,10 @@ gtk_entry_get_property (GObject         *object,
       g_value_set_boxed (value, priv->tabs);
       break;
 
+    case PROP_EMOJI_INPUT:
+      g_value_set_boolean (value, priv->emoji_input);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -8407,6 +8427,8 @@ typedef struct
   GdkEvent *trigger_event;
 } PopupInfo;
 
+static void gtk_entry_choose_emoji (GtkEntry *entry);
+
 static void
 popup_targets_received (GtkClipboard     *clipboard,
                        GtkSelectionData *data,
@@ -8463,6 +8485,16 @@ popup_targets_received (GtkClipboard     *clipboard,
       gtk_widget_show (menuitem);
       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
 
+      if (info_entry_priv->emoji_input)
+        {
+          menuitem = gtk_menu_item_new_with_mnemonic (_("Insert _Emoji"));
+          gtk_widget_set_sensitive (menuitem, info_entry_priv->editable);
+          g_signal_connect_swapped (menuitem, "activate",
+                                    G_CALLBACK (gtk_entry_choose_emoji), entry);
+          gtk_widget_show (menuitem);
+          gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+        }
+
       g_signal_emit (entry, signals[POPULATE_POPUP], 0, menu);
 
       if (info->trigger_event && gdk_event_triggers_context_menu (info->trigger_event))
@@ -9793,3 +9825,81 @@ gtk_entry_get_tabs (GtkEntry *entry)
 
   return entry->priv->tabs;
 }
+
+static void
+emoji_picked (GtkEmojiChooser *chooser,
+              const char      *text,
+              gpointer         data)
+{
+  GtkEntry *entry = data;
+  int pos, start, end;
+
+  if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
+    gtk_editable_delete_text (GTK_EDITABLE (entry), start, end);
+
+  pos = MIN (start, end);
+  gtk_editable_insert_text (GTK_EDITABLE (entry), text, -1, &pos);
+  gtk_editable_set_position (GTK_EDITABLE (entry), pos);
+}
+
+static void
+gtk_entry_choose_emoji (GtkEntry *entry)
+{
+  GtkWidget *chooser;
+  GdkRectangle rect;
+
+  chooser = GTK_WIDGET (g_object_get_data (G_OBJECT (entry), "gtk-emoji-chooser"));
+  if (!chooser)
+    {
+      chooser = gtk_emoji_chooser_new ();
+      g_object_set_data_full (G_OBJECT (entry), "gtk-emoji-chooser", chooser, 
(GDestroyNotify)gtk_widget_destroy);
+
+      gtk_popover_set_relative_to (GTK_POPOVER (chooser), GTK_WIDGET (entry));
+      gtk_entry_get_icon_area (entry, GTK_ENTRY_ICON_SECONDARY, &rect);
+      gtk_popover_set_pointing_to (GTK_POPOVER (chooser), &rect);
+      g_signal_connect (chooser, "emoji-picked", G_CALLBACK (emoji_picked), entry);
+    }
+
+  gtk_popover_popup (GTK_POPOVER (chooser));
+}
+
+static void
+pick_emoji (GtkEntry *entry,
+            int       icon,
+            GdkEvent *event,
+            gpointer  data)
+{
+  gtk_entry_choose_emoji (entry);
+}
+
+static void
+set_emoji_input (GtkEntry *entry,
+                 gboolean  value)
+{
+  GtkEntryPrivate *priv = entry->priv;
+
+  if (priv->emoji_input == value)
+    return;
+
+  priv->emoji_input = value;
+
+  if (priv->emoji_input)
+    {
+      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
+                                         GTK_ENTRY_ICON_SECONDARY,
+                                         "face-smile-symbolic");
+
+      gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
+                                    GTK_ENTRY_ICON_SECONDARY,
+                                    TRUE);
+
+      gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
+                                      GTK_ENTRY_ICON_SECONDARY,
+                                      TRUE);
+
+      g_signal_connect (entry, "icon-press", G_CALLBACK (pick_emoji), NULL);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_EMOJI_INPUT]);
+  gtk_widget_queue_resize (GTK_WIDGET (entry));
+}
diff --git a/gtk/org.gtk.Settings.EmojiChooser.gschema.xml b/gtk/org.gtk.Settings.EmojiChooser.gschema.xml
new file mode 100644
index 0000000..cbc4a30
--- /dev/null
+++ b/gtk/org.gtk.Settings.EmojiChooser.gschema.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist>
+
+  <schema id='org.gtk.Settings.EmojiChooser' path='/org/gtk/settings/emoji-chooser/'>
+    <key name='recent-emoji' type='a(ausaau)'>
+      <default>[]</default>
+      <summary>Recent Emoji</summary>
+      <description>
+        An array of Emoji definitions to show in the Emoji chooser. Each Emoji is
+        specified as an array of codepoints, a name, and an optional array of
+        nested Emoji.
+      </description>
+    </key>
+  </schema>
+
+</schemalist>
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index 539d252..1e84d2f 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -4376,3 +4376,52 @@ stackswitcher button.text-button.circular { // FIXME aggregate with buttons
   min-height: 32px;
   padding: 0;
 }
+
+popover.emoji-picker { padding-left: 0; padding-right: 0; }
+
+button.emoji-section {
+  border-color: transparent;
+  border-width: 3px;
+  border-style: none none solid;
+  border-radius: 0;
+
+  margin: 2px 4px 2px 4px;
+  padding: 3px 0 0;
+  min-width: 32px;
+  min-height: 28px;
+
+  /* reset props inherited from the button style */
+  background: none;
+  box-shadow: none;
+  text-shadow: none;
+
+  outline-offset: -5px;
+}
+
+button.emoji-section label { padding: 0; }
+
+button.emoji-section:hover { border-color: $borders_color; }
+
+button.emoji-section:checked { border-color: $selected_bg_color; }
+
+button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
+
+button.emoji-section { color: $borders_color; }
+
+button.emoji-section:hover { color: mix($fg_color, $borders_color, 0.5); }
+
+button.emoji-section:checked { color: $fg_color; }
+
+button.emoji-section:backdrop { color: $backdrop_borders_color; }
+
+button.emoji-section:backdrop:checked { color: $backdrop_fg_color; }
+
+.emoji {
+  font-size: x-large;
+  padding: 6px;
+  border-radius: 6px;
+}
+
+.emoji:hover {
+  background: $selected_bg_color;
+}
diff --git a/gtk/theme/Adwaita/gtk-contained-dark.css b/gtk/theme/Adwaita/gtk-contained-dark.css
index c3514bd..26b7911 100644
--- a/gtk/theme/Adwaita/gtk-contained-dark.css
+++ b/gtk/theme/Adwaita/gtk-contained-dark.css
@@ -1893,6 +1893,32 @@ stackswitcher button.text-button { min-width: 100px; }
 
 stackswitcher button.circular, stackswitcher button.text-button.circular { min-width: 32px; min-height: 
32px; padding: 0; }
 
+popover.emoji-picker { padding-left: 0; padding-right: 0; }
+
+button.emoji-section { border-color: transparent; border-width: 3px; border-style: none none solid; 
border-radius: 0; margin: 2px 4px 2px 4px; padding: 3px 0 0; min-width: 32px; min-height: 28px; /* reset 
props inherited from the button style */ background: none; box-shadow: none; text-shadow: none; 
outline-offset: -5px; }
+
+button.emoji-section label { padding: 0; }
+
+button.emoji-section:hover { border-color: #1b1f20; }
+
+button.emoji-section:checked { border-color: #215d9c; }
+
+button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
+
+button.emoji-section { color: #1b1f20; }
+
+button.emoji-section:hover { color: #1c2021; }
+
+button.emoji-section:checked { color: #eeeeec; }
+
+button.emoji-section:backdrop { color: #202425; }
+
+button.emoji-section:backdrop:checked { color: #919494; }
+
+.emoji { font-size: x-large; padding: 6px; border-radius: 6px; }
+
+.emoji:hover { background: #215d9c; }
+
 /* GTK NAMED COLORS ---------------- use responsibly! */
 /*
 widget text/foreground color */
diff --git a/gtk/theme/Adwaita/gtk-contained.css b/gtk/theme/Adwaita/gtk-contained.css
index f1973c0..4f54987 100644
--- a/gtk/theme/Adwaita/gtk-contained.css
+++ b/gtk/theme/Adwaita/gtk-contained.css
@@ -1913,6 +1913,32 @@ stackswitcher button.text-button { min-width: 100px; }
 
 stackswitcher button.circular, stackswitcher button.text-button.circular { min-width: 32px; min-height: 
32px; padding: 0; }
 
+popover.emoji-picker { padding-left: 0; padding-right: 0; }
+
+button.emoji-section { border-color: transparent; border-width: 3px; border-style: none none solid; 
border-radius: 0; margin: 2px 4px 2px 4px; padding: 3px 0 0; min-width: 32px; min-height: 28px; /* reset 
props inherited from the button style */ background: none; box-shadow: none; text-shadow: none; 
outline-offset: -5px; }
+
+button.emoji-section label { padding: 0; }
+
+button.emoji-section:hover { border-color: #b6b6b3; }
+
+button.emoji-section:checked { border-color: #4a90d9; }
+
+button.emoji-section:backdrop:not(:checked) { border-color: transparent; }
+
+button.emoji-section { color: #b6b6b3; }
+
+button.emoji-section:hover { color: #b5b5b2; }
+
+button.emoji-section:checked { color: #2e3436; }
+
+button.emoji-section:backdrop { color: #c0c0bd; }
+
+button.emoji-section:backdrop:checked { color: #8b8e8f; }
+
+.emoji { font-size: x-large; padding: 6px; border-radius: 6px; }
+
+.emoji:hover { background: #4a90d9; }
+
 /* GTK NAMED COLORS ---------------- use responsibly! */
 /*
 widget text/foreground color */
diff --git a/gtk/ui/gtkemojichooser.ui b/gtk/ui/gtkemojichooser.ui
new file mode 100644
index 0000000..6c5e0a8
--- /dev/null
+++ b/gtk/ui/gtkemojichooser.ui
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+  <template class="GtkEmojiChooser" parent="GtkPopover">
+    <property name="modal">1</property>
+    <style>
+      <class name="emoji-picker"/>
+    </style>
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkSearchEntry" id="search_entry">
+            <signal name="search-changed" handler="search_changed"/>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolled_window">
+            <property name="vexpand">1</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="vscrollbar-policy">automatic</property>
+            <property name="min-content-height">250</property>
+            <style>
+              <class name="view"/>
+            </style>
+            <child>
+              <object class="GtkBox" id="emoji_box">
+                <property name="orientation">vertical</property>
+                <property name="margin">6</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkFlowBox" id="recent.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="people.heading">
+                    <property name="label" translatable="yes">Smileys &amp; People</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="people.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="body.heading">
+                    <property name="label" translatable="yes">Body &amp; Clothing</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="body.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="nature.heading">
+                    <property name="label" translatable="yes">Animals &amp; Nature</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="nature.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="food.heading">
+                    <property name="label" translatable="yes">Food &amp; Drink</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="food.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="travel.heading">
+                    <property name="label" translatable="yes">Travel &amp; Places</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="travel.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="activities.heading">
+                    <property name="label" translatable="yes">Activities</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="activities.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="objects.heading">
+                    <property name="label" translatable="yes">Objects</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="objects.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="symbols.heading">
+                    <property name="label" translatable="yes">Symbols</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="symbols.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="flags.heading">
+                    <property name="label" translatable="yes">Flags</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFlowBox" id="flags.box">
+                    <property name="homogeneous">1</property>
+                    <property name="selection-mode">none</property>
+                    <property name="activate-on-single-click">1</property>
+                    <signal name="child-activated" handler="emoji_activated"/>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <child>
+              <object class="GtkButton" id="recent.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="people.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="body.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="nature.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="food.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="travel.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="activities.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="objects.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="symbols.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="flags.button">
+                <property name="relief">none</property>
+                <style>
+                  <class name="emoji-section"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/tests/Makefile.am b/tests/Makefile.am
index fa667ba..40fafa9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -310,7 +310,6 @@ testentrycompletion_SOURCES =       \
        testentrycompletion.c
 
 testentryicons_SOURCES =       \
-       gtkemojipicker.c        \
        testentryicons.c
 
 testfilechooser_SOURCES =      \
diff --git a/tests/testentryicons.c b/tests/testentryicons.c
index 9548038..d17aa8b 100644
--- a/tests/testentryicons.c
+++ b/tests/testentryicons.c
@@ -3,39 +3,6 @@
 #include "gtkemojipicker.h"
 
 static void
-emoji_activated (GtkEmojiPicker *picker, const char *text, GtkEntry *entry)
-{
-        int pos, start, end;
-
-        if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
-          gtk_editable_delete_text (GTK_EDITABLE (entry), start, end);
-
-        pos = MIN (start, end);
-        gtk_editable_insert_text (GTK_EDITABLE (entry), text, -1, &pos);
-        gtk_editable_set_position (GTK_EDITABLE (entry), pos);
-}
-
-static void
-pick_emoji (GtkEntry *entry, gint icon, GdkEvent *event, gpointer data)
-{
-  GtkWidget *picker;
-  GdkRectangle rect;
-
-  picker = GTK_WIDGET (g_object_get_data (G_OBJECT (entry), "emoji-picker"));
-  if (!picker) {
-        picker = gtk_emoji_picker_new ();
-        g_object_set_data_full (G_OBJECT (entry), "emoji-picker", picker, 
(GDestroyNotify)gtk_widget_destroy);
-
-        gtk_popover_set_relative_to (GTK_POPOVER (picker), GTK_WIDGET (entry));
-        gtk_entry_get_icon_area (entry, GTK_ENTRY_ICON_SECONDARY, &rect);
-        gtk_popover_set_pointing_to (GTK_POPOVER (picker), &rect);
-        g_signal_connect (picker, "activated", G_CALLBACK (emoji_activated), entry);
-  }
-
-  gtk_popover_popup (GTK_POPOVER (picker));
-}
-
-static void
 clear_pressed (GtkEntry *entry, gint icon, GdkEvent *event, gpointer data)
 {
    if (icon == GTK_ENTRY_ICON_SECONDARY)
@@ -315,24 +282,11 @@ main (int argc, char **argv)
   gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
 
   entry = gtk_entry_new ();
+  g_object_set (entry, "emoji-input", TRUE, NULL);
   gtk_widget_set_hexpand (entry, TRUE);
   gtk_grid_attach (GTK_GRID (grid), entry, 1, 6, 1, 1);
   gtk_widget_show (window);
 
-  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
-                                     GTK_ENTRY_ICON_SECONDARY,
-                                     "face-smile-symbolic");
-
-  gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
-                               GTK_ENTRY_ICON_SECONDARY,
-                               TRUE);
-
-  gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
-                                 GTK_ENTRY_ICON_SECONDARY,
-                                 TRUE);
-
-  g_signal_connect (entry, "icon-press", G_CALLBACK (pick_emoji), NULL);
-
   gtk_main();
 
   return 0;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]