void
empathy_smiley_manager_add (EmpathySmileyManager *manager,
			    const gchar          *icon_name,
			    const gchar          *first_str,
			    ...)
{
	GdkPixbuf *pixbuf;
	va_list    var_args;

	g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));
	g_return_if_fail (!TPAW_STR_EMPTY (icon_name));
	g_return_if_fail (!TPAW_STR_EMPTY (first_str));

	pixbuf = tpaw_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
	if (pixbuf) {
		gchar *path;

		va_start (var_args, first_str);
		path = tpaw_filename_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
		smiley_manager_add_valist (manager, pixbuf, path, first_str, var_args);
		va_end (var_args);
		g_object_unref (pixbuf);
		g_free (path);
	}
}
GSList *
empathy_smiley_manager_parse (EmpathySmileyManager *manager,
			      const gchar          *text)
{
	EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
	EmpathySmiley            *smiley;
	SmileyManagerTree        *cur_tree = priv->tree;
	const gchar              *t;
	const gchar              *cur_str = text;
	GSList                   *smileys = NULL;

	g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL);
	g_return_val_if_fail (text != NULL, NULL);

	for (t = text; *t; t = g_utf8_next_char (t)) {
		SmileyManagerTree *child;
		gunichar           c;
		
		c = g_utf8_get_char (t);
		child = smiley_manager_tree_find_child (cur_tree, c);

		if (cur_tree == priv->tree) {
			if (child) {
				if (t > cur_str) {
					smiley = smiley_new (NULL, g_strndup (cur_str, t - cur_str));
					smileys = g_slist_prepend (smileys, smiley);
				}
				cur_str = t;
				cur_tree = child;
			}

			continue;
		}

		if (child) {
			cur_tree = child;
			continue;
		}

		smiley = smiley_new (cur_tree->pixbuf, g_strndup (cur_str, t - cur_str));
		smileys = g_slist_prepend (smileys, smiley);
		if (cur_tree->pixbuf) {
			cur_str = t;
			cur_tree = smiley_manager_tree_find_child (priv->tree, c);

			if (!cur_tree) {
				cur_tree = priv->tree;
			}
		} else {
			cur_str = t;
			cur_tree = priv->tree;
		}
	}

	smiley = smiley_new (cur_tree->pixbuf, g_strndup (cur_str, t - cur_str));
	smileys = g_slist_prepend (smileys, smiley);

	return g_slist_reverse (smileys);
}
GtkWidget *
empathy_smiley_menu_new (EmpathySmileyManager *manager,
			 EmpathySmileyMenuFunc func,
			 gpointer              user_data)
{
	EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
	GSList                   *l;
	GtkWidget                *menu;
	gint                      x = 0;
	gint                      y = 0;

	g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL);
	g_return_val_if_fail (func != NULL, NULL);

	menu = gtk_menu_new ();

	for (l = priv->smileys; l; l = l->next) {
		EmpathySmiley *smiley;
		GtkWidget     *item;
		GtkWidget     *image;
		ActivateData  *data;

		smiley = l->data;
		image = gtk_image_new_from_pixbuf (smiley->pixbuf);

		item = gtk_image_menu_item_new_with_label ("");
		gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
		gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);

		gtk_menu_attach (GTK_MENU (menu), item,
				 x, x + 1, y, y + 1);

		gtk_widget_set_tooltip_text (item, smiley->str);

		data = g_slice_new (ActivateData);
		data->manager = g_object_ref (manager);
		data->smiley = smiley;
		data->func = func;
		data->user_data = user_data;

		g_signal_connect_data (item, "activate",
				       G_CALLBACK (smiley_menu_activate_cb),
				       data,
				       smiley_menu_data_free,
				       0);

		if (x > 3) {
			y++;
			x = 0;
		} else {
			x++;
		}
	}

	gtk_widget_show_all (menu);

	return menu;
}
void
empathy_smiley_manager_load (EmpathySmileyManager *manager)
{
	g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));

	/* From fd.o icon-naming spec */

	/* U+1F47C BABY ANGEL */
	empathy_smiley_manager_add (manager, "face-angel",      "👼",    "O:-)",  "O:)",  NULL);
	/* U+1F620 ANGRY FACE */
	empathy_smiley_manager_add (manager, "face-angry",      "😠",    "X-(",   ":@",   NULL);
	/* U+1F60E SMILING FACE WITH SUNGLASSES */
	empathy_smiley_manager_add (manager, "face-cool",       "😎",    "B-)",   "B-|",  NULL);
	/* U+1F62D LOUDLY CRYING FACE */
	empathy_smiley_manager_add (manager, "face-crying",     "😭",    ":'(",           NULL);
	/* U+1F608 SMILING FACE WITH HORNS  */
	empathy_smiley_manager_add (manager, "face-devilish",   "😈",    ">:-)",  ">:)",  NULL);
	/* U+1F633 FLUSHED FACE */
	empathy_smiley_manager_add (manager, "face-embarrassed","😳",    ":-[",   ":[",   ":-$", ":$", NULL);
	/* no suitable character in unicode */
	empathy_smiley_manager_add (manager, "face-glasses",    "8-)",   NULL);
	/* U+1F618 FACE THROWING A KISS */
	empathy_smiley_manager_add (manager, "face-kiss",       "😘",    ":-*",   ":*",   NULL);
	/* U+1F604 SMILING FACE WITH OPEN MOUTH AND SMILING EYES" */
	empathy_smiley_manager_add (manager, "face-laugh",      "😄",    ":-))",  ":))",  NULL);
	/* U+1F435 MONKEY */
	empathy_smiley_manager_add (manager, "face-monkey",     "🐵",    ":-(|)", ":(|)", NULL);
	/* U+1F610 NEUTRAL FACE */
	empathy_smiley_manager_add (manager, "face-plain",      "😐",    ":-|",   ":|",   NULL);
	/* U+1F61B FACE WITH STUCK-OUT TONGUE */
	empathy_smiley_manager_add (manager, "face-raspberry",  "😛",    ":-P",   ":P",	 ":-p", ":p", NULL);
	/* U+1F626 FROWING FACE WITH OPEN MOUTH */
	empathy_smiley_manager_add (manager, "face-sad",        "😦",    ":-(",   ":(",   NULL);
	/* U+1F635 DIZZY FACE */
	empathy_smiley_manager_add (manager, "face-sick",       "😵",    ":-&",   ":&",   NULL);
	/* U+1F603 SMILING FACE WITH OPEN MOUTH */
	empathy_smiley_manager_add (manager, "face-smile",      "😃",    ":-)",   ":)",   ":]",  "=)", NULL);
	/* U+1F601 GRINNING FACE WITH SMILING EYES */
	empathy_smiley_manager_add (manager, "face-smile-big",  "😁",    ":-D",   ":D",   ":-d", ":d", NULL);
	/* U+1F60F SMIRKING FACE */
	empathy_smiley_manager_add (manager, "face-smirk",      "😏",    ":-!",   ":!",   NULL);
	/* U+1F632 ASTONISHED FACE */
	empathy_smiley_manager_add (manager, "face-surprise",   "😲",    ":-O",   ":O",   ":-o", ":o", NULL);
	/* U+1F62A SLEEPY FACE */
	empathy_smiley_manager_add (manager, "face-tired",      "😪",    "|-)",   "|)",   NULL);
	/* U+1F615 CONFUSED FACE */
	empathy_smiley_manager_add (manager, "face-uncertain",  "😕",    ":-/",   ":/",   ":-\\", ":\\", NULL);
	/* U+1F609 WINKING FACE */
	empathy_smiley_manager_add (manager, "face-wink",       "😉",    ";-)",   ";)",   NULL);
	/* U+1F61F WORRIED FACE */
	empathy_smiley_manager_add (manager, "face-worried",    "😟",    ":-S",   ":S",   ":-s", ":s", NULL);
	/* U+2764 HEAVY BLACK HEART */
	empathy_smiley_manager_add (manager, "emblem-favorite", "❤",     "<3", NULL);
}
void
empathy_smiley_manager_add_from_pixbuf (EmpathySmileyManager *manager,
					GdkPixbuf            *smiley,
					const gchar          *first_str,
					...)
{
	va_list var_args;

	g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));
	g_return_if_fail (GDK_IS_PIXBUF (smiley));
	g_return_if_fail (!G_STR_EMPTY (first_str));

	va_start (var_args, first_str);
	smiley_manager_add_valist (manager, smiley, first_str, var_args);
	va_end (var_args);
}
void
empathy_smiley_manager_add (EmpathySmileyManager *manager,
			    const gchar          *icon_name,
			    const gchar          *first_str,
			    ...)
{
	GdkPixbuf *smiley;
	va_list    var_args;

	g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));
	g_return_if_fail (!G_STR_EMPTY (icon_name));
	g_return_if_fail (!G_STR_EMPTY (first_str));

	smiley = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
	if (smiley) {
		va_start (var_args, first_str);
		smiley_manager_add_valist (manager, smiley, first_str, var_args);
		va_end (var_args);
		g_object_unref (smiley);
	}
}
void
empathy_smiley_manager_load (EmpathySmileyManager *manager)
{
	g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));

	/* From fd.o icon-naming spec */
	empathy_smiley_manager_add (manager, "face-angel",      "O:-)",  "O:)",  NULL);
        empathy_smiley_manager_add (manager, "face-cool",       "B-)",   "B)",   NULL);
	empathy_smiley_manager_add (manager, "face-crying",     ":'(", NULL);
	empathy_smiley_manager_add (manager, "face-devilish",   ">:-)",  ">:)",  NULL);
        empathy_smiley_manager_add (manager, "face-embarrassed",":-[",   ":[",   ":-$", ":$", NULL);
	empathy_smiley_manager_add (manager, "face-kiss",       ":-*",   ":*",   NULL);
	empathy_smiley_manager_add (manager, "face-monkey",     ":-(|)", ":(|)", NULL);
	empathy_smiley_manager_add (manager, "face-plain",      ":-|",   ":|",   NULL);
        empathy_smiley_manager_add (manager, "face-raspberry",  ":-P",   ":P",	 ":-p", ":p", NULL);
	empathy_smiley_manager_add (manager, "face-sad",        ":-(",   ":(",   NULL);
	empathy_smiley_manager_add (manager, "face-smile",      ":-)",   ":)",   NULL);
	empathy_smiley_manager_add (manager, "face-smile-big",  ":-D",   ":D",   ":-d", ":d", NULL);
	empathy_smiley_manager_add (manager, "face-smirk",      ":-!",   ":!",   NULL);
	empathy_smiley_manager_add (manager, "face-surprise",   ":-O",   ":O",   NULL);
	empathy_smiley_manager_add (manager, "face-wink",       ";-)",   ";)",   NULL);
}
GSList *
empathy_smiley_manager_parse_len (EmpathySmileyManager *manager,
				  const gchar          *text,
				  gssize                len)
{
	EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
	EmpathySmileyHit         *hit;
	GSList                   *hits = NULL;
	SmileyManagerTree        *cur_tree = priv->tree;
	const gchar              *cur_str;
	const gchar              *start = NULL;

	g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL);
	g_return_val_if_fail (text != NULL, NULL);

	/* If len is negative, parse the string until we find '\0' */
	if (len < 0) {
		len = G_MAXSSIZE;
	}

	/* Parse the len first bytes of text to find smileys. Each time a smiley
	 * is detected, append a EmpathySmileyHit struct to the returned list,
	 * containing the smiley pixbuf and the position of the text to be
	 * replaced by it.
	 * cur_str is a pointer in the text showing the current position
	 * of the parsing. It is always at the begining of an UTF-8 character,
	 * because we support unicode smileys! For example we could want to
	 * replace ™ by an image. */

	for (cur_str = text;
	     *cur_str != '\0' && cur_str - text < len;
	     cur_str = g_utf8_next_char (cur_str)) {
		SmileyManagerTree *child;
		gunichar           c;

		c = g_utf8_get_char (cur_str);
		child = smiley_manager_tree_find_child (cur_tree, c);

		/* If we have a child it means c is part of a smiley */
		if (child) {
			if (cur_tree == priv->tree) {
				/* c is the first char of some smileys, keep
				 * the begining position */
				start = cur_str;
			}
			cur_tree = child;
			continue;
		}

		/* c is not part of a smiley. let's check if we found a smiley
		 * before it. */
		if (cur_tree->pixbuf != NULL) {
			/* found! */
			hit = smiley_hit_new (cur_tree, start - text,
					      cur_str - text);
			hits = g_slist_prepend (hits, hit);

			/* c was not part of this smiley, check if a new smiley
			 * start with it. */
			cur_tree = smiley_manager_tree_find_child (priv->tree, c);
			if (cur_tree) {
				start = cur_str;
			} else {
				cur_tree = priv->tree;
			}
		} else if (cur_tree != priv->tree) {
			/* We searched a smiley starting at 'start' but we ended
			 * with no smiley. Look again starting from next char.
			 *
			 * For example ">:)" and ":(" are both valid smileys,
			 * when parsing text ">:(" we first see '>' which could
			 * be the start of a smiley. 'start' variable is set to
			 * that position and we parse next char which is ':' and
			 * is still potential smiley. Then we see '(' which is
			 * NOT part of the smiley, ">:(" does not exist, so we
			 * have to start again from ':' to find ":(" which is
			 * correct smiley. */
			cur_str = start;
			cur_tree = priv->tree;
		}
	}

	/* Check if last char of the text was the end of a smiley */
	if (cur_tree->pixbuf != NULL) {
		hit = smiley_hit_new (cur_tree, start - text, cur_str - text);
		hits = g_slist_prepend (hits, hit);
	}

	return g_slist_reverse (hits);
}