/* gcc `pkg-config --cflags --libs gtk+-3.0` test.c -g -O0 -Wreturn-type -o test && ./test */

#define GETTEXT_PACKAGE "evolution"

#include <glib.h>
#include <gtk/gtk.h>
#include <stdio.h>

#include <string.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>

#ifndef E_EMOTICON_H
#define E_EMOTICON_H

#include <glib-object.h>

#define E_TYPE_EMOTICON \
	(e_emoticon_get_type ())

G_BEGIN_DECLS

typedef struct _EEmoticon EEmoticon;

struct _EEmoticon {
	gchar *label;
	gchar *icon_name;
	gchar *unicode_character;
	gchar *text_face;
};

GType		e_emoticon_get_type		(void) G_GNUC_CONST;
gboolean	e_emoticon_equal		(EEmoticon *emoticon_a,
						 EEmoticon *emoticon_b);
EEmoticon *	e_emoticon_copy			(EEmoticon *emoticon);
void		e_emoticon_free			(EEmoticon *emoticon);
gchar *		e_emoticon_get_uri		(EEmoticon *emoticon);

G_END_DECLS

#endif /* E_EMOTICON_H */

static EEmoticon *
emoticon_copy (EEmoticon *emoticon)
{
	EEmoticon *copy;

	copy = g_slice_new (EEmoticon);
	copy->label = g_strdup (emoticon->label);
	copy->icon_name = g_strdup (emoticon->icon_name);
	copy->unicode_character = g_strdup (emoticon->unicode_character);
	copy->text_face = g_strdup (emoticon->text_face);

	return copy;
}

static void
emoticon_free (EEmoticon *emoticon)
{
	g_free (emoticon->label);
	g_free (emoticon->icon_name);
	g_free (emoticon->unicode_character);
	g_free (emoticon->text_face);
	g_slice_free (EEmoticon, emoticon);
}

GType
e_emoticon_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0))
		type = g_boxed_type_register_static (
			"EEmoticon",
			(GBoxedCopyFunc) emoticon_copy,
			(GBoxedFreeFunc) emoticon_free);

	return type;
}

gboolean
e_emoticon_equal (EEmoticon *emoticon_a,
                  EEmoticon *emoticon_b)
{
	if (((emoticon_a == NULL) && (emoticon_b != NULL)) ||
	    ((emoticon_a != NULL) && (emoticon_b == NULL)))
		return FALSE;

	if (emoticon_a == emoticon_b)
		return TRUE;

	if (g_strcmp0 (emoticon_a->label, emoticon_b->label) != 0)
		return FALSE;

	if (g_strcmp0 (emoticon_a->icon_name, emoticon_b->icon_name) != 0)
		return FALSE;

	if (g_strcmp0 (emoticon_a->unicode_character, emoticon_b->unicode_character) != 0)
		return FALSE;

	if (g_strcmp0 (emoticon_a->text_face, emoticon_b->text_face) != 0)
		return FALSE;

	return TRUE;
}

EEmoticon *
e_emoticon_copy (EEmoticon *emoticon)
{
	return g_boxed_copy (E_TYPE_EMOTICON, emoticon);
}

void
e_emoticon_free (EEmoticon *emoticon)
{
	g_boxed_free (E_TYPE_EMOTICON, emoticon);
}

gchar *
e_emoticon_get_uri (EEmoticon *emoticon)
{
	GtkIconInfo *icon_info;
	GtkIconTheme *icon_theme;
	const gchar *filename;
	gchar *uri = NULL;

	icon_theme = gtk_icon_theme_get_default ();
	icon_info = gtk_icon_theme_lookup_icon (
		icon_theme, emoticon->icon_name, 16, 0);
	g_return_val_if_fail (icon_info != NULL, NULL);

	filename = gtk_icon_info_get_filename (icon_info);
	if (filename != NULL) {
		uri = g_filename_to_uri (filename, NULL, NULL);
	}
	gtk_icon_info_free (icon_info);
	g_return_val_if_fail (uri != NULL, NULL);

	return uri;
}

/* Standard GObject macros */
#define E_TYPE_EMOTICON_CHOOSER \
	(e_emoticon_chooser_get_type ())
#define E_EMOTICON_CHOOSER(obj) \
	(G_TYPE_CHECK_INSTANCE_CAST \
	((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooser))
#define E_IS_EMOTICON_CHOOSER(obj) \
	(G_TYPE_CHECK_INSTANCE_TYPE \
	((obj), E_TYPE_EMOTICON_CHOOSER))
#define E_EMOTICON_CHOOSER_GET_INTERFACE(obj) \
	(G_TYPE_INSTANCE_GET_INTERFACE \
	((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooserInterface))

G_BEGIN_DECLS

typedef struct _EEmoticonChooser EEmoticonChooser;
typedef struct _EEmoticonChooserInterface EEmoticonChooserInterface;

struct _EEmoticonChooserInterface {
	GTypeInterface parent_interface;

	/* Methods */
	EEmoticon *	(*get_current_emoticon)	(EEmoticonChooser *chooser);
	void		(*set_current_emoticon)	(EEmoticonChooser *chooser,
						 EEmoticon *emoticon);

	/* Signals */
	void		(*item_activated)	(EEmoticonChooser *chooser);
};

GType		e_emoticon_chooser_get_type	(void) G_GNUC_CONST;
EEmoticon *	e_emoticon_chooser_get_current_emoticon
						(EEmoticonChooser *chooser);
void		e_emoticon_chooser_set_current_emoticon
						(EEmoticonChooser *chooser,
						 EEmoticon *emoticon);
void		e_emoticon_chooser_item_activated
						(EEmoticonChooser *chooser);

GList *		e_emoticon_chooser_get_items	(void);
const EEmoticon *
		e_emoticon_chooser_lookup_emoticon
						(const gchar *icon_name);

G_END_DECLS

/* Constant version of EEMoticon. */
typedef struct {
	const gchar *label;
	const gchar *icon_name;
	const gchar *unicode_character;
	const gchar *text_face;
} ConstantEmoticon;

static ConstantEmoticon available_emoticons[] = {
	/* Translators: :-) */
	{ N_("_Smile"),		"face-smile",		"☺",	":-)"	},
	/* Translators: :-( */
	{ N_("S_ad"),		"face-sad",		"☹",	":-("	},
	/* Translators: ;-) */
	{ N_("_Wink"),		"face-wink",		"😉",	";-)"	},
	/* Translators: :-P */
	{ N_("Ton_gue"),	"face-raspberry",	"😛",	":-P"	},
	/* Translators: :-)) */
	{ N_("Laug_h"),		"face-laugh",		"😃",	":-D"	},
	/* Translators: :-| */
	{ N_("_Plain"),		"face-plain",		"😔",	":-|"	},
	/* Translators: :-! */
	{ N_("Smi_rk"),		"face-smirk",		"😏",	":-!"	},
	/* Translators: :"-) */
	{ N_("_Embarrassed"),	"face-embarrassed",	"😯",	":\"-)"	},
	/* Translators: :-D */
	{ N_("_Big Smile"),	"face-smile-big",	"😄",	":-D"	},
	/* Translators: :-/ */
	{ N_("Uncer_tain"),	"face-uncertain",	"😕",	":-/"	},
	/* Translators: :-O */
	{ N_("S_urprise"),	"face-surprise",	"😲",	":-O"	},
	/* Translators: :-S */
	{ N_("W_orried"),	"face-worried",		"😟",	":-S"	},
	/* Translators: :-* */
	{ N_("_Kiss"),		"face-kiss",		"😗",	":-*"	},
	/* Translators: X-( */
	{ N_("A_ngry"),		"face-angry",		"😠",	"X-("	},
	/* Translators: B-) */
	{ N_("_Cool"),		"face-cool",		"😎",	"B-)"	},
	/* Translators: O:-) */
	{ N_("Ange_l"),		"face-angel",		"😇",	"O:-)"	},
	/* Translators: :'( */
	{ N_("Cr_ying"),	"face-crying",		"😢",	":'("	},
	/* Translators: :-Q */
	{ N_("S_ick"),		"face-sick",		"😨",	":-Q"	},
	/* Translators: |-) */
	{ N_("Tire_d"),		"face-tired",		"😫",	"|-)"	},
	/* Translators: >:-) */
	{ N_("De_vilish"),	"face-devilish",	"😈",	">:-)"	},
	/* Translators: :-(|) */
	{ N_("_Monkey"),	"face-monkey",		"🐵",	":-(|)"	}
};

enum {
	ITEM_ACTIVATED,
	LAST_SIGNAL1
};

static guint signals1[LAST_SIGNAL1];

G_DEFINE_INTERFACE (
	EEmoticonChooser,
	e_emoticon_chooser,
	G_TYPE_OBJECT)

static void
e_emoticon_chooser_default_init (EEmoticonChooserInterface *interface)
{
	g_object_interface_install_property (
		interface,
		g_param_spec_boxed (
			"current-emoticon",
			"Current Emoticon",
			"Currently selected emoticon",
			E_TYPE_EMOTICON,
			G_PARAM_READWRITE));

	signals1[ITEM_ACTIVATED] = g_signal_new (
		"item-activated",
		G_TYPE_FROM_INTERFACE (interface),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EEmoticonChooserInterface, item_activated),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);
}

EEmoticon *
e_emoticon_chooser_get_current_emoticon (EEmoticonChooser *chooser)
{
	EEmoticonChooserInterface *interface;

	g_return_val_if_fail (E_IS_EMOTICON_CHOOSER (chooser), NULL);

	interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser);
	g_return_val_if_fail (interface->get_current_emoticon != NULL, NULL);

	return interface->get_current_emoticon (chooser);
}

void
e_emoticon_chooser_set_current_emoticon (EEmoticonChooser *chooser,
                                         EEmoticon *emoticon)
{
	EEmoticonChooserInterface *interface;

	g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser));

	interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser);
	g_return_if_fail (interface->set_current_emoticon != NULL);

	interface->set_current_emoticon (chooser, emoticon);
}

void
e_emoticon_chooser_item_activated (EEmoticonChooser *chooser)
{
	g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser));

	g_signal_emit (chooser, signals1[ITEM_ACTIVATED], 0);
}

GList *
e_emoticon_chooser_get_items (void)
{
	GList *list = NULL;
	gint ii;

	for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++)
		list = g_list_prepend (list, &available_emoticons[ii]);

	return g_list_reverse (list);
}

const EEmoticon *
e_emoticon_chooser_lookup_emoticon (const gchar *icon_name)
{
	gint ii;

	g_return_val_if_fail (icon_name && *icon_name, NULL);

	for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++) {
		if (strcmp (available_emoticons[ii].icon_name, icon_name) == 0) {
			return (const EEmoticon *) &available_emoticons[ii];
		}
	}

	return NULL;
}

/*
 * e-emoticon-tool-button.h
 *
 * Copyright (C) 2008 Novell, Inc.
 * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifndef E_EMOTICON_TOOL_BUTTON_H
#define E_EMOTICON_TOOL_BUTTON_H

#include <gtk/gtk.h>

/* Standard GObject macros */
#define E_TYPE_EMOTICON_TOOL_BUTTON \
	(e_emoticon_tool_button_get_type ())
#define E_EMOTICON_TOOL_BUTTON(obj) \
	(G_TYPE_CHECK_INSTANCE_CAST \
	((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButton))
#define E_EMOTICON_TOOL_BUTTON_CLASS(cls) \
	(G_TYPE_CHECK_CLASS_CAST \
	((cls), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass))
#define E_IS_EMOTICON_TOOL_BUTTON(obj) \
	(G_TYPE_CHECK_INSTANCE_TYPE \
	((obj), E_TYPE_EMOTICON_TOOL_BUTTON))
#define E_IS_EMOTICON_TOOL_BUTTON_CLASS(cls) \
	(G_TYPE_CHECK_CLASS_TYPE \
	((cls), E_TYPE_EMOTICON_TOOL_BUTTON))
#define E_EMOTICON_TOOL_BUTTON_GET_CLASS(obj) \
	(G_TYPE_INSTANCE_GET_CLASS \
	((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass))

G_BEGIN_DECLS

typedef struct _EEmoticonToolButton EEmoticonToolButton;
typedef struct _EEmoticonToolButtonClass EEmoticonToolButtonClass;
typedef struct _EEmoticonToolButtonPrivate EEmoticonToolButtonPrivate;

struct _EEmoticonToolButton {
	GtkToggleToolButton parent;
	EEmoticonToolButtonPrivate *priv;
};

struct _EEmoticonToolButtonClass {
	GtkToggleToolButtonClass parent_class;

	void		(*popup)		(EEmoticonToolButton *button);
	void		(*popdown)		(EEmoticonToolButton *button);
};

GType		e_emoticon_tool_button_get_type	(void) G_GNUC_CONST;
GtkToolItem *	e_emoticon_tool_button_new	(void);
void		e_emoticon_tool_button_popup	(EEmoticonToolButton *button);
void		e_emoticon_tool_button_popdown	(EEmoticonToolButton *button);

G_END_DECLS

#endif /* E_EMOTICON_TOOL_BUTTON_H */

#define E_EMOTICON_TOOL_BUTTON_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonPrivate))

/* XXX Should calculate this dynamically. */
#define NUM_ROWS	7
#define NUM_COLS	3

enum {
	PROP_0,
	PROP_CURRENT_EMOTICON,
	PROP_POPUP_SHOWN
};

enum {
	POPUP,
	POPDOWN,
	LAST_SIGNAL2
};

struct _EEmoticonToolButtonPrivate {
	GtkWidget *active_button;  /* not referenced */
	GtkWidget *table;
	GtkWidget *window;

	guint popup_shown	: 1;
	guint popup_in_progress	: 1;
	GdkDevice *grab_keyboard;
	GdkDevice *grab_mouse;
};

static guint signals2[LAST_SIGNAL2];

/* Forward Declarations */
static void	e_emoticon_tool_button_interface_init
					(EEmoticonChooserInterface *interface);

G_DEFINE_TYPE_WITH_CODE (
	EEmoticonToolButton,
	e_emoticon_tool_button,
	GTK_TYPE_TOGGLE_TOOL_BUTTON,
	G_IMPLEMENT_INTERFACE (
		E_TYPE_EMOTICON_CHOOSER,
		e_emoticon_tool_button_interface_init))

/* XXX Copied from _gtk_toolbar_elide_underscores() */
static gchar *
emoticon_tool_button_elide_underscores (const gchar *original)
{
	gchar *q, *result;
	const gchar *p, *end;
	gsize len;
	gboolean last_underscore;

	if (!original)
		return NULL;

	len = strlen (original);
	q = result = g_malloc (len + 1);
	last_underscore = FALSE;

	end = original + len;
	for (p = original; p < end; p++) {
		if (!last_underscore && *p == '_')
			last_underscore = TRUE;
		else {
			last_underscore = FALSE;
			if (original + 2 <= p && p + 1 <= end &&
				p[-2] == '(' && p[-1] == '_' &&
				p[0] != '_' && p[1] == ')') {
				q--;
				*q = '\0';
				p++;
			} else
				*q++ = *p;
		}
	}

	if (last_underscore)
		*q++ = '_';

	*q = '\0';

	return result;
}

static void
emoticon_tool_button_reposition_window (EEmoticonToolButton *button)
{
	GdkScreen *screen;
	GdkWindow *window;
	GdkRectangle monitor;
	GtkAllocation allocation;
	gint monitor_num;
	gint x, y, width, height;

	screen = gtk_widget_get_screen (GTK_WIDGET (button));
	window = gtk_widget_get_window (GTK_WIDGET (button));
	monitor_num = gdk_screen_get_monitor_at_window (screen, window);
	gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);

	gdk_window_get_origin (window, &x, &y);

	if (!gtk_widget_get_has_window (GTK_WIDGET (button))) {
		gtk_widget_get_allocation (GTK_WIDGET (button), &allocation);
		x += allocation.x;
		y += allocation.y;
	}

	gtk_widget_get_allocation (button->priv->window, &allocation);
	width = allocation.width;
	height = allocation.height;

	x = CLAMP (x, monitor.x, monitor.x + monitor.width - width);
	y = CLAMP (y, monitor.y, monitor.y + monitor.height - height);

	gtk_window_move (GTK_WINDOW (button->priv->window), x, y);
}

static void
emoticon_tool_button_emoticon_clicked_cb (EEmoticonToolButton *button,
                                          GtkWidget *emoticon_button)
{
	button->priv->active_button = emoticon_button;
	e_emoticon_tool_button_popdown (button);
}

static gboolean
emoticon_tool_button_emoticon_release_event_cb (EEmoticonToolButton *button,
                                                GdkEventButton *event,
                                                GtkButton *emoticon_button)
{
	GtkStateFlags state;

	state = gtk_widget_get_state_flags (GTK_WIDGET (button));

	if (state != GTK_STATE_FLAG_NORMAL)
		gtk_button_clicked (emoticon_button);

	return FALSE;
}

static gboolean
emoticon_tool_button_button_release_event_cb (EEmoticonToolButton *button,
                                              GdkEventButton *event)
{
	GtkToggleToolButton *tool_button;
	GtkWidget *event_widget;
	gboolean popup_in_progress;

	tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
	event_widget = gtk_get_event_widget ((GdkEvent *) event);

	popup_in_progress = button->priv->popup_in_progress;
	button->priv->popup_in_progress = FALSE;

	if (event_widget != GTK_WIDGET (button))
		goto popdown;

	if (popup_in_progress)
		return FALSE;

	if (gtk_toggle_tool_button_get_active (tool_button))
		goto popdown;

	return FALSE;

popdown:
	e_emoticon_tool_button_popdown (button);

	return TRUE;
}

static void
emoticon_tool_button_child_show_cb (EEmoticonToolButton *button)
{
	button->priv->popup_shown = TRUE;
	g_object_notify (G_OBJECT (button), "popup-shown");
}

static void
emoticon_tool_button_child_hide_cb (EEmoticonToolButton *button)
{
	button->priv->popup_shown = FALSE;
	g_object_notify (G_OBJECT (button), "popup-shown");
}

static gboolean
emoticon_tool_button_child_key_press_event_cb (EEmoticonToolButton *button,
                                               GdkEventKey *event)
{
	GtkWidget *window = button->priv->window;

	if (!gtk_bindings_activate_event (G_OBJECT (window), event))
		gtk_bindings_activate_event (G_OBJECT (button), event);

	return TRUE;
}

static void
emoticon_tool_button_set_property (GObject *object,
                                   guint property_id,
                                   const GValue *value,
                                   GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_CURRENT_EMOTICON:
			e_emoticon_chooser_set_current_emoticon (
				E_EMOTICON_CHOOSER (object),
				g_value_get_boxed (value));
			return;

		case PROP_POPUP_SHOWN:
			if (g_value_get_boolean (value))
				e_emoticon_tool_button_popup (
					E_EMOTICON_TOOL_BUTTON (object));
			else
				e_emoticon_tool_button_popdown (
					E_EMOTICON_TOOL_BUTTON (object));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
emoticon_tool_button_get_property (GObject *object,
                                   guint property_id,
                                   GValue *value,
                                   GParamSpec *pspec)
{
	EEmoticonToolButtonPrivate *priv;

	priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object);

	switch (property_id) {
		case PROP_CURRENT_EMOTICON:
			g_value_set_boxed (
				value,
				e_emoticon_chooser_get_current_emoticon (
				E_EMOTICON_CHOOSER (object)));
			return;

		case PROP_POPUP_SHOWN:
			g_value_set_boolean (value, priv->popup_shown);
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
emoticon_tool_button_dispose (GObject *object)
{
	EEmoticonToolButtonPrivate *priv;

	priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object);

	if (priv->window != NULL) {
		gtk_widget_destroy (priv->window);
		priv->window = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_emoticon_tool_button_parent_class)->dispose (object);
}

static gboolean
emoticon_tool_button_press_event (GtkWidget *widget,
                                  GdkEventButton *event)
{
	EEmoticonToolButton *button;
	GtkToggleToolButton *toggle_button;
	GtkWidget *event_widget;

	button = E_EMOTICON_TOOL_BUTTON (widget);

	event_widget = gtk_get_event_widget ((GdkEvent *) event);

	if (event_widget == button->priv->window)
		return TRUE;

	if (event_widget != widget)
		return FALSE;

	toggle_button = GTK_TOGGLE_TOOL_BUTTON (widget);
	if (gtk_toggle_tool_button_get_active (toggle_button))
		return FALSE;

	e_emoticon_tool_button_popup (button);

	button->priv->popup_in_progress = TRUE;

	return TRUE;
}

static void
emoticon_tool_button_toggled (GtkToggleToolButton *button)
{
	if (gtk_toggle_tool_button_get_active (button))
		e_emoticon_tool_button_popup (
			E_EMOTICON_TOOL_BUTTON (button));
	else
		e_emoticon_tool_button_popdown (
			E_EMOTICON_TOOL_BUTTON (button));
}

static void
emoticon_tool_button_popup (EEmoticonToolButton *button)
{
	GtkToggleToolButton *tool_button;
	GdkWindow *window;
	gboolean grab_status;
	GdkDevice *device, *mouse, *keyboard;
	guint32 activate_time;

	device = gtk_get_current_event_device ();
	g_return_if_fail (device != NULL);

	if (!gtk_widget_get_realized (GTK_WIDGET (button)))
		return;

	if (button->priv->popup_shown)
		return;

	activate_time = gtk_get_current_event_time ();
	if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) {
		keyboard = device;
		mouse = gdk_device_get_associated_device (device);
	} else {
		keyboard = gdk_device_get_associated_device (device);
		mouse = device;
	}

	/* Position the window over the button. */
	emoticon_tool_button_reposition_window (button);

	/* Show the pop-up. */
	gtk_widget_show (button->priv->window);
	gtk_widget_grab_focus (button->priv->window);

	/* Activate the tool button. */
	tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
	gtk_toggle_tool_button_set_active (tool_button, TRUE);

	/* Try to grab the pointer and keyboard. */
	window = gtk_widget_get_window (button->priv->window);
	grab_status = !keyboard ||
		gdk_device_grab (
			keyboard, window,
			GDK_OWNERSHIP_WINDOW, TRUE,
			GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
			NULL, activate_time) == GDK_GRAB_SUCCESS;
	if (grab_status) {
		grab_status = !mouse ||
			gdk_device_grab (mouse, window,
				GDK_OWNERSHIP_WINDOW, TRUE,
				GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
				NULL, activate_time) == GDK_GRAB_SUCCESS;
		if (!grab_status && keyboard)
			gdk_device_ungrab (keyboard, activate_time);
	}

	if (grab_status) {
		gtk_device_grab_add (button->priv->window, mouse, TRUE);
		button->priv->grab_keyboard = keyboard;
		button->priv->grab_mouse = mouse;
	} else {
		gtk_widget_hide (button->priv->window);
	}
}

static void
emoticon_tool_button_popdown (EEmoticonToolButton *button)
{
	GtkToggleToolButton *tool_button;

	if (!gtk_widget_get_realized (GTK_WIDGET (button)))
		return;

	if (!button->priv->popup_shown)
		return;

	/* Hide the pop-up. */
	gtk_device_grab_remove (button->priv->window, button->priv->grab_mouse);
	gtk_widget_hide (button->priv->window);

	/* Deactivate the tool button. */
	tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
	gtk_toggle_tool_button_set_active (tool_button, FALSE);

	if (button->priv->grab_keyboard)
		gdk_device_ungrab (button->priv->grab_keyboard, GDK_CURRENT_TIME);
	if (button->priv->grab_mouse)
		gdk_device_ungrab (button->priv->grab_mouse, GDK_CURRENT_TIME);

	button->priv->grab_keyboard = NULL;
	button->priv->grab_mouse = NULL;
}

static EEmoticon *
emoticon_tool_button_get_current_emoticon (EEmoticonChooser *chooser)
{
	EEmoticonToolButtonPrivate *priv;

	priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser);

	if (priv->active_button == NULL)
		return NULL;

	return g_object_get_data (G_OBJECT (priv->active_button), "emoticon");
}

static void
emoticon_tool_button_set_current_emoticon (EEmoticonChooser *chooser,
                                           EEmoticon *emoticon)
{
	EEmoticonToolButtonPrivate *priv;
	GList *list, *iter;

	priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser);

	list = gtk_container_get_children (GTK_CONTAINER (priv->table));

	for (iter = list; iter != NULL; iter = iter->next) {
		GtkWidget *item = iter->data;
		EEmoticon *candidate;

		candidate = g_object_get_data (G_OBJECT (item), "emoticon");
		if (candidate == NULL)
			continue;

		if (e_emoticon_equal (emoticon, candidate)) {
			gtk_button_clicked (GTK_BUTTON (item));
			break;
		}
	}

	g_list_free (list);
}

static void
e_emoticon_tool_button_class_init (EEmoticonToolButtonClass *class)
{
	GObjectClass *object_class;
	GtkWidgetClass *widget_class;
	GtkToggleToolButtonClass *toggle_tool_button_class;

	g_type_class_add_private (class, sizeof (EEmoticonToolButtonPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = emoticon_tool_button_set_property;
	object_class->get_property = emoticon_tool_button_get_property;
	object_class->dispose = emoticon_tool_button_dispose;

	widget_class = GTK_WIDGET_CLASS (class);
	widget_class->button_press_event = emoticon_tool_button_press_event;

	toggle_tool_button_class = GTK_TOGGLE_TOOL_BUTTON_CLASS (class);
	toggle_tool_button_class->toggled = emoticon_tool_button_toggled;

	class->popup = emoticon_tool_button_popup;
	class->popdown = emoticon_tool_button_popdown;

	g_object_class_override_property (
		object_class, PROP_CURRENT_EMOTICON, "current-emoticon");

	g_object_class_install_property (
		object_class,
		PROP_POPUP_SHOWN,
		g_param_spec_boolean (
			"popup-shown",
			"Popup Shown",
			"Whether the button's dropdown is shown",
			FALSE,
			G_PARAM_READWRITE));

	signals2[POPUP] = g_signal_new (
		"popup",
		G_OBJECT_CLASS_TYPE (class),
		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (EEmoticonToolButtonClass, popup),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals2[POPDOWN] = g_signal_new (
		"popdown",
		G_OBJECT_CLASS_TYPE (class),
		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		G_STRUCT_OFFSET (EEmoticonToolButtonClass, popdown),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	gtk_binding_entry_add_signal (
		gtk_binding_set_by_class (class),
		GDK_KEY_Down, GDK_MOD1_MASK, "popup", 0);
	gtk_binding_entry_add_signal (
		gtk_binding_set_by_class (class),
		GDK_KEY_KP_Down, GDK_MOD1_MASK, "popup", 0);

	gtk_binding_entry_add_signal (
		gtk_binding_set_by_class (class),
		GDK_KEY_Up, GDK_MOD1_MASK, "popdown", 0);
	gtk_binding_entry_add_signal (
		gtk_binding_set_by_class (class),
		GDK_KEY_KP_Up, GDK_MOD1_MASK, "popdown", 0);
	gtk_binding_entry_add_signal (
		gtk_binding_set_by_class (class),
		GDK_KEY_Escape, 0, "popdown", 0);
}

static void
e_emoticon_tool_button_interface_init (EEmoticonChooserInterface *interface)
{
	interface->get_current_emoticon =
		emoticon_tool_button_get_current_emoticon;
	interface->set_current_emoticon =
		emoticon_tool_button_set_current_emoticon;
}

static void
e_emoticon_tool_button_init (EEmoticonToolButton *button)
{
	EEmoticonChooser *chooser;
	GtkWidget *toplevel;
	GtkWidget *container;
	GtkWidget *widget;
	GtkWidget *window;
	GList *list, *iter;
	gint ii;

	button->priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (button);

	/* Build the pop-up window. */

	window = gtk_window_new (GTK_WINDOW_POPUP);
	gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
	gtk_window_set_type_hint (
		GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_COMBO);
	button->priv->window = g_object_ref_sink (window);

	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
	if (GTK_IS_WINDOW (toplevel)) {
		gtk_window_group_add_window (
			gtk_window_get_group (GTK_WINDOW (toplevel)),
			GTK_WINDOW (window));
		gtk_window_set_transient_for (
			GTK_WINDOW (window), GTK_WINDOW (toplevel));
	}

	g_signal_connect_swapped (
		window, "show",
		G_CALLBACK (emoticon_tool_button_child_show_cb), button);
	g_signal_connect_swapped (
		window, "hide",
		G_CALLBACK (emoticon_tool_button_child_hide_cb), button);
	g_signal_connect_swapped (
		window, "button-release-event",
		G_CALLBACK (emoticon_tool_button_button_release_event_cb),
		button);
	g_signal_connect_swapped (
		window, "key-press-event",
		G_CALLBACK (emoticon_tool_button_child_key_press_event_cb),
		button);

	/* Build the pop-up window contents. */

	widget = gtk_frame_new (NULL);
	gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_OUT);
	gtk_container_add (GTK_CONTAINER (window), widget);
	gtk_widget_show (widget);

	container = widget;

	widget = gtk_table_new (NUM_ROWS, NUM_COLS, TRUE);
	gtk_table_set_row_spacings (GTK_TABLE (widget), 0);
	gtk_table_set_col_spacings (GTK_TABLE (widget), 0);
	gtk_container_add (GTK_CONTAINER (container), widget);
	button->priv->table = g_object_ref (widget);
	gtk_widget_show (widget);

	container = widget;

	chooser = E_EMOTICON_CHOOSER (button);
	list = e_emoticon_chooser_get_items ();
	g_return_if_fail (g_list_length (list) <= NUM_ROWS * NUM_COLS);

	for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) {
		EEmoticon *emoticon = iter->data;
		guint left = ii % NUM_COLS;
		guint top = ii / NUM_COLS;
		gchar *tooltip;

		tooltip = emoticon_tool_button_elide_underscores (
			gettext (emoticon->label));

		widget = gtk_button_new ();
		gtk_button_set_image (
			GTK_BUTTON (widget),
			gtk_image_new_from_icon_name (
			emoticon->icon_name, GTK_ICON_SIZE_BUTTON));
		gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
		gtk_widget_set_tooltip_text (widget, tooltip);
		gtk_widget_show (widget);

		g_object_set_data_full (
			G_OBJECT (widget), "emoticon",
			e_emoticon_copy (emoticon),
			(GDestroyNotify) e_emoticon_free);

		g_signal_connect_swapped (
			widget, "clicked",
			G_CALLBACK (emoticon_tool_button_emoticon_clicked_cb),
			button);

		g_signal_connect_swapped (
			widget, "clicked",
			G_CALLBACK (e_emoticon_chooser_item_activated),
			chooser);

		g_signal_connect_swapped (
			widget, "button-release-event",
			G_CALLBACK (emoticon_tool_button_emoticon_release_event_cb),
			button);

		gtk_table_attach (
			GTK_TABLE (container), widget,
			left, left + 1, top, top + 1, 0, 0, 0, 0);

		g_free (tooltip);
	}

	g_list_free (list);
}

GtkToolItem *
e_emoticon_tool_button_new (void)
{
	return g_object_new (E_TYPE_EMOTICON_TOOL_BUTTON, NULL);
}

void
e_emoticon_tool_button_popup (EEmoticonToolButton *button)
{
	g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button));

	g_signal_emit (button, signals2[POPUP], 0);
}

void
e_emoticon_tool_button_popdown (EEmoticonToolButton *button)
{
	g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button));

	g_signal_emit (button, signals2[POPDOWN], 0);
}

int main (int argc, char *argv[])
{
	GtkWidget *dialog;
	GtkToolItem *button;
	EEmoticonToolButtonPrivate *priv;

	gtk_init (&argc, &argv);

	dialog = gtk_dialog_new_with_buttons ("Test", NULL, 0, "_Close", GTK_RESPONSE_CLOSE, NULL);
	button = e_emoticon_tool_button_new ();
	gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), "Click me");
	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), GTK_WIDGET (button));
	gtk_widget_show (GTK_WIDGET (button));

	priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (button);
        gtk_window_set_transient_for (GTK_WINDOW (priv->window), GTK_WINDOW (dialog));

	gtk_dialog_run (GTK_DIALOG (dialog));

	gtk_widget_destroy (dialog);

	return 0;
}
