/*
 * 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 "evolution-config.h"

/* Copied and adapted a bit from gtk+'s gtkemojichooser.h,
   waiting for it to be made public:
   https://gitlab.gnome.org/GNOME/gtk/issues/86
*/

#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>

#include "e-gtkemojichooser.h"

#define BOX_SPACE 6

typedef struct {
  GtkWidget *box;
  GtkWidget *heading;
  GtkWidget *button;
  const char *first;
  gunichar label;
  gboolean empty;
} EmojiSection;

struct _EGtkEmojiChooser
{
  GtkPopover parent_instance;

  GtkWidget *search_entry;
  GtkWidget *stack;
  GtkWidget *scrolled_window;

  int emoji_max_width;

  EmojiSection recent;
  EmojiSection people;
  EmojiSection body;
  EmojiSection nature;
  EmojiSection food;
  EmojiSection travel;
  EmojiSection activities;
  EmojiSection objects;
  EmojiSection symbols;
  EmojiSection flags;

  GtkGesture *recent_long_press;
  GtkGesture *recent_multi_press;
  GtkGesture *people_long_press;
  GtkGesture *people_multi_press;
  GtkGesture *body_long_press;
  GtkGesture *body_multi_press;

  GVariant *data;
  GtkWidget *box;
  GVariantIter *iter;
  guint populate_idle;

  GSettings *settings;
};

struct _EGtkEmojiChooserClass {
  GtkPopoverClass parent_class;
};

enum {
  EMOJI_PICKED,
  LAST_SIGNAL
};

static int signals[LAST_SIGNAL];

G_DEFINE_TYPE (EGtkEmojiChooser, e_gtk_emoji_chooser, GTK_TYPE_POPOVER)

static void
e_gtk_emoji_chooser_finalize (GObject *object)
{
  EGtkEmojiChooser *chooser = E_GTK_EMOJI_CHOOSER (object);

  if (chooser->populate_idle)
    g_source_remove (chooser->populate_idle);

  g_variant_unref (chooser->data);
  g_object_unref (chooser->settings);

  g_clear_object (&chooser->recent_long_press);
  g_clear_object (&chooser->recent_multi_press);
  g_clear_object (&chooser->people_long_press);
  g_clear_object (&chooser->people_multi_press);
  g_clear_object (&chooser->body_long_press);
  g_clear_object (&chooser->body_multi_press);

  G_OBJECT_CLASS (e_gtk_emoji_chooser_parent_class)->finalize (object);
}

static void
scroll_to_section (GtkButton *button,
                   gpointer   data)
{
  EmojiSection *section = data;
  EGtkEmojiChooser *chooser;
  GtkAdjustment *adj;
  GtkAllocation alloc = { 0, 0, 0, 0 };

  chooser = E_GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), E_GTK_TYPE_EMOJI_CHOOSER));

  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));

  if (section->heading)
    gtk_widget_get_allocation (section->heading, &alloc);

  gtk_adjustment_set_value (adj, alloc.y - BOX_SPACE);
}

static void
add_emoji (GtkWidget    *box,
           gboolean      prepend,
           GVariant     *item,
           gunichar      modifier,
           EGtkEmojiChooser *chooser);

#define MAX_RECENT (7*3)

static void
populate_recent_section (EGtkEmojiChooser *chooser)
{
  GVariant *variant;
  GVariant *item;
  GVariantIter iter;
  gboolean empty = FALSE;

  variant = g_settings_get_value (chooser->settings, "recent-emoji");
  g_variant_iter_init (&iter, variant);
  while ((item = g_variant_iter_next_value (&iter)))
    {
      GVariant *emoji_data;
      gunichar modifier;

      emoji_data = g_variant_get_child_value (item, 0);
      g_variant_get_child (item, 1, "u", &modifier);
      add_emoji (chooser->recent.box, FALSE, emoji_data, modifier, chooser);
      g_variant_unref (emoji_data);
      g_variant_unref (item);
      empty = FALSE;
    }

  if (!empty)
    {
      gtk_widget_show (chooser->recent.box);
      gtk_widget_set_sensitive (chooser->recent.button, TRUE);
    }
  g_variant_unref (variant);
}

static void
add_recent_item (EGtkEmojiChooser *chooser,
                 GVariant        *item,
                 gunichar         modifier)
{
  GList *children, *l;
  int i;
  GVariantBuilder builder;

  g_variant_ref (item);

  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)"));
  g_variant_builder_add (&builder, "(@(auss)u)", item, modifier);

  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");
      gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier"));

      if (modifier == modifier2 && g_variant_equal (item, item2))
        {
          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 (&builder, "(@(auss)u)", item2, modifier2);
    }
  g_list_free (children);

  add_emoji (chooser->recent.box, TRUE, item, modifier, chooser);

  /* Enable recent */
  gtk_widget_show (chooser->recent.box);
  gtk_widget_set_sensitive (chooser->recent.button, TRUE);

  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)
{
  EGtkEmojiChooser *chooser = data;
  char *text;
  GtkWidget *ebox;
  GtkWidget *label;
  GVariant *item;
  gunichar modifier;

  gtk_popover_popdown (GTK_POPOVER (chooser));

  ebox = gtk_bin_get_child (GTK_BIN (child));
  label = gtk_bin_get_child (GTK_BIN (ebox));
  text = g_strdup (gtk_label_get_label (GTK_LABEL (label)));

  item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
  modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier"));
  add_recent_item (chooser, item, modifier);

  g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
  g_free (text);
}

static gboolean
has_variations (GVariant *emoji_data)
{
  GVariant *codes;
  int i;
  gboolean has_variations;

  has_variations = FALSE;
  codes = g_variant_get_child_value (emoji_data, 0);
  for (i = 0; i < g_variant_n_children (codes); i++)
    {
      gunichar code;
      g_variant_get_child (codes, i, "u", &code);
      if (code == 0)
        {
          has_variations = TRUE;
          break;
        }
    }
  g_variant_unref (codes);

  return has_variations;
}

static void
show_variations (EGtkEmojiChooser *chooser,
                 GtkWidget       *child)
{
  GtkWidget *popover;
  GtkWidget *view;
  GtkWidget *box;
  GVariant *emoji_data;
  GtkWidget *parent_popover;
  gunichar modifier;

  if (!child)
    return;

  emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
  if (!emoji_data)
    return;

  if (!has_variations (emoji_data))
    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);

  add_emoji (box, FALSE, emoji_data, 0, chooser);
  for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++)
    add_emoji (box, FALSE, emoji_data, modifier, chooser);

  gtk_widget_show_all (view);
  gtk_popover_popup (GTK_POPOVER (popover));
}

static void
update_hover (GtkWidget *widget,
              GdkEvent  *event,
              gpointer   data)
{
  if (event->type == GDK_ENTER_NOTIFY)
    gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
  else
    gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
}

static void
long_pressed_cb (GtkGesture *gesture,
                 double      x,
                 double      y,
                 gpointer    data)
{
  EGtkEmojiChooser *chooser = data;
  GtkWidget *box;
  GtkWidget *child;

  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));
  show_variations (chooser, child);
}

static void
pressed_cb (GtkGesture *gesture,
            int         n_press,
            double      x,
            double      y,
            gpointer    data)
{
  EGtkEmojiChooser *chooser = data;
  GtkWidget *box;
  GtkWidget *child;

  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));
  show_variations (chooser, child);
}

static gboolean
popup_menu (GtkWidget *widget,
            gpointer   data)
{
  EGtkEmojiChooser *chooser = data;

  show_variations (chooser, widget);
  return TRUE;
}

static void
add_emoji (GtkWidget    *box,
           gboolean      prepend,
           GVariant     *item,
           gunichar      modifier,
           EGtkEmojiChooser *chooser)
{
  GtkWidget *child;
  GtkWidget *ebox;
  GtkWidget *label;
  PangoAttrList *attrs;
  GVariant *codes;
  char text[64];
  char *p = text;
  int i;
  PangoLayout *layout;
  PangoRectangle rect;

  codes = g_variant_get_child_value (item, 0);
  for (i = 0; i < g_variant_n_children (codes); i++)
    {
      gunichar code;

      g_variant_get_child (codes, i, "u", &code);
      if (code == 0)
        code = modifier;
      if (code != 0)
        p += g_unichar_to_utf8 (code, p);
    }
  g_variant_unref (codes);
  p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */
  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);

  layout = gtk_label_get_layout (GTK_LABEL (label));
  pango_layout_get_extents (layout, &rect, NULL);

  /* Check for fallback rendering that generates too wide items */
  if (pango_layout_get_unknown_glyphs_count (layout) > 0 ||
      rect.width >= 1.5 * chooser->emoji_max_width)
    {
      gtk_widget_destroy (label);
      return;
    }

  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 (item),
                          (GDestroyNotify)g_variant_unref);
  if (modifier != 0)
    g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier));

  ebox = gtk_event_box_new ();
  gtk_widget_add_events (ebox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
  g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (update_hover), FALSE);
  g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (update_hover), FALSE);
  gtk_container_add (GTK_CONTAINER (child), ebox);
  gtk_container_add (GTK_CONTAINER (ebox), label);
  gtk_widget_show_all (child);

  if (chooser)
    g_signal_connect (child, "popup-menu", G_CALLBACK (popup_menu), chooser);

  gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1);
}

static gboolean
populate_emoji_chooser (gpointer data)
{
  EGtkEmojiChooser *chooser = data;
  GBytes *bytes = NULL;
  GVariant *item;
  guint64 start, now;

  start = g_get_monotonic_time ();

  if (!chooser->data)
    {
      bytes = g_resources_lookup_data ("/org.gnome.Evolution/emoji.data", 0, NULL);
      chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE));
    }

  if (!chooser->iter)
    {
      chooser->iter = g_variant_iter_new (chooser->data);
      chooser->box = chooser->people.box;
    }
  while ((item = g_variant_iter_next_value (chooser->iter)))
    {
      const char *name;

      g_variant_get_child (item, 1, "&s", &name);

      if (strcmp (name, chooser->body.first) == 0)
        chooser->box = chooser->body.box;
      else if (strcmp (name, chooser->nature.first) == 0)
        chooser->box = chooser->nature.box;
      else if (strcmp (name, chooser->food.first) == 0)
        chooser->box = chooser->food.box;
      else if (strcmp (name, chooser->travel.first) == 0)
        chooser->box = chooser->travel.box;
      else if (strcmp (name, chooser->activities.first) == 0)
        chooser->box = chooser->activities.box;
      else if (strcmp (name, chooser->objects.first) == 0)
        chooser->box = chooser->objects.box;
      else if (strcmp (name, chooser->symbols.first) == 0)
        chooser->box = chooser->symbols.box;
      else if (strcmp (name, chooser->flags.first) == 0)
        chooser->box = chooser->flags.box;

      add_emoji (chooser->box, FALSE, item, 0, chooser);
      g_variant_unref (item);

      now = g_get_monotonic_time ();
      if (now > start + 8000)
        return G_SOURCE_CONTINUE;
    }

  /* We scroll to the top on show, so check the right button for the 1st time */
  gtk_widget_set_state_flags (chooser->recent.button, GTK_STATE_FLAG_CHECKED, FALSE);

  g_variant_iter_free (chooser->iter);
  chooser->iter = NULL;
  chooser->box = NULL;
  chooser->populate_idle = 0;

  return G_SOURCE_REMOVE;
}

static void
adj_value_changed (GtkAdjustment *adj,
                   gpointer       data)
{
  EGtkEmojiChooser *chooser = data;
  double value = gtk_adjustment_get_value (adj);
  EmojiSection const *sections[] = {
    &chooser->recent,
    &chooser->people,
    &chooser->body,
    &chooser->nature,
    &chooser->food,
    &chooser->travel,
    &chooser->activities,
    &chooser->objects,
    &chooser->symbols,
    &chooser->flags,
  };
  EmojiSection const *select_section = sections[0];
  gsize i;

  /* Figure out which section the current scroll position is within */
  for (i = 0; i < G_N_ELEMENTS (sections); ++i)
    {
      EmojiSection const *section = sections[i];
      GtkAllocation alloc;

      if (section->heading)
        gtk_widget_get_allocation (section->heading, &alloc);
      else
        gtk_widget_get_allocation (section->box, &alloc);

      if (value < alloc.y - BOX_SPACE)
        break;

      select_section = section;
    }

  /* Un/Check the section buttons accordingly */
  for (i = 0; i < G_N_ELEMENTS (sections); ++i)
    {
      EmojiSection const *section = sections[i];

      if (section == select_section)
        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 gboolean
filter_func (GtkFlowBoxChild *child,
             gpointer         data)
{
  EmojiSection *section = data;
  EGtkEmojiChooser *chooser;
  GVariant *emoji_data;
  const char *text;
  const char *name;
  gboolean res;

  res = TRUE;

  chooser = E_GTK_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), E_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)
    goto out;

  if (!emoji_data)
    goto out;

  g_variant_get_child (emoji_data, 1, "&s", &name);
  res = g_str_match_string (text, name, TRUE);

out:
  if (res)
    section->empty = FALSE;

  return res;
}

static void
invalidate_section (EmojiSection *section)
{
  section->empty = TRUE;
  gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
}

static void
update_headings (EGtkEmojiChooser *chooser)
{
  gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty);
  gtk_widget_set_visible (chooser->people.box, !chooser->people.empty);
  gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty);
  gtk_widget_set_visible (chooser->body.box, !chooser->body.empty);
  gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty);
  gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty);
  gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty);
  gtk_widget_set_visible (chooser->food.box, !chooser->food.empty);
  gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty);
  gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty);
  gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty);
  gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty);
  gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty);
  gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty);
  gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty);
  gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty);
  gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty);
  gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty);

  if (chooser->recent.empty && chooser->people.empty &&
      chooser->body.empty && chooser->nature.empty &&
      chooser->food.empty && chooser->travel.empty &&
      chooser->activities.empty && chooser->objects.empty &&
      chooser->symbols.empty && chooser->flags.empty)
    gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty");
  else
    gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list");
}

static void
search_changed (GtkEntry *entry,
                gpointer  data)
{
  EGtkEmojiChooser *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);

  update_headings (chooser);
}

static void
setup_section (EGtkEmojiChooser *chooser,
               EmojiSection   *section,
               const char     *first,
               const char     *icon)
{
  GtkAdjustment *adj;
  GtkWidget *image;

  section->first = first;

  image = gtk_bin_get_child (GTK_BIN (section->button));
  gtk_image_set_from_icon_name (GTK_IMAGE (image), icon, GTK_ICON_SIZE_BUTTON);

  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
e_gtk_emoji_chooser_add_group (EGtkEmojiChooser *chooser,
			       GtkBox *emoji_box,
			       GtkBox *button_box,
			       const gchar *label,
			       EmojiSection *section)
{
	section->heading = gtk_label_new (label);
	g_object_set (G_OBJECT (section->heading),
		"xalign", 0.0,
		NULL);
	gtk_box_pack_start (emoji_box, GTK_WIDGET (section->heading), FALSE, FALSE, 0);

	section->box = gtk_flow_box_new ();
	g_object_set (G_OBJECT (section->box),
		"homogeneous", TRUE,
		"selection-mode", GTK_SELECTION_NONE,
		NULL);
	gtk_box_pack_start (emoji_box, GTK_WIDGET (section->box), FALSE, FALSE, 0);
	g_signal_connect (section->box, "child-activated",
		G_CALLBACK (emoji_activated), chooser);

	section->button = gtk_button_new ();
	gtk_container_add (GTK_CONTAINER (section->button), gtk_image_new ());
	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (section->button)), "emoji-section");
	g_object_set (G_OBJECT (section->button),
		"relief", GTK_RELIEF_NONE,
		"tooltip-text", label,
		NULL);
	gtk_box_pack_start (button_box, GTK_WIDGET (section->button), FALSE, FALSE, 0);
}

static void
e_gtk_emoji_chooser_construct_without_template (EGtkEmojiChooser *chooser)
{
	GtkBox *box, *emoji_box, *button_box;
	GtkGrid *grid;
	GtkWidget *widget;
	PangoAttrList *attrs;

	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (chooser)), "emoji-picker");

	box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
	gtk_container_add (GTK_CONTAINER (chooser), GTK_WIDGET (box));

	chooser->search_entry = gtk_search_entry_new ();
	gtk_box_pack_start (box, chooser->search_entry, FALSE, FALSE, 0);

	g_signal_connect (chooser->search_entry, "search-changed", G_CALLBACK (search_changed), chooser);

	chooser->stack = gtk_stack_new ();
	gtk_box_pack_start (box, chooser->stack, FALSE, FALSE, 0);

	gtk_widget_show_all (GTK_WIDGET (box));

	box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
	gtk_widget_set_name (GTK_WIDGET (box), "list");
	gtk_stack_add_named (GTK_STACK (chooser->stack), GTK_WIDGET (box), "list");

	chooser->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
	g_object_set (G_OBJECT (chooser->scrolled_window),
		"vexpand", TRUE,
		"hscrollbar-policy", GTK_POLICY_NEVER,
		"min-content-height", 250,
		NULL);
	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (chooser->scrolled_window)), "view");
	gtk_box_pack_start (box, chooser->scrolled_window, FALSE, FALSE, 0);

	emoji_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
	g_object_set (G_OBJECT (emoji_box),
		"margin", 6,
		"spacing", 6,
		NULL);
	gtk_container_add (GTK_CONTAINER (chooser->scrolled_window), GTK_WIDGET (emoji_box));

	button_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
	gtk_box_pack_start (box, GTK_WIDGET (button_box), FALSE, FALSE, 0);

	chooser->recent.box = gtk_flow_box_new ();
	g_object_set (G_OBJECT (chooser->recent.box),
		"homogeneous", TRUE,
		"selection-mode", GTK_SELECTION_NONE,
		NULL);
	gtk_box_pack_start (emoji_box, GTK_WIDGET (chooser->recent.box), FALSE, FALSE, 0);
	g_signal_connect (chooser->recent.box, "child-activated",
		G_CALLBACK (emoji_activated), chooser);

	chooser->recent.button = gtk_button_new ();
	gtk_container_add (GTK_CONTAINER (chooser->recent.button), gtk_image_new ());
	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (chooser->recent.button)), "emoji-section");
	g_object_set (G_OBJECT (chooser->recent.button),
		"relief", GTK_RELIEF_NONE,
		"tooltip-text", C_("EmojiChooser", "Recent"),
		NULL);
	gtk_box_pack_start (button_box, GTK_WIDGET (chooser->recent.button), FALSE, FALSE, 0);

	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Smileys & People"), &chooser->people);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Body & Clothing"), &chooser->body);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Animals & Nature"), &chooser->nature);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Food & Drink"), &chooser->food);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Travel & Places"), &chooser->travel);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Activities"), &chooser->activities);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Objects"), &chooser->objects);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Symbols"), &chooser->symbols);
	e_gtk_emoji_chooser_add_group (chooser, emoji_box, button_box, C_("EmojiChooser", "Flags"), &chooser->flags);

	grid = GTK_GRID (gtk_grid_new ());
	g_object_set (G_OBJECT (grid),
		"name", "empty",
		"row-spacing", 12,
		"halign", GTK_ALIGN_CENTER,
		"valign", GTK_ALIGN_CENTER,
		NULL);
	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (grid)), "dim-label");
	gtk_stack_add_named (GTK_STACK (chooser->stack), GTK_WIDGET (grid), "empty");

	widget = gtk_image_new ();
	g_object_set (G_OBJECT (widget),
		"icon-name", "edit-find-symbolic",
		"pixel-size", 72,
		NULL);
	gtk_style_context_add_class (gtk_widget_get_style_context (widget), "dim-label");
	gtk_grid_attach (grid, widget, 0, 0, 1, 1);

	attrs = pango_attr_list_new ();
	pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
	pango_attr_list_insert (attrs, pango_attr_scale_new (1.44));

	widget = gtk_label_new (C_("EmojiChooser", "No Results Found"));
	g_object_set (G_OBJECT (widget),
		"attributes", attrs,
		NULL);
	gtk_grid_attach (grid, widget, 0, 1, 1, 1);

	pango_attr_list_unref (attrs);

	widget = gtk_label_new (C_("EmojiChooser", "Try a different search"));
	gtk_style_context_add_class (gtk_widget_get_style_context (widget), "dim-label");
	gtk_grid_attach (grid, widget, 0, 2, 1, 1);

	gtk_widget_show_all (GTK_WIDGET (chooser->stack));
}

static void
e_gtk_emoji_chooser_init (EGtkEmojiChooser *chooser)
{
  GtkAdjustment *adj;

  chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser");

  e_gtk_emoji_chooser_construct_without_template (chooser);

  /* Get a reasonable maximum width for an emoji. We do this to
   * skip overly wide fallback rendering for certain emojis the
   * font does not contain and therefore end up being rendered
   * as multiply glyphs.
   */
  {
    PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), "🙂");
    PangoAttrList *attrs;
    PangoRectangle rect;

    attrs = pango_attr_list_new ();
    pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
    pango_layout_set_attributes (layout, attrs);
    pango_attr_list_unref (attrs);

    pango_layout_get_extents (layout, &rect, NULL);
    chooser->emoji_max_width = rect.width;

    g_object_unref (layout);
  }

  chooser->recent_long_press = gtk_gesture_long_press_new (chooser->recent.box);
  g_signal_connect (chooser->recent_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
  chooser->recent_multi_press = gtk_gesture_multi_press_new (chooser->recent.box);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->recent_multi_press), GDK_BUTTON_SECONDARY);
  g_signal_connect (chooser->recent_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);

  chooser->people_long_press = gtk_gesture_long_press_new (chooser->people.box);
  g_signal_connect (chooser->people_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
  chooser->people_multi_press = gtk_gesture_multi_press_new (chooser->people.box);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->people_multi_press), GDK_BUTTON_SECONDARY);
  g_signal_connect (chooser->people_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);

  chooser->body_long_press = gtk_gesture_long_press_new (chooser->body.box);
  g_signal_connect (chooser->body_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
  chooser->body_multi_press = gtk_gesture_multi_press_new (chooser->body.box);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->body_multi_press), GDK_BUTTON_SECONDARY);
  g_signal_connect (chooser->body_multi_press, "pressed", G_CALLBACK (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, "emoji-recent-symbolic");
  setup_section (chooser, &chooser->people, "grinning face", "emoji-people-symbolic");
  setup_section (chooser, &chooser->body, "selfie", "emoji-body-symbolic");
  setup_section (chooser, &chooser->nature, "monkey face", "emoji-nature-symbolic");
  setup_section (chooser, &chooser->food, "grapes", "emoji-food-symbolic");
  setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", "emoji-travel-symbolic");
  setup_section (chooser, &chooser->activities, "jack-o-lantern", "emoji-activities-symbolic");
  setup_section (chooser, &chooser->objects, "muted speaker", "emoji-objects-symbolic");
  setup_section (chooser, &chooser->symbols, "ATM sign", "emoji-symbols-symbolic");
  setup_section (chooser, &chooser->flags, "chequered flag", "emoji-flags-symbolic");

  populate_recent_section (chooser);

  chooser->populate_idle = g_idle_add (populate_emoji_chooser, chooser);
  g_source_set_name_by_id (chooser->populate_idle, "[gtk] populate_emoji_chooser");
}

static void
e_gtk_emoji_chooser_show (GtkWidget *widget)
{
  EGtkEmojiChooser *chooser = E_GTK_EMOJI_CHOOSER (widget);
  GtkAdjustment *adj;

  GTK_WIDGET_CLASS (e_gtk_emoji_chooser_parent_class)->show (widget);

  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), "");
}

static void
e_gtk_emoji_chooser_class_init (EGtkEmojiChooserClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = e_gtk_emoji_chooser_finalize;
  widget_class->show = e_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);
}

GtkWidget *
e_gtk_emoji_chooser_new (void)
{
  return GTK_WIDGET (g_object_new (E_GTK_TYPE_EMOJI_CHOOSER, NULL));
}
