changeset 21975:c7c5e2ff2b04

merge of '46216b10d02d4e5cae2343e469f0e405088537d3' and '795d7e0fc010741ba22123931ad0cca10f456bc8'
author Richard Laager <rlaager@wiktel.com>
date Wed, 02 Jan 2008 22:15:06 +0000
parents 2ca7a3d28186 (current diff) ecdb9f9b75e4 (diff)
children a86c035be695 c38d72677c8a
files
diffstat 74 files changed, 3190 insertions(+), 1470 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Tue Dec 18 18:18:31 2007 +0000
+++ b/AUTHORS	Wed Jan 02 22:15:06 2008 +0000
@@ -35,6 +35,7 @@
 Megan 'Cae' Schneider - support/QA
 Evan Schoenberg - Developer
 Kevin 'SimGuy' Stange - Developer & Webmaster
+Will 'resiak' Thompson - Developer
 Stu 'nosnilmot' Tomlinson - Developer
 Nathan 'faceprint' Walp - Developer
 
@@ -42,8 +43,8 @@
 -------------------
 Dennis 'EvilDennisR' Ristuccia
 Peter 'Fmoo' Ruibal
+Elliott 'QuLogic' Sales de Andrade
 Gabriel 'Nix' Schulhof
-Will 'resiak' Thompson
 
 Retired Developers:
 ------------------
--- a/COPYRIGHT	Tue Dec 18 18:18:31 2007 +0000
+++ b/COPYRIGHT	Wed Jan 02 22:15:06 2008 +0000
@@ -298,6 +298,7 @@
 Jory A. Pratt
 Brent Priddy
 Justin Pryzby
+Ignacio Casal Quinteiro
 Federicco Mena Quintero
 Yosef Radchenko
 David Raeman
@@ -327,6 +328,7 @@
 Andrew Sayman
 Alceste Scalas
 Carsten Schaar
+Jonathan Schleifer <js-pidgin@webkeks.org>
 Matteo Settenvini
 Colin Seymour
 Luke Schierer
@@ -413,6 +415,7 @@
 Zsombor Welker
 Andrew Wellington
 Adam Wendt
+Simon Wenner
 Dave West
 Zac West
 Daniel Westermann-Clark
--- a/ChangeLog	Tue Dec 18 18:18:31 2007 +0000
+++ b/ChangeLog	Wed Jan 02 22:15:06 2008 +0000
@@ -1,6 +1,6 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.3.2 (??/??/????):
+version 2.4.0 (??/??/????):
 	libpurple:
 	* Fixed various problems with loss of status messages when going
 	  or returning from idle on MySpaceIM.
@@ -12,6 +12,8 @@
 	* Added the ability to theme conversation name colors (red and blue)
 	  through your GTK+ theme, and exposed those theme settings to the 
 	  Pidgin GTK+ Theme Control plugin (Dustin Howett)
+	* Fixed having multiple alias edit areas in the infopane (Elliott Sales de
+	  Andrade)
 
 	Finch:
 	* Color is used in the buddylist to indicate status, and the conversation
--- a/ChangeLog.API	Tue Dec 18 18:18:31 2007 +0000
+++ b/ChangeLog.API	Wed Jan 02 22:15:06 2008 +0000
@@ -1,6 +1,33 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.3.2 (??/??/????):
+version 2.4.0 (??/??/????):
+	libpurple:
+		Added:
+		* purple_certificate_add_ca_search_path. (Florian Quèze)
+		* purple_gai_strerror.
+		* purple_major_version, purple_minor_version,
+		  purple_micro_version variables are exported by version.h,
+		  giving the version of libpurple in use at runtime.
+
+	Pidgin:
+		Added:
+		* pidgin_create_dialog to create a window that closes on escape. Also
+		  added utility functions pidgin_dialog_get_vbox_with_properties,
+		  pidgin_dialog_get_vbox, pidgin_dialog_get_action_area to access the
+		  contents in the created dialog. (Peter 'fmoo' Ruibal)
+		* pidgin_dialog_add_button to add buttons to a dialog created by
+		  pidgin_create_dialog.
+		* GTK_IMHTML_NO_SMILEY for GtkIMHtmlOptions means not to look for
+		  smileys in the text. (Florian 'goutnet' Delizy)
+		* pidgin_auto_parent_window to make a window transient for a suitable
+		  parent window.
+		* pidgin_tooltip_setup_for_treeview, pidgin_tooltip_destroy,
+		  pidgin_tooltip_show and pidgin_tooltip_setup_for_widget to simplify
+		  the process of drawing tooltips.
+
+	        Deprecated:
+	        * PIDGIN_DIALOG
+
 	Finch:
 		libgnt:
 		* Added gnt_tree_set_row_color to set the color for a row in a tree.
--- a/ChangeLog.win32	Tue Dec 18 18:18:31 2007 +0000
+++ b/ChangeLog.win32	Wed Jan 02 22:15:06 2008 +0000
@@ -1,3 +1,6 @@
+version 2.3.1 (12/7/2007):
+	* No changes
+
 version 2.3.0 (11/24/2007):
 	* Updated GTK+ to 2.12.1 (This was actually included in 2.2.2, but 
 	  didn't get into the Changelog.)
@@ -135,7 +138,7 @@
 
 version 0.82 (08/26/2004):
 	* Selecting away messages using the system tray icon works
-	  (Thanks Fran?ois Gagn?)
+	  (Thanks François Gagné)
 	* Transparency plugin will save your settings again (Kevin Stange)
 	* Updated gtk-wimp to 0.6.2
 	* Updated libpng to 1.2.6 (major security update)
--- a/configure.ac	Tue Dec 18 18:18:31 2007 +0000
+++ b/configure.ac	Wed Jan 02 22:15:06 2008 +0000
@@ -43,19 +43,19 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [3])
+m4_define([purple_lt_current], [4])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [3])
-m4_define([purple_micro_version], [2])
+m4_define([purple_minor_version], [4])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
 
-m4_define([gnt_lt_current], [3])
+m4_define([gnt_lt_current], [4])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_minor_version], [4])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -678,7 +678,6 @@
 PKG_CHECK_MODULES(MEANWHILE, [meanwhile >= 1.0.0 meanwhile < 2.0.0], [
 	have_meanwhile="yes"
 ], [
-	AC_MSG_RESULT(no)
 	have_meanwhile="no"
 ])
 AC_SUBST(MEANWHILE_CFLAGS)
@@ -697,7 +696,6 @@
 	avahiincludes="yes"
 	avahilibs="yes"
 ], [
-	AC_MSG_RESULT(no)
 	avahiincludes="no"
 	avahilibs="no"
 ])
@@ -742,7 +740,6 @@
 		silcincludes="yes"
 		silcclient="yes"
 	], [
-		AC_MSG_RESULT(no)
 		have_silc="no"
 	])
 	if test "x$have_silc" = "xno"; then
@@ -751,7 +748,6 @@
 			silc10includes="yes"
 			silc10client="yes"
 		], [
-			AC_MSG_RESULT(no)
 			have_silc="no"
 		])
 		dnl If silcclient.pc wasn't found, check for just silc.pc
@@ -761,7 +757,6 @@
 				silc10includes="yes"
 				silc10client="yes"
 			], [
-				AC_MSG_RESULT(no)
 				have_silc="no"
 			])
 		fi
@@ -1154,7 +1149,6 @@
 		AC_SUBST(DBUS_LIBS)
 		enable_dbus=yes
 	], [
-		AC_MSG_RESULT(no)
 		enable_dbus=no
 	])
 
--- a/doc/pidgin.1.in	Tue Dec 18 18:18:31 2007 +0000
+++ b/doc/pidgin.1.in	Wed Jan 02 22:15:06 2008 +0000
@@ -590,6 +590,8 @@
 .br
   Kevin 'SimGuy' Stange (developer and webmaster)
 .br
+  Will 'resiak' Thompson (developer)
+.br
   Stu 'nosnilmot' Tomlinson (developer)
 .br
   Nathan 'faceprint' Walp (developer)
@@ -602,9 +604,9 @@
 .br
   Peter 'fmoo' Ruibal
 .br
-  Gabriel 'Nix' Schulhof
+  Elliott 'QuLogic' Sales de Andrade
 .br
-  Will 'resiak' Thompson
+  Gabriel 'Nix' Schulhof
 .br
 
 
--- a/finch/gntrequest.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/finch/gntrequest.c	Wed Jan 02 22:15:06 2008 +0000
@@ -44,7 +44,7 @@
 	GntWidget *dialog;
 	GCallback *cbs;
 	gboolean save;
-} PurpleGntFileRequest;
+} FinchFileRequest;
 
 static GntWidget *
 setup_request_window(const char *title, const char *primary,
@@ -387,6 +387,156 @@
 	}
 }
 
+static GntWidget*
+create_boolean_field(PurpleRequestField *field)
+{
+	const char *label = purple_request_field_get_label(field);
+	GntWidget *check = gnt_check_box_new(label);
+	gnt_check_box_set_checked(GNT_CHECK_BOX(check),
+			purple_request_field_bool_get_default_value(field));
+	return check;
+}
+
+static GntWidget*
+create_string_field(PurpleRequestField *field, GntWidget **screenname)
+{
+	const char *hint = purple_request_field_get_type_hint(field);
+	GntWidget *entry = gnt_entry_new(
+			purple_request_field_string_get_default_value(field));
+	gnt_entry_set_masked(GNT_ENTRY(entry),
+			purple_request_field_string_is_masked(field));
+	if (hint && purple_str_has_prefix(hint, "screenname")) {
+		PurpleBlistNode *node = purple_blist_get_root();
+		gboolean offline = purple_str_has_suffix(hint, "all");
+		for (; node; node = purple_blist_node_next(node, offline)) {
+			if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
+				continue;
+			gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node));
+		}
+		gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE);
+		if (screenname)
+			*screenname = entry;
+	} else if (hint && !strcmp(hint, "group")) {
+		PurpleBlistNode *node;
+		for (node = purple_blist_get_root(); node; node = node->next) {
+			if (PURPLE_BLIST_NODE_IS_GROUP(node))
+				gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
+		}
+	}
+	return entry;
+}
+
+static GntWidget*
+create_integer_field(PurpleRequestField *field)
+{
+	char str[256];
+	int val = purple_request_field_int_get_default_value(field);
+	GntWidget *entry;
+
+	snprintf(str, sizeof(str), "%d", val);
+	entry = gnt_entry_new(str);
+	gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT);
+	return entry;
+}
+
+static GntWidget*
+create_choice_field(PurpleRequestField *field)
+{
+	int id;
+	GList *list;
+	GntWidget *combo = gnt_combo_box_new();
+
+	list = purple_request_field_choice_get_labels(field);
+	for (id = 1; list; list = list->next, id++)
+	{
+		gnt_combo_box_add_data(GNT_COMBO_BOX(combo),
+				GINT_TO_POINTER(id), list->data);
+	}
+	gnt_combo_box_set_selected(GNT_COMBO_BOX(combo),
+			GINT_TO_POINTER(purple_request_field_choice_get_default_value(field)));
+	return combo;
+}
+
+static GntWidget*
+create_list_field(PurpleRequestField *field)
+{
+	GntWidget *ret = NULL;
+	GList *list;
+	gboolean multi = purple_request_field_list_get_multi_select(field);
+	if (multi)
+	{
+		GntWidget *tree = gnt_tree_new();
+
+		list = purple_request_field_list_get_items(field);
+		for (; list; list = list->next)
+		{
+			const char *text = list->data;
+			gpointer key = purple_request_field_list_get_data(field, text);
+			gnt_tree_add_choice(GNT_TREE(tree), key,
+					gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL);
+			if (purple_request_field_list_is_selected(field, text))
+				gnt_tree_set_choice(GNT_TREE(tree), key, TRUE);
+		}
+		ret = tree;
+	}
+	else
+	{
+		GntWidget *combo = gnt_combo_box_new();
+
+		list = purple_request_field_list_get_items(field);
+		for (; list; list = list->next)
+		{
+			const char *text = list->data;
+			gpointer key = purple_request_field_list_get_data(field, text);
+			gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text);
+			if (purple_request_field_list_is_selected(field, text))
+				gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key);
+		}
+		ret = combo;
+	}
+	return ret;
+}
+
+static GntWidget*
+create_account_field(PurpleRequestField *field)
+{
+	gboolean all;
+	PurpleAccount *def;
+	GList *list;
+	GntWidget *combo = gnt_combo_box_new();
+
+	all = purple_request_field_account_get_show_all(field);
+	def = purple_request_field_account_get_value(field);
+	if (!def)
+		def = purple_request_field_account_get_default_value(field);
+
+	if (all)
+		list = purple_accounts_get_all();
+	else
+		list = purple_connections_get_all();
+
+	for (; list; list = list->next)
+	{
+		PurpleAccount *account;
+		char *text;
+
+		if (all)
+			account = list->data;
+		else
+			account = purple_connection_get_account(list->data);
+
+		text = g_strdup_printf("%s (%s)",
+				purple_account_get_username(account),
+				purple_account_get_protocol_name(account));
+		gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text);
+		g_free(text);
+		if (account == def)
+			gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account);
+	}
+	gnt_widget_set_size(combo, 20, 3); /* ew */
+	return combo;
+}
+
 static void *
 finch_request_fields(const char *title, const char *primary,
 		const char *secondary, PurpleRequestFields *allfields,
@@ -424,10 +574,10 @@
 			PurpleRequestField *field = fields->data;
 			PurpleRequestFieldType type = purple_request_field_get_type(field);
 			const char *label = purple_request_field_get_label(field);
-				
+
 			hbox = gnt_hbox_new(TRUE);   /* hrm */
 			gnt_box_add_widget(GNT_BOX(box), hbox);
-			
+
 			if (type != PURPLE_REQUEST_FIELD_BOOLEAN && label)
 			{
 				GntWidget *l = gnt_label_new(label);
@@ -437,154 +587,35 @@
 
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				GntWidget *check = gnt_check_box_new(label);
-				gnt_check_box_set_checked(GNT_CHECK_BOX(check),
-						purple_request_field_bool_get_default_value(field));
-				gnt_box_add_widget(GNT_BOX(hbox), check);
-				field->ui_data = check;
+				field->ui_data = create_boolean_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				const char *hint = purple_request_field_get_type_hint(field);
-				GntWidget *entry = gnt_entry_new(
-							purple_request_field_string_get_default_value(field));
-				gnt_entry_set_masked(GNT_ENTRY(entry),
-						purple_request_field_string_is_masked(field));
-				if (hint && purple_str_has_prefix(hint, "screenname")) {
-					PurpleBlistNode *node = purple_blist_get_root();
-					gboolean offline = purple_str_has_suffix(hint, "all");
-					for (; node; node = purple_blist_node_next(node, offline)) {
-						if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
-							continue;
-						gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node));
-					}
-					gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE);
-					screenname = entry;
-				} else if (hint && !strcmp(hint, "group")) {
-					PurpleBlistNode *node;
-					for (node = purple_blist_get_root(); node; node = node->next) {
-						if (PURPLE_BLIST_NODE_IS_GROUP(node))
-							gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
-					}
-				}
-				gnt_box_add_widget(GNT_BOX(hbox), entry);
-				field->ui_data = entry;
+				field->ui_data = create_string_field(field, &screenname);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				char str[256];
-				int val = purple_request_field_int_get_default_value(field);
-				GntWidget *entry;
-				
-				snprintf(str, sizeof(str), "%d", val);
-				entry = gnt_entry_new(str);
-				gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT);
-				gnt_box_add_widget(GNT_BOX(hbox), entry);
-				field->ui_data = entry;
+				field->ui_data = create_integer_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				int id;
-				GList *list;
-				GntWidget *combo = gnt_combo_box_new();
-				gnt_box_add_widget(GNT_BOX(hbox), combo);
-				field->ui_data = combo;
-
-				list = purple_request_field_choice_get_labels(field);
-				for (id = 1; list; list = list->next, id++)
-				{
-					gnt_combo_box_add_data(GNT_COMBO_BOX(combo),
-							GINT_TO_POINTER(id), list->data);
-				}
-				gnt_combo_box_set_selected(GNT_COMBO_BOX(combo),
-						GINT_TO_POINTER(purple_request_field_choice_get_default_value(field)));
+				field->ui_data = create_choice_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_LIST)
 			{
-				GList *list;
-				gboolean multi = purple_request_field_list_get_multi_select(field);
-				if (multi)
-				{
-					GntWidget *tree = gnt_tree_new();
-					gnt_box_add_widget(GNT_BOX(hbox), tree);
-					field->ui_data = tree;
-
-					list = purple_request_field_list_get_items(field);
-					for (; list; list = list->next)
-					{
-						const char *text = list->data;
-						gpointer key = purple_request_field_list_get_data(field, text);
-						gnt_tree_add_choice(GNT_TREE(tree), key,
-								gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL);
-						if (purple_request_field_list_is_selected(field, text))
-							gnt_tree_set_choice(GNT_TREE(tree), key, TRUE);
-					}
-				}
-				else
-				{
-					GntWidget *combo = gnt_combo_box_new();
-					gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-					gnt_box_add_widget(GNT_BOX(hbox), combo);
-					field->ui_data = combo;
-
-					list = purple_request_field_list_get_items(field);
-					for (; list; list = list->next)
-					{
-						const char *text = list->data;
-						gpointer key = purple_request_field_list_get_data(field, text);
-						gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text);
-						if (purple_request_field_list_is_selected(field, text))
-							gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key);
-					}
-				}
+				field->ui_data = create_list_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				gboolean all;
-				PurpleAccount *def;
-				GList *list;
-				GntWidget *combo = gnt_combo_box_new();
-				gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-				gnt_box_add_widget(GNT_BOX(hbox), combo);
-				field->ui_data = combo;
-
-				all = purple_request_field_account_get_show_all(field);
-				def = purple_request_field_account_get_value(field);
-				if (!def)
-					def = purple_request_field_account_get_default_value(field);
-
-				if (all)
-					list = purple_accounts_get_all();
-				else
-					list = purple_connections_get_all();
-
-				for (; list; list = list->next)
-				{
-					PurpleAccount *account;
-					char *text;
-
-					if (all)
-						account = list->data;
-					else
-						account = purple_connection_get_account(list->data);
-
-					text = g_strdup_printf("%s (%s)",
-							purple_account_get_username(account),
-							purple_account_get_protocol_name(account));
-					gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text);
-					g_free(text);
-					if (account == def)
-						gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account);
-				}
-				gnt_widget_set_size(combo, 20, 3); /* ew */
-				accountlist = combo;
+				accountlist = field->ui_data = create_account_field(field);
 			}
 			else
 			{
-				gnt_box_add_widget(GNT_BOX(hbox),
-						gnt_label_new_with_format(_("Not implemented yet."),
-							GNT_TEXT_FLAG_BOLD));
+				field->ui_data = gnt_label_new_with_format(_("Not implemented yet."),
+						GNT_TEXT_FLAG_BOLD);
 			}
+			gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
+			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(field->ui_data));
 		}
 		if (grlist->next)
 			gnt_box_add_widget(GNT_BOX(box), gnt_hline_new());
@@ -610,7 +641,7 @@
 static void
 file_cancel_cb(gpointer fq, GntWidget *wid)
 {
-	PurpleGntFileRequest *data = fq;
+	FinchFileRequest *data = fq;
 	if (data->cbs[1] != NULL)
 		((PurpleRequestFileCb)data->cbs[1])(data->user_data, NULL);
 
@@ -620,7 +651,7 @@
 static void
 file_ok_cb(gpointer fq, GntWidget *widget)
 {
-	PurpleGntFileRequest *data = fq;
+	FinchFileRequest *data = fq;
 	char *file = gnt_file_sel_get_selected_file(GNT_FILE_SEL(data->dialog));
 	char *dir = g_path_get_dirname(file);
 	if (data->cbs[0] != NULL)
@@ -634,38 +665,30 @@
 }
 
 static void
-file_request_destroy(PurpleGntFileRequest *data)
+file_request_destroy(FinchFileRequest *data)
 {
 	g_free(data->cbs);
 	g_free(data);
 }
 
-static void *
-finch_request_file(const char *title, const char *filename,
-				gboolean savedialog,
+static FinchFileRequest *
+finch_file_request_window(const char *title, const char *path,
 				GCallback ok_cb, GCallback cancel_cb,
-				PurpleAccount *account, const char *who, PurpleConversation *conv,
 				void *user_data)
 {
 	GntWidget *window = gnt_file_sel_new();
 	GntFileSel *sel = GNT_FILE_SEL(window);
-	PurpleGntFileRequest *data = g_new0(PurpleGntFileRequest, 1);
-	const char *path;
+	FinchFileRequest *data = g_new0(FinchFileRequest, 1);
 
 	data->user_data = user_data;
 	data->cbs = g_new0(GCallback, 2);
 	data->cbs[0] = ok_cb;
 	data->cbs[1] = cancel_cb;
 	data->dialog = window;
-	data->save = savedialog;
-	gnt_box_set_title(GNT_BOX(window), title ? title : (savedialog ? _("Save File...") : _("Open File...")));
+	gnt_box_set_title(GNT_BOX(window), title);
 
-	path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder");
 	gnt_file_sel_set_current_location(sel, (path && *path) ? path : purple_home_dir());
 
-	if (savedialog)
-		gnt_file_sel_set_suggested_filename(sel, filename);
-
 	g_signal_connect(G_OBJECT(sel->cancel), "activate",
 			G_CALLBACK(action_performed), window);
 	g_signal_connect(G_OBJECT(sel->select), "activate",
@@ -678,9 +701,45 @@
 	setup_default_callback(window, file_cancel_cb, data);
 	g_object_set_data_full(G_OBJECT(window), "filerequestdata", data,
 			(GDestroyNotify)file_request_destroy);
-	gnt_widget_show(window);
+
+	return data;
+}
+
+static void *
+finch_request_file(const char *title, const char *filename,
+				gboolean savedialog,
+				GCallback ok_cb, GCallback cancel_cb,
+				PurpleAccount *account, const char *who, PurpleConversation *conv,
+				void *user_data)
+{
+	FinchFileRequest *data;
+	const char *path;
 
-	return window;
+	path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder");
+	data = finch_file_request_window(title ? title : (savedialog ? _("Save File...") : _("Open File...")), path,
+			ok_cb, cancel_cb, user_data);
+	data->save = savedialog;
+	if (savedialog)
+		gnt_file_sel_set_suggested_filename(GNT_FILE_SEL(data->dialog), filename);
+
+	gnt_widget_show(data->dialog);
+	return data->dialog;
+}
+
+static void *
+finch_request_folder(const char *title, const char *dirname, GCallback ok_cb,
+		GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv,
+		void *user_data)
+{
+	FinchFileRequest *data;
+
+	data = finch_file_request_window(title ? title : _("Choose Location..."), dirname,
+			ok_cb, cancel_cb, user_data);
+	data->save = TRUE;
+	gnt_file_sel_set_dirs_only(GNT_FILE_SEL(data->dialog), TRUE);
+
+	gnt_widget_show(data->dialog);
+	return data->dialog;
 }
 
 static PurpleRequestUiOps uiops =
@@ -691,7 +750,7 @@
 	finch_request_fields,
 	finch_request_file,
 	finch_close_request,
-	NULL,                       /* No plans for request_folder */
+	finch_request_folder,
 	NULL,
 	NULL,
 	NULL,
--- a/finch/libgnt/configure.ac	Tue Dec 18 18:18:31 2007 +0000
+++ b/finch/libgnt/configure.ac	Wed Jan 02 22:15:06 2008 +0000
@@ -24,10 +24,10 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [3])
+m4_define([gnt_lt_current], [4])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_minor_version], [4])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/finch/libgnt/gntcolors.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Wed Jan 02 22:15:06 2008 +0000
@@ -93,7 +93,7 @@
  *
  * @return A color
  *
- * @since 2.3.1 (gnt), 2.3.1 (pidgin)
+ * @since 2.4.0
  */
 int gnt_colors_get_color(char *key);
 #endif
@@ -119,7 +119,7 @@
  *
  * @return  A color pair
  *
- * @since 2.3.1
+ * @since 2.4.0
  */
 int gnt_color_add_pair(int fg, int bg);
 #endif
--- a/finch/libgnt/gntstyle.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Wed Jan 02 22:15:06 2008 +0000
@@ -74,7 +74,7 @@
  *
  * @return        NULL terminated string array. The array should be freed with g_strfreev().
  *
- * @since 2.3.2
+ * @since 2.4.0
  */
 char **gnt_style_get_string_list(const char *group, const char *key, gsize *length);
 
@@ -87,7 +87,7 @@
  *
  * @return  The value of the color as an int, or 0 on error.
  *
- * @since 2.3.2
+ * @since 2.4.0
  */
 int gnt_style_get_color(char *group, char *key);
 
--- a/finch/libgnt/gnttree.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/finch/libgnt/gnttree.h	Wed Jan 02 22:15:06 2008 +0000
@@ -330,6 +330,7 @@
  * @param tree   The tree
  * @param key    The key for the row
  * @param color  The color
+ * @since 2.4.0
  */
 void gnt_tree_set_row_color(GntTree *, void *, int);
 
--- a/libpurple/account.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/account.c	Wed Jan 02 22:15:06 2008 +0000
@@ -1645,7 +1645,7 @@
 	if (status == NULL)
 	{
 		purple_debug_error("account",
-				   "Invalid status ID %s for account %s (%s)\n",
+				   "Invalid status ID '%s' for account %s (%s)\n",
 				   status_id, purple_account_get_username(account),
 				   purple_account_get_protocol_id(account));
 		return;
--- a/libpurple/blist.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/blist.h	Wed Jan 02 22:15:06 2008 +0000
@@ -680,7 +680,8 @@
  *
  * @param g The group
  *
- * @return A list of purple_accounts
+ * @return A GSList of accounts (which must be freed), or NULL if the group
+ *         has no accounts.
  */
 GSList *purple_group_get_accounts(PurpleGroup *g);
 
--- a/libpurple/certificate.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/certificate.c	Wed Jan 02 22:15:06 2008 +0000
@@ -627,7 +627,7 @@
 
 /** System directory to probe for CA certificates */
 /* This is set in the lazy_init function */
-static const gchar *x509_ca_syspath = NULL;
+static GList *x509_ca_paths = NULL;
 
 /** A list of loaded CAs, populated from the above path whenever the lazy_init
     happens. Contains pointers to x509_ca_elements */
@@ -674,6 +674,7 @@
 	GDir *certdir;
 	const gchar *entry;
 	GPatternSpec *pempat;
+	GList *iter = NULL;
 	
 	if (x509_ca_initialized) return TRUE;
 
@@ -687,54 +688,48 @@
 		return FALSE;
 	}
 
-	/* Attempt to point at the appropriate system path */
-	if (NULL == x509_ca_syspath) {
-#ifdef _WIN32
-		x509_ca_syspath = g_build_filename(DATADIR,
-						   "ca-certs", NULL);
-#else
-		x509_ca_syspath = g_build_filename(DATADIR,
-						   "purple", "ca-certs", NULL);
-#endif
-	}
-
-	/* Populate the certificates pool from the system path */
-	certdir = g_dir_open(x509_ca_syspath, 0, NULL);
-	g_return_val_if_fail(certdir, FALSE);
-
 	/* Use a glob to only read .pem files */
 	pempat = g_pattern_spec_new("*.pem");
-	
-	while ( (entry = g_dir_read_name(certdir)) ) {
-		gchar *fullpath;
-		PurpleCertificate *crt;
 
-		if ( !g_pattern_match_string(pempat, entry) ) {
+	/* Populate the certificates pool from the search path(s) */
+	for (iter = x509_ca_paths; iter; iter = iter->next) {
+		certdir = g_dir_open(iter->data, 0, NULL);
+		if (!certdir) {
+			purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", iter->data);
 			continue;
 		}
 
-		fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
-		
-		/* TODO: Respond to a failure in the following? */
-		crt = purple_certificate_import(x509, fullpath);
+		while ( (entry = g_dir_read_name(certdir)) ) {
+			gchar *fullpath;
+			PurpleCertificate *crt;
+
+			if ( !g_pattern_match_string(pempat, entry) ) {
+				continue;
+			}
+
+			fullpath = g_build_filename(iter->data, entry, NULL);
+
+			/* TODO: Respond to a failure in the following? */
+			crt = purple_certificate_import(x509, fullpath);
 
-		if (x509_ca_quiet_put_cert(crt)) {
-			purple_debug_info("certificate/x509/ca",
-					  "Loaded %s\n",
-					  fullpath);
-		} else {
-			purple_debug_error("certificate/x509/ca",
-					  "Failed to load %s\n",
-					  fullpath);
+			if (x509_ca_quiet_put_cert(crt)) {
+				purple_debug_info("certificate/x509/ca",
+						  "Loaded %s\n",
+						  fullpath);
+			} else {
+				purple_debug_error("certificate/x509/ca",
+						  "Failed to load %s\n",
+						  fullpath);
+			}
+
+			purple_certificate_destroy(crt);
+			g_free(fullpath);
 		}
-
-		purple_certificate_destroy(crt);
-		g_free(fullpath);
+		g_dir_close(certdir);
 	}
 
 	g_pattern_spec_free(pempat);
-	g_dir_close(certdir);
-	
+
 	purple_debug_info("certificate/x509/ca",
 			  "Lazy init completed.\n");
 	x509_ca_initialized = TRUE;
@@ -744,6 +739,17 @@
 static gboolean
 x509_ca_init(void)
 {
+	/* Attempt to point at the appropriate system path */
+	if (NULL == x509_ca_paths) {
+#ifdef _WIN32
+		x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
+						   "ca-certs", NULL));
+#else
+		x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
+						   "purple", "ca-certs", NULL));
+#endif
+	}
+
 	/* Attempt to initialize now, but if it doesn't work, that's OK;
 	   it will get done later */
 	if ( ! x509_ca_lazy_init()) {
@@ -752,7 +758,7 @@
 				  "dependency is not yet registered. "
 				  "It has been deferred to later.\n");
 	}
-	
+
 	return TRUE;
 }
 
@@ -768,6 +774,9 @@
 	g_list_free(x509_ca_certs);
 	x509_ca_certs = NULL;
 	x509_ca_initialized = FALSE;
+	g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL);
+	g_list_free(x509_ca_paths);
+	x509_ca_paths = NULL;
 }
 
 /** Look up a ca_element by dn */
@@ -1219,6 +1228,9 @@
 }
 
 static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq);
+
+static void
 x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
 {
 	/* TODO: Looking this up by name over and over is expensive.
@@ -1259,8 +1271,8 @@
 	} else {
 		purple_debug_info("certificate/x509/tls_cached",
 				  "Peer cert did NOT match cached\n");
-		/* vrq now becomes the problem of cert_changed */
-		x509_tls_cached_peer_cert_changed(vrq);
+		/* vrq now becomes the problem of the user */
+		x509_tls_cached_unknown_peer(vrq);
 	}
 	
 	purple_certificate_destroy(cached_crt);
@@ -1271,7 +1283,9 @@
 /* For when we've never communicated with this party before */
 /* TODO: Need ways to specify possibly multiple problems with a cert, or at
    least  reprioritize them. For example, maybe the signature ought to be
-   checked BEFORE the hostname checking? */
+   checked BEFORE the hostname checking?
+   Stu thinks we should check the signature before the name, so we do now.
+   The above TODO still stands. */
 static void
 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
 {
@@ -1283,35 +1297,6 @@
 
 	peer_crt = (PurpleCertificate *) chain->data;
 
-	/* First, check that the hostname matches */
-	if ( ! purple_certificate_check_subject_name(peer_crt,
-						     vrq->subject_name) ) {
-		gchar *sn = purple_certificate_get_subject_name(peer_crt);
-		gchar *msg;
-		
-		purple_debug_info("certificate/x509/tls_cached",
-				  "Name mismatch: Certificate given for %s "
-				  "has a name of %s\n",
-				  vrq->subject_name, sn);
-
-		/* Prompt the user to authenticate the certificate */
-		/* TODO: Provide the user with more guidance about why he is
-		   being prompted */
-		/* vrq will be completed by user_auth */
-		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
-					"claims to be from \"%s\" instead.  "
-					"This could mean that you are not "
-					"connecting to the service you "
-					"believe you are."),
-				      vrq->subject_name, sn);
-				      
-		x509_tls_cached_user_auth(vrq,msg);
-
-		g_free(sn);
-		g_free(msg);
-		return;
-	} /* if (name mismatch) */
-
 	/* TODO: Figure out a way to check for a bad signature, as opposed to
 	   "not self-signed" */
 	if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
@@ -1332,7 +1317,7 @@
 
 		g_free(msg);
 		return;
-	} /* if (name mismatch) */
+	} /* if (self signed) */
 	
 	/* Next, check that the certificate chain is valid */
 	if ( ! purple_certificate_check_signature_chain(chain) ) {
@@ -1431,6 +1416,35 @@
 		return;
 	} /* if (CA signature not good) */
 
+	/* Last, check that the hostname matches */
+	if ( ! purple_certificate_check_subject_name(peer_crt,
+						     vrq->subject_name) ) {
+		gchar *sn = purple_certificate_get_subject_name(peer_crt);
+		gchar *msg;
+		
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Name mismatch: Certificate given for %s "
+				  "has a name of %s\n",
+				  vrq->subject_name, sn);
+
+		/* Prompt the user to authenticate the certificate */
+		/* TODO: Provide the user with more guidance about why he is
+		   being prompted */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"claims to be from \"%s\" instead.  "
+					"This could mean that you are not "
+					"connecting to the service you "
+					"believe you are."),
+				      vrq->subject_name, sn);
+				      
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(sn);
+		g_free(msg);
+		return;
+	} /* if (name mismatch) */
+
 	/* If we reach this point, the certificate is good. */
 	/* Look up the local cache and store it there for future use */
 	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
@@ -1901,3 +1915,10 @@
 	g_byte_array_free(sha_bin, TRUE);
 }
 
+void purple_certificate_add_ca_search_path(const char *path)
+{
+	if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp))
+		return;
+	x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path));
+}
+
--- a/libpurple/certificate.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/certificate.h	Wed Jan 02 22:15:06 2008 +0000
@@ -786,6 +786,12 @@
 void
 purple_certificate_display_x509(PurpleCertificate *crt);
 
+/**
+ * Add a search path for certificates.
+ *
+ * @param path   Path to search for certificates.
+ */
+void purple_certificate_add_ca_search_path(const char *path);
 
 #ifdef __cplusplus
 }
--- a/libpurple/cipher.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/cipher.c	Wed Jan 02 22:15:06 2008 +0000
@@ -64,6 +64,8 @@
 /*******************************************************************************
  * MD5
  ******************************************************************************/
+#define MD5_HMAC_BLOCK_SIZE	64
+
 struct MD5Context {
 	guint32 total[2];
 	guint32 state[4];
@@ -325,6 +327,13 @@
 	return TRUE;
 }
 
+static size_t
+md5_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return MD5_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps MD5Ops = {
 	NULL,			/* Set option */
 	NULL,			/* Get option */
@@ -340,12 +349,10 @@
 	NULL,			/* get salt size */
 	NULL,			/* set key */
 	NULL,			/* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	md5_get_block_size,	/* get block size */
+	NULL			/* set key with len */
 };
 
 /*******************************************************************************
@@ -580,6 +587,13 @@
 	md4_context = NULL;
 }
 
+static size_t
+md4_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return MD4_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps MD4Ops = {
 	NULL,                   /* Set option */
 	NULL,                   /* Get option */
@@ -595,12 +609,202 @@
 	NULL,                   /* get salt size */
 	NULL,                   /* set key */
 	NULL,                   /* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,                   /* set batch mode */
+	NULL,                   /* get batch mode */
+	md4_get_block_size,     /* get block size */
+	NULL                    /* set key with len */
+};
+
+/*******************************************************************************
+ * HMAC
+ ******************************************************************************/
+
+struct HMAC_Context {
+	PurpleCipherContext *hash;
+	char *name;
+	int blocksize;
+	guchar *opad;
+};
+
+static void
+hmac_init(PurpleCipherContext *context, gpointer extra)
+{
+	struct HMAC_Context *hctx;
+	hctx = g_new0(struct HMAC_Context, 1);
+	purple_cipher_context_set_data(context, hctx);
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+hmac_reset(PurpleCipherContext *context, gpointer extra)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	g_free(hctx->name);
+	hctx->name = NULL;
+	if (hctx->hash)
+		purple_cipher_context_destroy(hctx->hash);
+	hctx->hash = NULL;
+	hctx->blocksize = 0;
+	g_free(hctx->opad);
+	hctx->opad = NULL;
+}
+
+static void
+hmac_set_opt(PurpleCipherContext *context, const gchar *name, void *value)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	if (!strcmp(name, "hash")) {
+		g_free(hctx->name);
+		if (hctx->hash)
+			purple_cipher_context_destroy(hctx->hash);
+		hctx->name = g_strdup((char*)value);
+		hctx->hash = purple_cipher_context_new_by_name((char *)value, NULL);
+		hctx->blocksize = purple_cipher_context_get_block_size(hctx->hash);
+	}
+}
+
+static void *
+hmac_get_opt(PurpleCipherContext *context, const gchar *name)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	if (!strcmp(name, "hash")) {
+		return hctx->name;
+	}
+
+	return NULL;
+}
+
+static void
+hmac_append(PurpleCipherContext *context, const guchar *data, size_t len) 
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(hctx->hash != NULL);
+
+	purple_cipher_context_append(hctx->hash, data, len);
+}
+
+static gboolean
+hmac_digest(PurpleCipherContext *context, size_t in_len, guchar *out, size_t *out_len)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+	PurpleCipherContext *hash = hctx->hash;
+	guchar *inner_hash;
+	size_t hash_len;
+	gboolean result;
+
+	g_return_val_if_fail(hash != NULL, FALSE);
+
+	inner_hash = g_malloc(100); /* TODO: Should be enough for now... */
+	result = purple_cipher_context_digest(hash, 100, inner_hash, &hash_len);
+
+	purple_cipher_context_reset(hash, NULL);
+
+	purple_cipher_context_append(hash, hctx->opad, hctx->blocksize);
+	purple_cipher_context_append(hash, inner_hash, hash_len);
+
+	g_free(inner_hash);
+
+	result = result && purple_cipher_context_digest(hash, in_len, out, out_len);
+
+	return result;
+}
+
+static void
+hmac_uninit(PurpleCipherContext *context)
+{
+	struct HMAC_Context *hctx;
+
+	purple_cipher_context_reset(context, NULL);
+
+	hctx = purple_cipher_context_get_data(context);
+
+	g_free(hctx);
+}
+
+static void
+hmac_set_key_with_len(PurpleCipherContext *context, const guchar * key, size_t key_len)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+	int blocksize, i;
+	guchar *ipad;
+	guchar *full_key;
+
+	g_return_if_fail(hctx->hash != NULL);
+
+	g_free(hctx->opad);
+
+	blocksize = hctx->blocksize;
+	ipad = g_malloc(blocksize);
+	hctx->opad = g_malloc(blocksize);
+
+	if (key_len > blocksize) {
+		purple_cipher_context_reset(hctx->hash, NULL);
+		purple_cipher_context_append(hctx->hash, key, key_len);
+		full_key = g_malloc(100); /* TODO: Should be enough for now... */
+		purple_cipher_context_digest(hctx->hash, 100, full_key, &key_len);
+	} else
+		full_key = g_memdup(key, key_len);
+
+    if (key_len < blocksize) {
+    	full_key = g_realloc(full_key, blocksize);
+    	memset(full_key + key_len, 0, blocksize - key_len);
+    }
+
+	for(i = 0; i < blocksize; i++) {
+		ipad[i] = 0x36 ^ full_key[i];
+		hctx->opad[i] = 0x5c ^ full_key[i];
+	}
+
+	g_free(full_key);
+
+	purple_cipher_context_reset(hctx->hash, NULL);
+	purple_cipher_context_append(hctx->hash, ipad, blocksize);
+	g_free(ipad);
+}
+
+static void
+hmac_set_key(PurpleCipherContext *context, const guchar * key)
+{
+	hmac_set_key_with_len(context, key, strlen(key));
+}
+
+static size_t 
+hmac_get_block_size(PurpleCipherContext *context)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+
+	return hctx->blocksize;
+}
+
+static PurpleCipherOps HMACOps = {
+	hmac_set_opt,           /* Set option */
+	hmac_get_opt,           /* Get option */
+	hmac_init,               /* init */
+	hmac_reset,              /* reset */
+	hmac_uninit,             /* uninit */
+	NULL,                   /* set iv */
+	hmac_append,             /* append */
+	hmac_digest,             /* digest */
+	NULL,                   /* encrypt */
+	NULL,                   /* decrypt */
+	NULL,                   /* set salt */
+	NULL,                   /* get salt size */
+	hmac_set_key,           /* set key */
+	NULL,                   /* get key size */
+	NULL,                   /* set batch mode */
+	NULL,                   /* get batch mode */
+	hmac_get_block_size,    /* get block size */
+	hmac_set_key_with_len   /* set key with len */
 };
 
 /******************************************************************************
@@ -986,6 +1190,36 @@
 	return 0;
 }
 
+static gint
+des_decrypt(PurpleCipherContext *context, const guchar data[],
+	    size_t len, guchar output[], size_t *outlen) {
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while(offset+8<=len) {
+		des_ecb_crypt(purple_cipher_context_get_data(context),
+				data+offset,
+				output+offset,
+				1);
+		offset+=8;
+	}
+	*outlen = len;
+	if(offset<len) {
+		*outlen += len - offset;
+		tmp = offset;
+		while(tmp<len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(purple_cipher_context_get_data(context),
+				buf,
+				output+offset,
+				1);
+	}	
+	return 0;
+}
+
 static void
 des_init(PurpleCipherContext *context, gpointer extra) {
 	struct _des_ctx *mctx;
@@ -1005,32 +1239,380 @@
 }
 
 static PurpleCipherOps DESOps = {
-	NULL,                   /* Set option */
-	NULL,                   /* Get option */
-	des_init,               /* init */
+	NULL,              /* Set option */
+	NULL,              /* Get option */
+	des_init,          /* init */
+ 	NULL,              /* reset */
+	des_uninit,        /* uninit */
+	NULL,              /* set iv */
+	NULL,              /* append */
+	NULL,              /* digest */
+	des_encrypt,       /* encrypt */
+	des_decrypt,       /* decrypt */
+	NULL,              /* set salt */
+	NULL,              /* get salt size */
+	des_set_key,       /* set key */
+	NULL,              /* get key size */
+	NULL,              /* set batch mode */
+	NULL,              /* get batch mode */
+	NULL,              /* get block size */
+	NULL               /* set key with len */
+};
+
+/******************************************************************************
+ * Triple-DES
+ *****************************************************************************/
+
+typedef struct _des3_ctx
+{
+	PurpleCipherBatchMode mode;
+	guchar iv[8];
+	/* First key for encryption */
+	struct _des_ctx key1;
+	/* Second key for decryption */
+	struct _des_ctx key2;
+	/* Third key for encryption */
+	struct _des_ctx key3;
+} des3_ctx[1];
+
+/*
+ *  Fill a DES3 context with subkeys calculated from 3 64bit key.
+ *  Does not check parity bits, but simply ignore them.
+ *  Does not check for weak keys.
+ **/
+static void
+des3_set_key(PurpleCipherContext *context, const guchar * key)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+	int i;
+
+	des_key_schedule (key +  0, ctx->key1.encrypt_subkeys);
+	des_key_schedule (key +  8, ctx->key2.encrypt_subkeys);
+	des_key_schedule (key + 16, ctx->key3.encrypt_subkeys);
+
+	for (i = 0; i < 32; i += 2)
+	{
+		ctx->key1.decrypt_subkeys[i]	= ctx->key1.encrypt_subkeys[30-i];
+		ctx->key1.decrypt_subkeys[i+1]	= ctx->key1.encrypt_subkeys[31-i];
+		ctx->key2.decrypt_subkeys[i]	= ctx->key2.encrypt_subkeys[30-i];
+		ctx->key2.decrypt_subkeys[i+1]	= ctx->key2.encrypt_subkeys[31-i];
+		ctx->key3.decrypt_subkeys[i]	= ctx->key3.encrypt_subkeys[30-i];
+		ctx->key3.decrypt_subkeys[i+1]	= ctx->key3.encrypt_subkeys[31-i];
+	}
+}
+
+static gint
+des3_ecb_encrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while (offset + 8 <= len) {
+		des_ecb_crypt(&ctx->key1,
+		              data+offset,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+		offset += 8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		while (tmp < len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+	}
+	return 0;
+}
+
+static gint
+des3_cbc_encrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8];
+	memcpy(buf, ctx->iv, 8);
+	while (offset + 8 <= len) {
+		for (i = 0; i < 8; i++)
+			buf[i] ^= data[offset + i];
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+		memcpy(buf, output+offset, 8);
+		offset += 8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		i = 0;
+		while (tmp < len) {
+			buf[i++] ^= data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+	}
+	return 0;
+}
+
+static gint
+des3_encrypt(PurpleCipherContext *context, const guchar data[],
+             size_t len, guchar output[], size_t *outlen)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) {
+		return des3_ecb_encrypt(ctx, data, len, output, outlen);
+	} else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) {
+		return des3_cbc_encrypt(ctx, data, len, output, outlen);
+	} else {
+		g_return_val_if_reached(0);
+	}
+
+	return 0;
+}
+
+static gint
+des3_ecb_decrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while (offset + 8 <= len) {
+		/* NOTE: Apply key in reverse */
+		des_ecb_crypt(&ctx->key3,
+		              data+offset,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		offset+=8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		while (tmp < len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+	}
+	return 0;
+}
+
+static gint
+des3_cbc_decrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	guint8 link[8];
+	memcpy(link, ctx->iv, 8);
+	while (offset + 8 <= len) {
+		des_ecb_crypt(&ctx->key3,
+		              data+offset,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		for (i = 0; i < 8; i++)
+			output[offset + i] ^= link[i];
+		memcpy(link, data + offset, 8);
+		offset+=8;
+	}
+	*outlen = len;
+	if(offset<len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		i = 0;
+		while(tmp<len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		for (i = 0; i < 8; i++)
+			output[offset + i] ^= link[i];
+	}
+	return 0;
+}
+
+static gint
+des3_decrypt(PurpleCipherContext *context, const guchar data[],
+             size_t len, guchar output[], size_t *outlen)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) {
+		return des3_ecb_decrypt(ctx, data, len, output, outlen);
+	} else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) {
+		return des3_cbc_decrypt(ctx, data, len, output, outlen);
+	} else {
+		g_return_val_if_reached(0);
+	}
+
+	return 0;
+}
+
+static void
+des3_set_batch(PurpleCipherContext *context, PurpleCipherBatchMode mode)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	ctx->mode = mode;
+}
+
+static PurpleCipherBatchMode
+des3_get_batch(PurpleCipherContext *context)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	return ctx->mode;
+}
+
+static void
+des3_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
+{
+	struct _des3_ctx *ctx;
+
+	g_return_if_fail(len == 8);
+
+	ctx = purple_cipher_context_get_data(context);
+
+	memcpy(ctx->iv, iv, len);
+}
+
+static void
+des3_init(PurpleCipherContext *context, gpointer extra)
+{
+	struct _des3_ctx *mctx;
+	mctx = g_new0(struct _des3_ctx, 1);
+	purple_cipher_context_set_data(context, mctx);
+}
+
+static void
+des3_uninit(PurpleCipherContext *context)
+{
+	struct _des3_ctx *des3_context;
+
+	des3_context = purple_cipher_context_get_data(context);
+	memset(des3_context, 0, sizeof(des3_context));
+
+	g_free(des3_context);
+	des3_context = NULL;
+}
+
+static PurpleCipherOps DES3Ops = {
+	NULL,              /* Set option */
+	NULL,              /* Get option */
+	des3_init,         /* init */
 	NULL,              /* reset */
-	des_uninit,             /* uninit */
-	NULL,                   /* set iv */
-	NULL,             /* append */
-	NULL,             /* digest */
-	des_encrypt,                   /* encrypt */
-	NULL,                   /* decrypt */
-	NULL,                   /* set salt */
-	NULL,                   /* get salt size */
-	des_set_key,		/* set key */
-	NULL,                   /* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	des3_uninit,       /* uninit */
+	des3_set_iv,       /* set iv */
+	NULL,              /* append */
+	NULL,              /* digest */
+	des3_encrypt,      /* encrypt */
+	des3_decrypt,      /* decrypt */
+	NULL,              /* set salt */
+	NULL,              /* get salt size */
+	des3_set_key,      /* set key */
+	NULL,              /* get key size */
+	des3_set_batch,    /* set batch mode */
+	des3_get_batch,    /* get batch mode */
+	NULL,              /* get block size */
+	NULL               /* set key with len */
 };
 
-
 /*******************************************************************************
  * SHA-1
  ******************************************************************************/
+#define SHA1_HMAC_BLOCK_SIZE	64
 #define SHA1_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xFFFFFFFF)
 
 struct SHA1Context {
@@ -1251,6 +1833,13 @@
 	return TRUE;
 }
 
+static size_t
+sha1_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return SHA1_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps SHA1Ops = {
 	sha1_set_opt,	/* Set Option		*/
 	sha1_get_opt,	/* Get Option		*/
@@ -1266,12 +1855,10 @@
 	NULL,			/* get salt size	*/
 	NULL,			/* set key			*/
 	NULL,			/* get key size		*/
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	sha1_get_block_size,	/* get block size */
+	NULL			/* set key with len */
 };
 
 /*******************************************************************************
@@ -1435,12 +2022,10 @@
 	NULL,          /* get salt size */
 	rc4_set_key,   /* set key       */
 	rc4_get_key_size, /* get key size  */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,          /* set batch mode */
+	NULL,          /* get batch mode */
+	NULL,          /* get block size */
+	NULL           /* set key with len */
 };
 
 /*******************************************************************************
@@ -1510,6 +2095,14 @@
 		caps |= PURPLE_CIPHER_CAPS_SET_KEY;
 	if(ops->get_key_size)
 		caps |= PURPLE_CIPHER_CAPS_GET_KEY_SIZE;
+	if(ops->set_batch_mode)
+		caps |= PURPLE_CIPHER_CAPS_SET_BATCH_MODE;
+	if(ops->get_batch_mode)
+		caps |= PURPLE_CIPHER_CAPS_GET_BATCH_MODE;
+	if(ops->get_block_size)
+		caps |= PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE;
+	if(ops->set_key_with_len)
+		caps |= PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN;
 
 	return caps;
 }
@@ -1636,7 +2229,9 @@
 	purple_ciphers_register_cipher("md5", &MD5Ops);
 	purple_ciphers_register_cipher("sha1", &SHA1Ops);
 	purple_ciphers_register_cipher("md4", &MD4Ops);
+	purple_ciphers_register_cipher("hmac", &HMACOps);
 	purple_ciphers_register_cipher("des", &DESOps);
+	purple_ciphers_register_cipher("des3", &DES3Ops);
 	purple_ciphers_register_cipher("rc4", &RC4Ops);
 }
 
@@ -1967,6 +2562,80 @@
 }
 
 void
+purple_cipher_context_set_batch_mode(PurpleCipherContext *context,
+                                     PurpleCipherBatchMode mode)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_if_fail(context);
+
+	cipher = context->cipher;
+	g_return_if_fail(cipher);
+
+	if(cipher->ops && cipher->ops->set_batch_mode)
+		cipher->ops->set_batch_mode(context, mode);
+	else
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "set_batch_mode operation\n", cipher->name);
+}
+
+PurpleCipherBatchMode
+purple_cipher_context_get_batch_mode(PurpleCipherContext *context)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_val_if_fail(context, -1);
+
+	cipher = context->cipher;
+	g_return_val_if_fail(cipher, -1);
+
+	if(cipher->ops && cipher->ops->get_batch_mode)
+		return cipher->ops->get_batch_mode(context);
+	else {
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "get_batch_mode operation\n", cipher->name);
+		return -1;
+	}
+}
+
+size_t
+purple_cipher_context_get_block_size(PurpleCipherContext *context)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_val_if_fail(context, -1);
+
+	cipher = context->cipher;
+	g_return_val_if_fail(cipher, -1);
+
+	if(cipher->ops && cipher->ops->get_block_size)
+		return cipher->ops->get_block_size(context);
+	else {
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "get_block_size operation\n", cipher->name);
+		return -1;
+	}
+}
+
+void
+purple_cipher_context_set_key_with_len(PurpleCipherContext *context,
+                                       const guchar *key, size_t len)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_if_fail(context);
+
+	cipher = context->cipher;
+	g_return_if_fail(cipher);
+
+	if(cipher->ops && cipher->ops->set_key_with_len)
+		cipher->ops->set_key_with_len(context, key, len);
+	else
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "set_key_with_len operation\n", cipher->name);
+}
+
+void
 purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data) {
 	g_return_if_fail(context);
 
--- a/libpurple/cipher.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/cipher.h	Wed Jan 02 22:15:06 2008 +0000
@@ -37,26 +37,37 @@
 typedef struct _PurpleCipherOps		PurpleCipherOps;		/**< Ops for a PurpleCipher		*/
 typedef struct _PurpleCipherContext	PurpleCipherContext;	/**< A context for a PurpleCipher	*/
 
+/**
+ * Modes for batch encrypters
+ */
+typedef enum _PurpleCipherBatchMode {
+	PURPLE_CIPHER_BATCH_MODE_ECB,
+	PURPLE_CIPHER_BATCH_MODE_CBC
+} PurpleCipherBatchMode;
 
 /**
  * The operation flags for a cipher
  */
 typedef enum _PurpleCipherCaps {
-	PURPLE_CIPHER_CAPS_SET_OPT			= 1 << 1,		/**< Set option flag	*/
-	PURPLE_CIPHER_CAPS_GET_OPT			= 1 << 2,		/**< Get option flag	*/
-	PURPLE_CIPHER_CAPS_INIT				= 1 << 3,		/**< Init flag			*/
-	PURPLE_CIPHER_CAPS_RESET				= 1 << 4,		/**< Reset flag			*/
-	PURPLE_CIPHER_CAPS_UNINIT				= 1 << 5,		/**< Uninit flag		*/
-	PURPLE_CIPHER_CAPS_SET_IV				= 1 << 6,		/**< Set IV flag		*/
-	PURPLE_CIPHER_CAPS_APPEND				= 1 << 7,		/**< Append flag		*/
-	PURPLE_CIPHER_CAPS_DIGEST				= 1 << 8,		/**< Digest flag		*/
-	PURPLE_CIPHER_CAPS_ENCRYPT			= 1 << 9,		/**< Encrypt flag		*/
-	PURPLE_CIPHER_CAPS_DECRYPT			= 1 << 10,		/**< Decrypt flag		*/
-	PURPLE_CIPHER_CAPS_SET_SALT			= 1 << 11,		/**< Set salt flag		*/
-	PURPLE_CIPHER_CAPS_GET_SALT_SIZE		= 1 << 12,		/**< Get salt size flag	*/
-	PURPLE_CIPHER_CAPS_SET_KEY			= 1 << 13,		/**< Set key flag		*/
-	PURPLE_CIPHER_CAPS_GET_KEY_SIZE		= 1 << 14,		/**< Get key size flag	*/
-	PURPLE_CIPHER_CAPS_UNKNOWN			= 1 << 16		/**< Unknown			*/
+	PURPLE_CIPHER_CAPS_SET_OPT          = 1 << 1,   /**< Set option flag	*/
+	PURPLE_CIPHER_CAPS_GET_OPT          = 1 << 2,   /**< Get option flag	*/
+	PURPLE_CIPHER_CAPS_INIT             = 1 << 3,   /**< Init flag			*/
+	PURPLE_CIPHER_CAPS_RESET            = 1 << 4,   /**< Reset flag			*/
+	PURPLE_CIPHER_CAPS_UNINIT           = 1 << 5,   /**< Uninit flag		*/
+	PURPLE_CIPHER_CAPS_SET_IV           = 1 << 6,   /**< Set IV flag		*/
+	PURPLE_CIPHER_CAPS_APPEND           = 1 << 7,   /**< Append flag		*/
+	PURPLE_CIPHER_CAPS_DIGEST           = 1 << 8,   /**< Digest flag		*/
+	PURPLE_CIPHER_CAPS_ENCRYPT          = 1 << 9,   /**< Encrypt flag		*/
+	PURPLE_CIPHER_CAPS_DECRYPT          = 1 << 10,  /**< Decrypt flag		*/
+	PURPLE_CIPHER_CAPS_SET_SALT         = 1 << 11,  /**< Set salt flag		*/
+	PURPLE_CIPHER_CAPS_GET_SALT_SIZE    = 1 << 12,  /**< Get salt size flag	*/
+	PURPLE_CIPHER_CAPS_SET_KEY          = 1 << 13,  /**< Set key flag		*/
+	PURPLE_CIPHER_CAPS_GET_KEY_SIZE     = 1 << 14,  /**< Get key size flag	*/
+	PURPLE_CIPHER_CAPS_SET_BATCH_MODE   = 1 << 15,  /**< Set batch mode flag */
+	PURPLE_CIPHER_CAPS_GET_BATCH_MODE   = 1 << 16,  /**< Get batch mode flag */
+	PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE   = 1 << 17,  /**< The get block size flag */
+	PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN = 1 << 18,  /**< The set key with length flag */
+	PURPLE_CIPHER_CAPS_UNKNOWN          = 1 << 19   /**< Unknown			*/
 } PurpleCipherCaps;
 
 /**
@@ -105,10 +116,17 @@
 	/** The get key size function */
 	size_t (*get_key_size)(PurpleCipherContext *context);
 
-	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
-	void (*_purple_reserved3)(void);
-	void (*_purple_reserved4)(void);
+	/** The set batch mode function */
+	void (*set_batch_mode)(PurpleCipherContext *context, PurpleCipherBatchMode mode);
+
+	/** The get batch mode function */
+	PurpleCipherBatchMode (*get_batch_mode)(PurpleCipherContext *context);
+
+	/** The get block size function */
+	size_t (*get_block_size)(PurpleCipherContext *context);
+
+	/** The set key with length function */
+	void (*set_key_with_len)(PurpleCipherContext *context, const guchar *key, size_t len);
 };
 
 #ifdef __cplusplus
@@ -345,7 +363,7 @@
 /**
  * Sets the salt on a context
  *
- * @param context The context who's salt to set
+ * @param context The context whose salt to set
  * @param salt    The salt
  */
 void purple_cipher_context_set_salt(PurpleCipherContext *context, guchar *salt);
@@ -353,7 +371,7 @@
 /**
  * Gets the size of the salt if the cipher supports it
  *
- * @param context The context who's salt size to get
+ * @param context The context whose salt size to get
  *
  * @return The size of the salt
  */
@@ -362,7 +380,7 @@
 /**
  * Sets the key on a context
  *
- * @param context The context who's key to set
+ * @param context The context whose key to set
  * @param key     The key
  */
 void purple_cipher_context_set_key(PurpleCipherContext *context, const guchar *key);
@@ -370,16 +388,53 @@
 /**
  * Gets the key size for a context
  *
- * @param context The context who's key size to get
+ * @param context The context whose key size to get
  *
  * @return The size of the key
  */
 size_t purple_cipher_context_get_key_size(PurpleCipherContext *context);
 
 /**
+ * Sets the batch mode of a context
+ *
+ * @param context The context whose batch mode to set
+ * @param mode    The batch mode under which the cipher should operate
+ *
+ */
+void purple_cipher_context_set_batch_mode(PurpleCipherContext *context, PurpleCipherBatchMode mode);
+
+/**
+ * Gets the batch mode of a context
+ *
+ * @param context The context whose batch mode to get
+ *
+ * @return The batch mode under which the cipher is operating
+ */
+PurpleCipherBatchMode purple_cipher_context_get_batch_mode(PurpleCipherContext *context);
+
+/**
+ * Gets the block size of a context
+ *
+ * @param context The context whose block size to get
+ *
+ * @return The block size of the context
+ */
+size_t purple_cipher_context_get_block_size(PurpleCipherContext *context);
+
+/**
+ * Sets the key with a given length on a context 
+ *
+ * @param context The context whose key to set
+ * @param key     The key
+ * @param len     The length of the key
+ *
+ */
+void purple_cipher_context_set_key_with_len(PurpleCipherContext *context, const guchar *key, size_t len);
+
+/**
  * Sets the cipher data for a context
  *
- * @param context The context who's cipher data to set
+ * @param context The context whose cipher data to set
  * @param data    The cipher data to set
  */
 void purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data);
@@ -387,7 +442,7 @@
 /**
  * Gets the cipher data for a context
  *
- * @param context The context who's cipher data to get
+ * @param context The context whose cipher data to get
  *
  * @return The cipher data
  */
--- a/libpurple/cmds.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/cmds.h	Wed Jan 02 22:15:06 2008 +0000
@@ -30,25 +30,20 @@
 /**************************************************************************/
 /*@{*/
 
-typedef enum _PurpleCmdPriority PurpleCmdPriority;
-typedef enum _PurpleCmdFlag     PurpleCmdFlag;
-typedef enum _PurpleCmdStatus   PurpleCmdStatus;
-typedef enum _PurpleCmdRet      PurpleCmdRet;
-
-enum _PurpleCmdStatus {
+typedef enum _PurpleCmdStatus {
 	PURPLE_CMD_STATUS_OK,
 	PURPLE_CMD_STATUS_FAILED,
 	PURPLE_CMD_STATUS_NOT_FOUND,
 	PURPLE_CMD_STATUS_WRONG_ARGS,
 	PURPLE_CMD_STATUS_WRONG_PRPL,
 	PURPLE_CMD_STATUS_WRONG_TYPE,
-};
+} PurpleCmdStatus;
 
-enum _PurpleCmdRet {
+typedef enum _PurpleCmdRet {
 	PURPLE_CMD_RET_OK,       /**< Everything's okay. Don't look for another command to call. */
 	PURPLE_CMD_RET_FAILED,   /**< The command failed, but stop looking.*/
 	PURPLE_CMD_RET_CONTINUE, /**< Continue, looking for other commands with the same name to call. */
-};
+} PurpleCmdRet;
 
 #define PURPLE_CMD_FUNC(func) ((PurpleCmdFunc)func)
 
@@ -56,7 +51,7 @@
                                   gchar **args, gchar **error, void *data);
 typedef guint PurpleCmdId;
 
-enum _PurpleCmdPriority {
+typedef enum _PurpleCmdPriority {
 	PURPLE_CMD_P_VERY_LOW  = -1000,
 	PURPLE_CMD_P_LOW       =     0,
 	PURPLE_CMD_P_DEFAULT   =  1000,
@@ -65,7 +60,7 @@
 	PURPLE_CMD_P_ALIAS     =  4000,
 	PURPLE_CMD_P_HIGH      =  5000,
 	PURPLE_CMD_P_VERY_HIGH =  6000,
-};
+} PurpleCmdPriority;
 
 /** Flags used to set various properties of commands.  Every command should
  *  have at least one of #PURPLE_CMD_FLAG_IM and #PURPLE_CMD_FLAG_CHAT set in
@@ -73,7 +68,7 @@
  *
  *  @see purple_cmd_register
  */
-enum _PurpleCmdFlag {
+typedef enum _PurpleCmdFlag {
 	/** Command is usable in IMs. */
 	PURPLE_CMD_FLAG_IM               = 0x01,
 	/** Command is usable in multi-user chats. */
@@ -82,7 +77,7 @@
 	PURPLE_CMD_FLAG_PRPL_ONLY        = 0x04,
 	/** Incorrect arguments to this command should be accepted anyway. */
 	PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS = 0x08,
-};
+} PurpleCmdFlag;
 
 
 /*@}*/
--- a/libpurple/connection.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/connection.h	Wed Jan 02 22:15:06 2008 +0000
@@ -196,11 +196,11 @@
 	 *  available; on Windows, it uses Win32's network change notification
 	 *  infrastructure.
 	 */
-	void (*network_connected)();
+	void (*network_connected)(void);
 	/** Called when libpurple discovers that the computer's network
 	 *  connection has gone away.
 	 */
-	void (*network_disconnected)();
+	void (*network_disconnected)(void);
 
 	/** Called when an error causes a connection to be disconnected.
 	 *  Called before #disconnected.  This op is intended to replace
--- a/libpurple/conversation.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/conversation.h	Wed Jan 02 22:15:06 2008 +0000
@@ -490,7 +490,8 @@
  *
  * @param conv The conversation.
  *
- * @return The conversation's name.
+ * @return The conversation's name. If the conversation is an IM with a PurpleBuddy,
+ *         then it's the name of the PurpleBuddy.
  */
 const char *purple_conversation_get_name(const PurpleConversation *conv);
 
@@ -718,7 +719,7 @@
  *
  * @param msg   A PurpleConvMessage
  *
- * @return   The name of the sender of the message
+ * @return   The message flags
  *
  * @since 2.2.0
  */
@@ -729,7 +730,7 @@
  *
  * @param msg   A PurpleConvMessage
  *
- * @return   The name of the sender of the message
+ * @return   The timestamp of the message
  *
  * @since 2.2.0
  */
--- a/libpurple/plugin.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/plugin.c	Wed Jan 02 22:15:06 2008 +0000
@@ -667,7 +667,10 @@
 			}
 			else
 			{
+#if 0
+				/* This isn't necessary. This has already been done when unloading dep_plugin. */
 				plugin->dependent_plugins = g_list_delete_link(plugin->dependent_plugins, l);
+#endif
 			}
 		}
 	}
--- a/libpurple/prefs.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/prefs.c	Wed Jan 02 22:15:06 2008 +0000
@@ -438,19 +438,6 @@
 	g_free(filename);
 	prefs_loaded = TRUE;
 
-	/* I introduced a bug in 2.0.0beta2.  This fixes the broken
-	 * scores on upgrade.  This can be removed sometime shortly
-	 * after 2.0.0 final is released. -- rlaager */
-	if (purple_prefs_get_int("/purple/status/scores/offline") == -500 &&
-	    purple_prefs_get_int("/purple/status/scores/available") == 100 &&
-	    purple_prefs_get_int("/purple/status/scores/invisible") == -50 &&
-	    purple_prefs_get_int("/purple/status/scores/away") == -100 &&
-	    purple_prefs_get_int("/purple/status/scores/extended_away") == -200 &&
-	    purple_prefs_get_int("/purple/status/scores/idle") == -400)
-	{
-		purple_prefs_set_int("/purple/status/scores/idle", -10);
-	}
-
 	return TRUE;
 }
 
--- a/libpurple/protocols/irc/cmds.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/irc/cmds.c	Wed Jan 02 22:15:06 2008 +0000
@@ -367,7 +367,12 @@
 		if (!end)
 			end = cur + strlen(cur);
 		msg = g_strndup(cur, end - cur);
-		buf = irc_format(irc, "vt:", "PRIVMSG", args[0], msg);
+
+		if(!strcmp(cmd, "msg"))
+			buf = irc_format(irc, "vt:", "PRIVMSG", args[0], msg);
+		else /* seding a notice if we get here */
+			buf = irc_format(irc, "vt:", "NOTICE", args[0], msg);
+
 		irc_send(irc, buf);
 		g_free(msg);
 		g_free(buf);
--- a/libpurple/protocols/irc/parse.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/irc/parse.c	Wed Jan 02 22:15:06 2008 +0000
@@ -136,6 +136,7 @@
 	{ "names", "c", irc_cmd_names, N_("names [channel]:  List the users currently in a channel.") },
 	{ "nick", "n", irc_cmd_nick, N_("nick &lt;new nickname&gt;:  Change your nickname.") },
 	{ "nickserv", ":", irc_cmd_service, N_("nickserv: Send a command to nickserv") },
+	{ "notice", "t:", irc_cmd_privmsg, N_("notice &lt;target&lt;:  Send a notice to a user or channel.") },
 	{ "op", ":", irc_cmd_op, N_("op &lt;nick1&gt; [nick2] ...:  Grant channel operator status to someone. You must be a channel operator to do this.") },
 	{ "operwall", ":", irc_cmd_wallops, N_("operwall &lt;message&gt;:  If you don't know what this is, you probably can't use it.") },
 	{ "operserv", ":", irc_cmd_service, N_("operserv: Send a command to operserv") },
--- a/libpurple/protocols/jabber/auth.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Wed Jan 02 22:15:06 2008 +0000
@@ -690,6 +690,10 @@
 			char h[17], *p;
 			int i;
 
+			challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge");
+			auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), digest);
+
+			/* Create the response query */
 			iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
 			query = xmlnode_get_child(iq->node, "query");
 
@@ -699,8 +703,6 @@
 			xmlnode_insert_data(x, js->user->resource, -1);
 
 			x = xmlnode_new_child(query, "crammd5");
-			challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge");
-			auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), &digest);
 
 			/* Translate the digest to a hexadecimal notation */
 			p = h;
@@ -1075,10 +1077,12 @@
 		}
 	}
 	/* If we've negotiated a security layer, we need to enable it */
-	sasl_getprop(js->sasl, SASL_SSF, &x);
-	if (*(int *)x > 0) {
-		sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x);
-		js->sasl_maxbuf = *(int *)x;
+	if (js->sasl) {
+		sasl_getprop(js->sasl, SASL_SSF, &x);
+		if (*(int *)x > 0) {
+			sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x);
+			js->sasl_maxbuf = *(int *)x;
+		}
 	}
 #endif
 
--- a/libpurple/protocols/jabber/jabber.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Jan 02 22:15:06 2008 +0000
@@ -390,26 +390,29 @@
 
 static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer timeout) 
 {
-	g_source_remove(GPOINTER_TO_INT(timeout));
+	purple_timeout_remove(GPOINTER_TO_INT(timeout));
+	js->keepalive_timeout = -1;
 }
 
 static gboolean jabber_pong_timeout(PurpleConnection *gc)
 {
+	JabberStream *js = gc->proto_data;
 	purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 					_("Ping timeout"));
+	js->keepalive_timeout = -1;
 	return FALSE;
 }
 
 void jabber_keepalive(PurpleConnection *gc)
 {
-	JabberIq *iq = jabber_iq_new(gc->proto_data, JABBER_IQ_GET);
-	guint timeout;
+	JabberStream *js = gc->proto_data;
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
 
-        xmlnode *ping = xmlnode_new_child(iq->node, "ping");
-        xmlnode_set_namespace(ping, "urn:xmpp:ping");
+	xmlnode *ping = xmlnode_new_child(iq->node, "ping");
+	xmlnode_set_namespace(ping, "urn:xmpp:ping");
 
-	timeout = purple_timeout_add_seconds(20, (GSourceFunc)(jabber_pong_timeout), gc);
-        jabber_iq_set_callback(iq, jabber_pong_cb, GINT_TO_POINTER(timeout));
+	js->keepalive_timeout = purple_timeout_add_seconds(20, (GSourceFunc)(jabber_pong_timeout), gc);
+	jabber_iq_set_callback(iq, jabber_pong_cb, GINT_TO_POINTER(js->keepalive_timeout));
 	jabber_iq_send(iq);
 }
 
@@ -612,6 +615,7 @@
 	js->next_id = g_random_int();
 	js->write_buffer = purple_circ_buffer_new(512);
 	js->old_length = -1;
+	js->keepalive_timeout = -1;
 
 	if(!js->user) {
 		purple_connection_error_reason (gc,
@@ -1311,6 +1315,9 @@
 	g_free(js->old_uri);
 	g_free(js->old_track);
 
+	if (js->keepalive_timeout != -1)
+		purple_timeout_remove(js->keepalive_timeout);
+	
 	g_free(js);
 
 	gc->proto_data = NULL;
--- a/libpurple/protocols/jabber/jabber.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed Jan 02 22:15:06 2008 +0000
@@ -193,6 +193,9 @@
 	char *old_track;
 	
 	char *host;
+	
+	/* A purple timeout tag for the keepalive */
+	int keepalive_timeout;
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/msn/soap2.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/msn/soap2.c	Wed Jan 02 22:15:06 2008 +0000
@@ -170,6 +170,9 @@
 {
 	MsnSoapConnection *conn = data;
 
+	/* sslconn already frees the connection in case of error */
+	conn->ssl = NULL;
+
 	g_hash_table_remove(conn->session->soap_table, conn->host);
 }
 
--- a/libpurple/protocols/myspace/myspace.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Wed Jan 02 22:15:06 2008 +0000
@@ -2912,8 +2912,7 @@
 	menu = g_list_append(menu, act);
 #endif
 
-	act = purple_plugin_action_new(g_strdup_printf("%s",
-				_("Add friends from MySpace.com")), msim_import_friends);
+	act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends);
 	menu = g_list_append(menu, act);
 
 	return menu;
--- a/libpurple/protocols/qq/group_opt.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Wed Jan 02 22:15:06 2008 +0000
@@ -39,37 +39,12 @@
 #include "packet_parse.h"
 #include "utils.h"
 
-/* TODO: can't we use qsort here? */
-/* This implement quick sort algorithm (low->high) */
-static void _quick_sort(gint *numbers, gint left, gint right)
+static int _compare_guint32(const void *a,
+                            const void *b)
 {
-	gint pivot, l_hold, r_hold;
-
-	l_hold = left;
-	r_hold = right;
-	pivot = numbers[left];
-	while (left < right) {
-		while ((numbers[right] >= pivot) && (left < right))
-			right--;
-		if (left != right) {
-			numbers[left] = numbers[right];
-			left++;
-		}
-		while ((numbers[left] <= pivot) && (left < right))
-			left++;
-		if (left != right) {
-			numbers[right] = numbers[left];
-			right--;
-		}
-	}
-	numbers[left] = pivot;
-	pivot = left;
-	left = l_hold;
-	right = r_hold;
-	if (left < pivot)
-		_quick_sort(numbers, left, pivot - 1);
-	if (right > pivot)
-		_quick_sort(numbers, pivot + 1, right);
+	const guint32 *x = a;
+	const guint32 *y = b;
+	return (*x - *y);
 }
 
 static void _sort(guint32 *list)
@@ -77,7 +52,7 @@
 	gint i;
 	for (i = 0; list[i] < 0xffffffff; i++) {;
 	}
-	_quick_sort((gint *) list, 0, i - 1);
+	qsort (list, i, sizeof (guint32), _compare_guint32);
 }
 
 static void _qq_group_member_opt(PurpleConnection *gc, qq_group *group, gint operation, guint32 *members)
--- a/libpurple/request.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/request.c	Wed Jan 02 22:15:06 2008 +0000
@@ -1209,7 +1209,6 @@
 	g_return_val_if_fail(ok_text != NULL,  NULL);
 	g_return_val_if_fail(ok_cb   != NULL,  NULL);
 	g_return_val_if_fail(cancel_text != NULL,  NULL);
-	g_return_val_if_fail(cancel_cb   != NULL,  NULL);
 
 	ops = purple_request_get_ui_ops();
 
@@ -1299,7 +1298,6 @@
 	g_return_val_if_fail(ok_text != NULL, NULL);
 	g_return_val_if_fail(ok_cb   != NULL, NULL);
 	g_return_val_if_fail(cancel_text != NULL, NULL);
-	g_return_val_if_fail(cancel_cb   != NULL, NULL);
 
 	ops = purple_request_get_ui_ops();
 
--- a/libpurple/request.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/request.h	Wed Jan 02 22:15:06 2008 +0000
@@ -1203,11 +1203,11 @@
  *                      NULL.
  * @param cancel_text   The text for the @c Cancel button, which may not be @c
  *                      NULL.
- * @param cancel_cb     The callback for the @c Cancel button, which may not be
+ * @param cancel_cb     The callback for the @c Cancel button, which may be
  *                      @c NULL.
  * @param account       The #PurpleAccount associated with this request, or @c
  *                      NULL if none is.
- * @param who           The username of the buddy assonciated with this request,
+ * @param who           The username of the buddy associated with this request,
  *                      or @c NULL if none is.
  * @param conv          The #PurpleConversation associated with this request, or
  *                      @c NULL if none is.
@@ -1241,11 +1241,11 @@
  *                      NULL.
  * @param cancel_text   The text for the @c Cancel button, which may not be @c
  *                      NULL.
- * @param cancel_cb     The callback for the @c Cancel button, which may not be
- *                      @c NULL.
+ * @param cancel_cb     The callback for the @c Cancel button, or @c NULL to
+ *                      do nothing.
  * @param account       The #PurpleAccount associated with this request, or @c
  *                      NULL if none is.
- * @param who           The username of the buddy assonciated with this request,
+ * @param who           The username of the buddy associated with this request,
  *                      or @c NULL if none is.
  * @param conv          The #PurpleConversation associated with this request, or
  *                      @c NULL if none is.
@@ -1281,11 +1281,11 @@
  *                      NULL.
  * @param cancel_text   The text for the @c Cancel button, which may not be @c
  *                      NULL.
- * @param cancel_cb     The callback for the @c Cancel button, which may not be
- *                      @c NULL.
+ * @param cancel_cb     The callback for the @c Cancel button, or @c NULL to do
+ *                      nothing.
  * @param account       The #PurpleAccount associated with this request, or @c
  *                      NULL if none is
- * @param who           The username of the buddy assonciated with this request,
+ * @param who           The username of the buddy associated with this request,
  *                      or @c NULL if none is
  * @param conv          The #PurpleConversation associated with this request, or
  *                      @c NULL if none is
@@ -1320,7 +1320,7 @@
  *                       supplied should be the default, supply <tt>2</tt>.
  * @param account        The #PurpleAccount associated with this request, or @c
  *                       NULL if none is.
- * @param who            The username of the buddy assonciated with this request,
+ * @param who            The username of the buddy associated with this request,
  *                       or @c NULL if none is.
  * @param conv           The #PurpleConversation associated with this request, or
  *                       @c NULL if none is.
@@ -1358,7 +1358,7 @@
  *                       supplied should be the default, supply <tt>2</tt>.
  * @param account        The #PurpleAccount associated with this request, or @c
  *                       NULL if none is.
- * @param who            The username of the buddy assonciated with this request,
+ * @param who            The username of the buddy associated with this request,
  *                       or @c NULL if none is.
  * @param conv           The #PurpleConversation associated with this request, or
  *                       @c NULL if none is.
@@ -1396,11 +1396,11 @@
  *                    NULL.
  * @param cancel_text The text for the @c Cancel button, which may not be @c
  *                    NULL.
- * @param cancel_cb   The callback for the @c Cancel button, which may not be
+ * @param cancel_cb   The callback for the @c Cancel button, which may be
  *                    @c NULL.
  * @param account     The #PurpleAccount associated with this request, or @c
  *                    NULL if none is
- * @param who         The username of the buddy assonciated with this request,
+ * @param who         The username of the buddy associated with this request,
  *                    or @c NULL if none is
  * @param conv        The #PurpleConversation associated with this request, or
  *                    @c NULL if none is
@@ -1476,10 +1476,10 @@
  * @param savedialog  True if this dialog is being used to save a file.
  *                    False if it is being used to open a file.
  * @param ok_cb       The callback for the @c OK button.
- * @param cancel_cb   The callback for the @c Cancel button.
+ * @param cancel_cb   The callback for the @c Cancel button, which may be @c NULL.
  * @param account     The #PurpleAccount associated with this request, or @c
  *                    NULL if none is
- * @param who         The username of the buddy assonciated with this request,
+ * @param who         The username of the buddy associated with this request,
  *                    or @c NULL if none is
  * @param conv        The #PurpleConversation associated with this request, or
  *                    @c NULL if none is
@@ -1503,10 +1503,10 @@
  *                    no title.
  * @param dirname     The default directory name (may be @c NULL)
  * @param ok_cb       The callback for the @c OK button.
- * @param cancel_cb   The callback for the @c Cancel button.
+ * @param cancel_cb   The callback for the @c Cancel button, which may be @c NULL.
  * @param account     The #PurpleAccount associated with this request, or @c
  *                    NULL if none is
- * @param who         The username of the buddy assonciated with this request,
+ * @param who         The username of the buddy associated with this request,
  *                    or @c NULL if none is
  * @param conv        The #PurpleConversation associated with this request, or
  *                    @c NULL if none is
--- a/libpurple/signals.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/signals.h	Wed Jan 02 22:15:06 2008 +0000
@@ -44,11 +44,24 @@
 /**************************************************************************/
 /*@{*/
 
-/**
- * Signal Connect Priorities
+/** The priority of a signal connected using purple_signal_connect().
+ *
+ *  @see purple_signal_connect_priority()
  */
 #define PURPLE_SIGNAL_PRIORITY_DEFAULT     0
+/** The largest signal priority; signals with this priority will be called
+ *  <em>last</em>.  (This is highest as in numerical value, not as in order of
+ *  importance.)
+ *
+ *  @see purple_signal_connect_priority().
+ */
 #define PURPLE_SIGNAL_PRIORITY_HIGHEST  9999
+/** The smallest signal priority; signals with this priority will be called
+ *  <em>first</em>.  (This is lowest as in numerical value, not as in order of
+ *  importance.)
+ *
+ *  @see purple_signal_connect_priority().
+ */
 #define PURPLE_SIGNAL_PRIORITY_LOWEST  -9999
 
 /**
@@ -109,19 +122,21 @@
  * @param handle   The handle of the receiver.
  * @param func     The callback function.
  * @param data     The data to pass to the callback function.
- * @param priority The priority with which the handler should be called. Signal handlers are called
- *                 in order from PURPLE_SIGNAL_PRIORITY_LOWEST to PURPLE_SIGNAL_PRIORITY_HIGHEST.
+ * @param priority The priority with which the handler should be called. Signal
+ *                 handlers are called in ascending numerical order of @a
+ *                 priority from #PURPLE_SIGNAL_PRIORITY_LOWEST to
+ *                 #PURPLE_SIGNAL_PRIORITY_HIGHEST.
  *
  * @return The signal handler ID.
  *
  * @see purple_signal_disconnect()
  */
 gulong purple_signal_connect_priority(void *instance, const char *signal,
-				   void *handle, PurpleCallback func, void *data, int priority);
+	void *handle, PurpleCallback func, void *data, int priority);
 
 /**
  * Connects a signal handler to a signal for a particular object.
- * (priority defaults to 0)
+ * (Its priority defaults to 0, aka #PURPLE_SIGNAL_PRIORITY_DEFAULT.)
  * 
  * Take care not to register a handler function twice. Purple will
  * not correct any mistakes for you in this area.
@@ -137,7 +152,7 @@
  * @see purple_signal_disconnect()
  */
 gulong purple_signal_connect(void *instance, const char *signal,
-						   void *handle, PurpleCallback func, void *data);
+	void *handle, PurpleCallback func, void *data);
 
 /**
  * Connects a signal handler to a signal for a particular object.
@@ -153,18 +168,22 @@
  * @param handle   The handle of the receiver.
  * @param func     The callback function.
  * @param data     The data to pass to the callback function.
- * @param priority The order in which the signal should be added to the list
+ * @param priority The priority with which the handler should be called. Signal
+ *                 handlers are called in ascending numerical order of @a
+ *                 priority from #PURPLE_SIGNAL_PRIORITY_LOWEST to
+ *                 #PURPLE_SIGNAL_PRIORITY_HIGHEST.
  *
  * @return The signal handler ID.
  *
  * @see purple_signal_disconnect()
  */
 gulong purple_signal_connect_priority_vargs(void *instance, const char *signal,
-					void *handle, PurpleCallback func, void *data, int priority);
+	void *handle, PurpleCallback func, void *data, int priority);
 
 /**
  * Connects a signal handler to a signal for a particular object.
- * (priority defaults to 0)
+ * (Its priority defaults to 0, aka #PURPLE_SIGNAL_PRIORITY_DEFAULT.)
+ *
  * The signal handler will take a va_args of arguments, instead of
  * individual arguments.
  *
@@ -182,7 +201,7 @@
  * @see purple_signal_disconnect()
  */
 gulong purple_signal_connect_vargs(void *instance, const char *signal,
-								 void *handle, PurpleCallback func, void *data);
+	void *handle, PurpleCallback func, void *data);
 
 /**
  * Disconnects a signal handler from a signal on an object.
--- a/libpurple/tests/test_cipher.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/tests/test_cipher.c	Wed Jan 02 22:15:06 2008 +0000
@@ -190,6 +190,511 @@
 END_TEST
 
 /******************************************************************************
+ * DES Tests
+ *****************************************************************************/
+#define DES_TEST(in, keyz, out, len) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	guchar answer[len+1]; \
+	gint ret = 0; \
+	guchar decrypt[len+1] = in; \
+	guchar key[8+1] = keyz;\
+	guchar encrypt[len+1] = out;\
+	size_t outlen; \
+	\
+	cipher = purple_ciphers_find_cipher("des"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_key(context, key); \
+	\
+	ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \
+	\
+	ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+START_TEST(test_des_12345678) {
+	DES_TEST("12345678",
+	         "\x3b\x38\x98\x37\x15\x20\xf7\x5e",
+	         "\x06\x22\x05\xac\x6a\x0d\x55\xdd",
+	         8);
+}
+END_TEST
+
+START_TEST(test_des_abcdefgh) {
+	DES_TEST("abcdefgh",
+	         "\x3b\x38\x98\x37\x15\x20\xf7\x5e",
+	         "\x62\xe0\xc6\x8c\x48\xe4\x75\xed",
+	         8);
+}
+END_TEST
+
+/******************************************************************************
+ * DES3 Tests
+ * See http://csrc.nist.gov/groups/ST/toolkit/examples.html
+ * and some NULL things I made up
+ *****************************************************************************/
+
+#define DES3_TEST(in, key, iv, out, len, mode) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	guchar answer[len+1]; \
+	guchar decrypt[len+1] = in; \
+	guchar encrypt[len+1] = out; \
+	size_t outlen; \
+	gint ret = 0; \
+	\
+	cipher = purple_ciphers_find_cipher("des3"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_key(context, (guchar *)key); \
+	purple_cipher_context_set_batch_mode(context, (mode)); \
+	purple_cipher_context_set_iv(context, (guchar *)iv, 8); \
+	\
+	ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \
+	\
+	ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+START_TEST(test_des3_ecb_nist1) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x45\x67\x89\xAB\xCD\xEF\x01\x23",
+	          "00000000", /* ignored */
+	          "\x71\x47\x72\xF3\x39\x84\x1D\x34\x26\x7F\xCC\x4B\xD2\x94\x9C\xC3"
+	          "\xEE\x11\xC2\x2A\x57\x6A\x30\x38\x76\x18\x3F\x99\xC0\xB6\xDE\x87",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_nist2) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF",
+	          "00000000", /* ignored */
+	          "\x06\xED\xE3\xD8\x28\x84\x09\x0A\xFF\x32\x2C\x19\xF0\x51\x84\x86"
+	          "\x73\x05\x76\x97\x2A\x66\x6E\x58\xB6\xC8\x8C\xF1\x07\x34\x0D\x3D",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_key) {
+	DES3_TEST(
+	          "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca",
+	          "\x38\x00\x88\x6a\xef\xcb\x00\xad"
+	          "\x5d\xe5\x29\x00\x7d\x98\x64\x4c"
+	          "\x86\x00\x7b\xd3\xc7\x00\x7b\x32",
+	          "00000000", /* ignored */
+	          "\xc0\x60\x30\xa1\xb7\x25\x42\x44",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_text) {
+	DES3_TEST(
+	          "\x65\x73\x34\xc1\x19\x00\x79\x65",
+	          "\x32\x64\xda\x10\x13\x6a\xfe\x1e"
+	          "\x37\x54\xd1\x2c\x41\x04\x10\x40"
+	          "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5",
+	          "00000000", /* ignored */
+	          "\xe5\x80\xf6\x12\xf8\x4e\xd9\x6c",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_key_and_text) {
+	DES3_TEST(
+	          "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2",
+	          "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1"
+	          "\x66\x49\x42\x63\x22\x00\xf0\x99"
+	          "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f",
+	          "00000000", /* ignored */
+	          "\x73\xd8\x1f\x1f\x50\x01\xe4\x79",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_nist1) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x45\x67\x89\xAB\xCD\xEF\x01\x23",
+	          "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17",
+	          "\x20\x79\xC3\xD5\x3A\xA7\x63\xE1\x93\xB7\x9E\x25\x69\xAB\x52\x62"
+	          "\x51\x65\x70\x48\x1F\x25\xB5\x0F\x73\xC0\xBD\xA8\x5C\x8E\x0D\xA7",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_nist2) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF",
+	          "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17",
+	          "\x74\x01\xCE\x1E\xAB\x6D\x00\x3C\xAF\xF8\x4B\xF4\x7B\x36\xCC\x21"
+	          "\x54\xF0\x23\x8F\x9F\xFE\xCD\x8F\x6A\xCF\x11\x83\x92\xB4\x55\x81",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_key) {
+	DES3_TEST(
+	          "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca",
+	          "\x38\x00\x88\x6a\xef\xcb\x00\xad"
+	          "\x5d\xe5\x29\x00\x7d\x98\x64\x4c"
+	          "\x86\x00\x7b\xd3\xc7\x00\x7b\x32",
+	          "\x31\x32\x33\x34\x35\x36\x37\x38",
+	          "\x52\xe7\xde\x96\x39\x87\x87\xdb",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_text) {
+	DES3_TEST(
+	          "\x65\x73\x34\xc1\x19\x00\x79\x65",
+	          "\x32\x64\xda\x10\x13\x6a\xfe\x1e"
+	          "\x37\x54\xd1\x2c\x41\x04\x10\x40"
+	          "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5",
+	          "\x7C\xAF\x0D\x57\x1E\x57\x10\xDA",
+	          "\x40\x12\x0e\x00\x85\xff\x6c\xc2",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_key_and_text) {
+	DES3_TEST(
+	          "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2",
+	          "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1"
+	          "\x66\x49\x42\x63\x22\x00\xf0\x99"
+	          "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f",
+	          "\x01\x19\x0D\x2c\x40\x67\x89\x67",
+	          "\xa7\xc1\x10\xbe\x9b\xd5\x8a\x67",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+/******************************************************************************
+ * HMAC Tests
+ * See RFC2202 and some other NULL tests I made up
+ *****************************************************************************/
+
+#define HMAC_TEST(data, data_len, key, key_len, type, digest) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	gchar cdigest[41]; \
+	gboolean ret = FALSE; \
+	\
+	cipher = purple_ciphers_find_cipher("hmac"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_option(context, "hash", type); \
+	purple_cipher_context_set_key_with_len(context, (guchar *)key, (key_len)); \
+	\
+	purple_cipher_context_append(context, (guchar *)(data), (data_len)); \
+	ret = purple_cipher_context_digest_to_str(context, sizeof(cdigest), cdigest, \
+	                                        NULL); \
+	\
+	fail_unless(ret == TRUE, NULL); \
+	fail_unless(strcmp((digest), cdigest) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+/* HMAC MD5 */
+
+START_TEST(test_hmac_md5_Hi) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          16,
+	          "md5",
+	          "9294727a3638bb1c13f48ef8158bfc9d");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_what) {
+	HMAC_TEST("what do ya want for nothing?",
+	          28,
+	          "Jefe",
+	          4,
+	          "md5",
+	          "750c783e6ab0b503eaa86e310a5db738");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_dd) {
+	HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
+	          50,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          16,
+	          "md5",
+	          "56be34521d144c88dbb8c733f0e8b3f6");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_cd) {
+	HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
+	          50,
+	          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+	          "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+	          "\x15\x16\x17\x18\x19",
+	          25,
+	          "md5",
+	          "697eaf0aca3a3aea3a75164746ffaa79");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_truncation) {
+	HMAC_TEST("Test With Truncation",
+	          20,
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
+	          16,
+	          "md5",
+	          "56461ef2342edc00f9bab995690efd4c");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_large_key) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First",
+	          54,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "md5",
+	          "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_large_key_and_data) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
+	          73,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "md5",
+	          "6f630fad67cda0ee1fb1f562db3aa53e");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_key) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "md5",
+	          "597bfd644b797a985561eeb03a169e59");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_text) {
+	HMAC_TEST("Hi\x00There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "md5",
+	          "70be8e1b7b50dfcc335d6cd7992c564f");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_key_and_text) {
+	HMAC_TEST("Hi\x00Th\x00re",
+	          8,
+	          "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "md5",
+	          "b31bcbba35a33a067cbba9131cba4889");
+}
+END_TEST
+
+/* HMAC SHA1 */
+
+START_TEST(test_hmac_sha1_Hi) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "sha1",
+	          "b617318655057264e28bc0b6fb378c8ef146be00");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_what) {
+	HMAC_TEST("what do ya want for nothing?",
+	          28,
+	          "Jefe",
+	          4,
+	          "sha1",
+	          "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_dd) {
+	HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
+	          50,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          20,
+	          "sha1",
+	          "125d7342b9ac11cd91a39af48aa17b4f63f175d3");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_cd) {
+	HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
+	          50,
+	          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+	          "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+	          "\x15\x16\x17\x18\x19",
+	          25,
+	          "sha1",
+	          "4c9007f4026250c6bc8414f9bf50c86c2d7235da");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_truncation) {
+	HMAC_TEST("Test With Truncation",
+	          20,
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
+	          20,
+	          "sha1",
+	          "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_large_key) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First",
+	          54,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "sha1",
+	          "aa4ae5e15272d00e95705637ce8a3b55ed402112");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_large_key_and_data) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
+	          73,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "sha1",
+	          "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_key) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "sha1",
+	          "eb62a2e0e33d300be669c52aab3f591bc960aac5");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_text) {
+	HMAC_TEST("Hi\x00There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "sha1",
+	          "31ca58d849e971e418e3439de2c6f83144b6abb7");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_key_and_text) {
+	HMAC_TEST("Hi\x00Th\x00re",
+	          8,
+	          "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "sha1",
+	          "e6b8e2fede87aa09dcb13e554df1435e056eae36");
+}
+END_TEST
+
+/******************************************************************************
  * Suite
  *****************************************************************************/
 Suite *
@@ -227,6 +732,53 @@
 	tcase_add_test(tc, test_sha1_1000_as_1000_times);
 	suite_add_tcase(s, tc);
 
+	/* des tests */
+	tc = tcase_create("DES");
+	tcase_add_test(tc, test_des_12345678);
+	tcase_add_test(tc, test_des_abcdefgh);
+	suite_add_tcase(s, tc);
+
+	/* des3 ecb tests */
+	tc = tcase_create("DES3 ECB");
+	tcase_add_test(tc, test_des3_ecb_nist1);
+	tcase_add_test(tc, test_des3_ecb_nist2);
+	tcase_add_test(tc, test_des3_ecb_null_key);
+	tcase_add_test(tc, test_des3_ecb_null_text);
+	tcase_add_test(tc, test_des3_ecb_null_key_and_text);
+	suite_add_tcase(s, tc);
+	/* des3 cbc tests */
+	tc = tcase_create("DES3 CBC");
+	tcase_add_test(tc, test_des3_cbc_nist1);
+	tcase_add_test(tc, test_des3_cbc_nist2);
+	tcase_add_test(tc, test_des3_cbc_null_key);
+	tcase_add_test(tc, test_des3_cbc_null_text);
+	tcase_add_test(tc, test_des3_cbc_null_key_and_text);
+	suite_add_tcase(s, tc);
+
+	/* hmac tests */
+	tc = tcase_create("HMAC");
+	tcase_add_test(tc, test_hmac_md5_Hi);
+	tcase_add_test(tc, test_hmac_md5_what);
+	tcase_add_test(tc, test_hmac_md5_dd);
+	tcase_add_test(tc, test_hmac_md5_cd);
+	tcase_add_test(tc, test_hmac_md5_truncation);
+	tcase_add_test(tc, test_hmac_md5_large_key);
+	tcase_add_test(tc, test_hmac_md5_large_key_and_data);
+	tcase_add_test(tc, test_hmac_md5_null_key);
+	tcase_add_test(tc, test_hmac_md5_null_text);
+	tcase_add_test(tc, test_hmac_md5_null_key_and_text);
+	tcase_add_test(tc, test_hmac_sha1_Hi);
+	tcase_add_test(tc, test_hmac_sha1_what);
+	tcase_add_test(tc, test_hmac_sha1_dd);
+	tcase_add_test(tc, test_hmac_sha1_cd);
+	tcase_add_test(tc, test_hmac_sha1_truncation);
+	tcase_add_test(tc, test_hmac_sha1_large_key);
+	tcase_add_test(tc, test_hmac_sha1_large_key_and_data);
+	tcase_add_test(tc, test_hmac_sha1_null_key);
+	tcase_add_test(tc, test_hmac_sha1_null_text);
+	tcase_add_test(tc, test_hmac_sha1_null_key_and_text);
+	suite_add_tcase(s, tc);
+
 	return s;
 }
 
--- a/libpurple/util.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/util.h	Wed Jan 02 22:15:06 2008 +0000
@@ -1117,6 +1117,7 @@
  * @param errnum The error code.
  *
  * @return The UTF-8 error message.
+ * @since 2.4.0
  */
 G_CONST_RETURN gchar *purple_gai_strerror(gint errnum);
 
--- a/libpurple/version.h.in	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/version.h.in	Wed Jan 02 22:15:06 2008 +0000
@@ -49,6 +49,34 @@
  */
 const char *purple_version_check(guint required_major, guint required_minor, guint required_micro);
 
+/**
+ * The major version of the running libpurple.  Contrast with
+ * #PURPLE_MAJOR_VERSION, which expands at compile time to the major version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_major_version;
+
+/**
+ * The minor version of the running libpurple.  Contrast with
+ * #PURPLE_MINOR_VERSION, which expands at compile time to the minor version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_minor_version;
+
+/**
+ *
+ * The micro version of the running libpurple.  Contrast with
+ * #PURPLE_MICRO_VERSION, which expands at compile time to the micro version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_micro_version;
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/xmlnode.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/libpurple/xmlnode.c	Wed Jan 02 22:15:06 2008 +0000
@@ -345,6 +345,7 @@
 	g_free(node->name);
 	g_free(node->data);
 	g_free(node->xmlns);
+	g_free(node->prefix);
 
 	if(node->namespace_map)
 		g_hash_table_destroy(node->namespace_map);
@@ -551,6 +552,9 @@
 		g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml);
 	g_free(xml);
 
+	if (len)
+		*len += sizeof("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S) - 1;
+
 	return xml_with_declaration;
 }
 
--- a/pidgin/Makefile.am	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/Makefile.am	Wed Jan 02 22:15:06 2008 +0000
@@ -119,7 +119,8 @@
 	gtkthemes.c \
 	gtkutils.c \
 	gtkwhiteboard.c \
-	minidialog.c
+	minidialog.c \
+	pidgintooltip.c
 
 pidgin_headers = \
 	eggtrayicon.h \
@@ -172,6 +173,7 @@
 	gtkutils.h \
 	gtkwhiteboard.h \
 	minidialog.h \
+	pidgintooltip.h \
 	pidgin.h
 
 pidginincludedir=$(includedir)/pidgin
--- a/pidgin/Makefile.mingw	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/Makefile.mingw	Wed Jan 02 22:15:06 2008 +0000
@@ -95,6 +95,7 @@
 			gtkwhiteboard.c \
 			minidialog.c \
 			pidginstock.c \
+			pidgintooltip.c \
 			win32/MinimizeToTray.c \
 			win32/gtkdocklet-win32.c \
 			win32/gtkwin32dep.c \
--- a/pidgin/gtkaccount.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkaccount.c	Wed Jan 02 22:15:06 2008 +0000
@@ -55,7 +55,6 @@
 	COLUMN_ENABLED,
 	COLUMN_PROTOCOL,
 	COLUMN_DATA,
-	COLUMN_PULSE_DATA,
 	NUM_COLUMNS
 };
 
@@ -139,18 +138,6 @@
 
 } AccountPrefsDialog;
 
-typedef struct
-{
-	GdkPixbuf *online_pixbuf;
-	gboolean pulse_to_grey;
-	float pulse_value;
-	int timeout;
-	PurpleAccount *account;
-	GtkTreeModel *model;
-
-} PidginPulseData;
-
-
 static AccountsWindow *accounts_window = NULL;
 static GHashTable *account_pref_wins;
 
@@ -1121,7 +1108,7 @@
 					 G_CALLBACK(proxy_type_changed_cb), dialog);
 }
 
-static void
+static gboolean
 account_win_destroy_cb(GtkWidget *w, GdkEvent *event,
 					   AccountPrefsDialog *dialog)
 {
@@ -1142,6 +1129,7 @@
 	purple_signals_disconnect_by_handle(dialog);
 
 	g_free(dialog);
+	return FALSE;
 }
 
 static void
@@ -1437,7 +1425,6 @@
 	GtkWidget *win;
 	GtkWidget *main_vbox;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *dbox;
 	GtkWidget *notebook;
 	GtkWidget *button;
@@ -1475,16 +1462,14 @@
 	if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL)
 		dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin);
 
-	dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"),
+	dialog->window = win = pidgin_create_dialog((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"),
 		PIDGIN_HIG_BORDER, "account", FALSE);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(account_win_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	main_vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), main_vbox);
-	gtk_widget_show(main_vbox);
+	main_vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	notebook = gtk_notebook_new();
 	gtk_box_pack_start(GTK_BOX(main_vbox), notebook, FALSE, FALSE, 0);
@@ -1511,8 +1496,6 @@
 	if (!dialog->prpl_info || !dialog->prpl_info->register_user)
 		gtk_widget_hide(button);
 
-
-
 	/* Setup the page with 'Advanced'. */
 	dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER);
@@ -1524,30 +1507,13 @@
 	add_protocol_options(dialog, dbox);
 	add_proxy_options(dialog, dbox);
 
-	/* Setup the button box */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(cancel_account_prefs_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, G_CALLBACK(cancel_account_prefs_cb), dialog);
 
 	/* Save button */
-	button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, G_CALLBACK(ok_account_prefs_cb), dialog);
 	if (dialog->account == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
-	gtk_widget_show(button);
-
 	dialog->ok_button = button;
 
 	/* Set up DND */
@@ -1561,9 +1527,6 @@
 	g_signal_connect(G_OBJECT(dialog->window), "drag_data_received",
 			 G_CALLBACK(account_dnd_recv), dialog);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(ok_account_prefs_cb), dialog);
-
 	/* Show the window. */
 	gtk_widget_show(win);
 }
@@ -1575,7 +1538,6 @@
 signed_on_off_cb(PurpleConnection *gc, gpointer user_data)
 {
 	PurpleAccount *account;
-	PidginPulseData *pulse_data;
 	GtkTreeModel *model;
 	GtkTreeIter iter;
 	GdkPixbuf *pixbuf;
@@ -1591,29 +1553,14 @@
 
 	if (gtk_tree_model_iter_nth_child(model, &iter, NULL, index))
 	{
-		gtk_tree_model_get(GTK_TREE_MODEL(accounts_window->model), &iter,
-						   COLUMN_PULSE_DATA, &pulse_data, -1);
-
-		if (pulse_data != NULL)
-		{
-			if (pulse_data->timeout > 0)
-				g_source_remove(pulse_data->timeout);
-
-			g_object_unref(G_OBJECT(pulse_data->online_pixbuf));
-
-			g_free(pulse_data);
-		}
-
 		pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
 		if ((pixbuf != NULL) && purple_account_is_disconnected(account))
 			gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
 
 		gtk_list_store_set(accounts_window->model, &iter,
 				   COLUMN_ICON, pixbuf,
-				   COLUMN_PULSE_DATA, NULL,
 				   -1);
 
-
 		if (pixbuf != NULL)
 			g_object_unref(G_OBJECT(pixbuf));
 	}
@@ -1812,16 +1759,14 @@
 	}
 }
 
-static gint
+static gboolean
 accedit_win_destroy_cb(GtkWidget *w, GdkEvent *event, AccountsWindow *dialog)
 {
-	/* Since this is called as the window is closing, we don't need
-	 * pidgin_accounts_window_hide() to also dispose of the window */
 	dialog->window = NULL;
 
 	pidgin_accounts_window_hide();
 
-	return 0;
+	return FALSE;
 }
 
 static gboolean
@@ -2155,24 +2100,24 @@
 	GtkTreeViewColumn *column;
 	GtkTreeIter iter;
 	PurpleAccount *account;
-	const gchar *title;
 
 	dialog = (AccountsWindow *)user_data;
 
+	if (event->window != gtk_tree_view_get_bin_window(treeview))
+	    return FALSE;
+
 	/* Figure out which node was clicked */
 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(dialog->treeview), event->x, event->y, &path, &column, NULL, NULL))
 		return FALSE;
-	title = gtk_tree_view_column_get_title(column);
-	/* The -1 is required because the first two columns of the list
-	 * store are displayed as only one column in the tree view. */
-	column = gtk_tree_view_get_column(treeview, COLUMN_ENABLED-1);
+	if (column == gtk_tree_view_get_column(treeview, 0))
+		return FALSE;
+
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
 	gtk_tree_path_free(path);
 	gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter, COLUMN_DATA, &account, -1);
 
 	if ((account != NULL) && (event->button == 1) &&
-		(event->type == GDK_2BUTTON_PRESS) &&
-		(strcmp(gtk_tree_view_column_get_title(column), title)))
+		(event->type == GDK_2BUTTON_PRESS))
 	{
 		pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
 		return TRUE;
@@ -2313,7 +2258,6 @@
 	AccountsWindow *dialog;
 	GtkWidget *win;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *sw;
 	GtkWidget *button;
 	int width, height;
@@ -2328,7 +2272,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -2337,57 +2281,28 @@
 					 G_CALLBACK(configure_cb), accounts_window);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Setup the scrolled window that will contain the list of accounts. */
 	sw = create_accounts_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
 	gtk_widget_show(sw);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(add_account_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(add_account_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(modify_account_cb), dialog);
 	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(modify_account_cb), dialog);
 
 	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(ask_delete_account_cb), dialog);
 	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(ask_delete_account_cb), dialog);
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_accounts_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(close_accounts_cb), dialog);
 
 	purple_signal_connect(pidgin_account_get_handle(), "account-modified",
 	                    accounts_window,
--- a/pidgin/gtkblist.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkblist.c	Wed Jan 02 22:15:06 2008 +0000
@@ -59,6 +59,7 @@
 #include "gtkscrollbook.h"
 #include "gtkutils.h"
 #include "pidgin/minidialog.h"
+#include "pidgin/pidgintooltip.h"
 
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
@@ -153,6 +154,7 @@
 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
+static gboolean buddy_is_displayable(PurpleBuddy *buddy);
 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
@@ -407,6 +409,7 @@
 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
 {
 	editing_blist = FALSE;
+	g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
 	pidgin_blist_refresh(list);
 }
 
@@ -532,12 +535,14 @@
 			if (node_alias && !g_utf8_collate(node_alias, a)) {
 				merges = g_list_append(merges, buddy);
 				i++;
+				g_free(node_alias);
+				break;
 			}
 			g_free(node_alias);
 		}
 	}
 	g_free(a);
-	
+
 	if (i > 1)
 	{
 		char *msg = g_strdup_printf(ngettext("You have %d contact named %s. Would you like to merge them?", "You currently have %d contacts named %s. Would you like to merge them?", i), i, alias);
@@ -2573,28 +2578,37 @@
 
 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
 {
-	char *tooltip_text = NULL;
 	struct tooltip_data *td = g_new0(struct tooltip_data, 1);
 	PurpleAccount *account = NULL;
-	char *tmp, *node_name;
-
-	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+	char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
+
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		account = ((PurpleBuddy*)(node))->account;
-	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 		account = ((PurpleChat*)(node))->account;
 	}
 
 	td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
 	td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
-	td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
+	if (account != NULL) {
+		td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
+	}
 	tooltip_text = pidgin_get_tooltip_text(node, full);
 	td->layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
 	td->name_layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
 
-	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
-	else
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 		tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
+	} else {
+		/* I don't believe this can happen currently, I think
+		 * everything that calls this function checks for one of the
+		 * above node types first. */
+		tmp = g_strdup(_("Unknown node type"));
+	}
 	node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp);
 	g_free(tmp);
 
@@ -2630,7 +2644,8 @@
 	return td;
 }
 
-static void pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, PurpleBlistNode *node)
+static gboolean
+pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
 {
 	GtkStyle *style;
 	int current_height, max_width;
@@ -2638,14 +2653,12 @@
 	int max_avatar_width;
 	GList *l;
 	int prpl_col = 0;
-        GtkTextDirection dir = gtk_widget_get_direction(widget);
+	GtkTextDirection dir = gtk_widget_get_direction(widget);
 
 	if(gtkblist->tooltipdata == NULL)
-		return;
+		return FALSE;
 
 	style = gtkblist->tipwindow->style;
-	gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
-			NULL, gtkblist->tipwindow, "tooltip", 0, 0, -1, -1);
 
 	max_text_width = 0;
 	max_avatar_width = 0;
@@ -2684,14 +2697,16 @@
 		}
 
 #if GTK_CHECK_VERSION(2,2,0)
-		if (dir == GTK_TEXT_DIR_RTL)
-			gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
-					0, 0, max_width - TOOLTIP_BORDER - STATUS_SIZE, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
-		else
-			gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
-					0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
-		if(td->avatar)
-		{
+		if (td->status_icon) {
+			if (dir == GTK_TEXT_DIR_RTL)
+				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
+				                0, 0, max_width - TOOLTIP_BORDER - STATUS_SIZE, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
+			else
+				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
+				                0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
+		}
+
+		if(td->avatar) {
 			if (dir == GTK_TEXT_DIR_RTL)
 				gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
 						td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
@@ -2701,7 +2716,7 @@
 						current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
 		}
 
-		if (!td->avatar_is_prpl_icon)
+		if (!td->avatar_is_prpl_icon && td->prpl_icon)
 			gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
 					0, 0,
 					prpl_col,
@@ -2709,7 +2724,9 @@
 					-1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
 
 #else
-		gdk_pixbuf_render_to_drawable(td->status_icon, GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0, 12, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
+		if (td->status_icon) {
+			gdk_pixbuf_render_to_drawable(td->status_icon, GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0, 12, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
+		}
 		if(td->avatar)
 			gdk_pixbuf_render_to_drawable(td->avatar,
 					GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0,
@@ -2740,10 +2757,11 @@
 
 		current_height += MAX(td->name_height + td->height, td->avatar_height) + TOOLTIP_BORDER;
 	}
-}
-
-
-void pidgin_blist_tooltip_destroy()
+	return FALSE;
+}
+
+static void
+pidgin_blist_destroy_tooltip_data()
 {
 	while(gtkblist->tooltipdata) {
 		struct tooltip_data *td = gtkblist->tooltipdata->data;
@@ -2759,12 +2777,69 @@
 		g_free(td);
 		gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
 	}
-
-	if (gtkblist->tipwindow == NULL)
-		return;
-
-	gtk_widget_destroy(gtkblist->tipwindow);
-	gtkblist->tipwindow = NULL;
+}
+
+void pidgin_blist_tooltip_destroy()
+{
+	pidgin_blist_destroy_tooltip_data();
+	pidgin_tooltip_destroy();
+}
+
+static gboolean
+pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
+{
+	PurpleBlistNode *node = data;
+	int width, height;
+
+	if (gtkblist->tooltipdata) {
+		gtkblist->tipwindow = NULL;
+		pidgin_blist_destroy_tooltip_data();
+	}
+
+	gtkblist->tipwindow = widget;
+	if(PURPLE_BLIST_NODE_IS_CHAT(node) ||
+	   PURPLE_BLIST_NODE_IS_BUDDY(node) ||
+	   PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		struct tooltip_data *td = create_tip_for_node(node, TRUE);
+		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
+		width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE +
+			MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER;
+		height = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height))
+			+ TOOLTIP_BORDER;
+	} else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *child;
+		PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
+		int max_text_width = 0;
+		int max_avatar_width = 0;
+		width = height = 0;
+
+		for(child = node->child; child; child = child->next)
+		{
+			if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
+				struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
+				if (b == (PurpleBuddy *)child) {
+					gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
+				} else {
+					gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
+				}
+				max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
+				max_avatar_width = MAX(max_avatar_width, td->avatar_width);
+				height += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height),
+						TOOLTIP_BORDER + td->height + td->name_height);
+			}
+		}
+		height += TOOLTIP_BORDER;
+		width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
+	} else {
+		return FALSE;
+	}
+
+	if (w)
+		*w = width;
+	if (h)
+		*h = height;
+
+	return TRUE;
 }
 
 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
@@ -2826,164 +2901,9 @@
 			 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
 }
 
-static gboolean pidgin_blist_tooltip_timeout(GtkWidget *tv)
-{
-	GtkTreePath *path;
-	GtkTreeIter iter;
-	PurpleBlistNode *node;
-	GValue val;
-	gboolean editable = FALSE;
-
-	/* If we're editing a cell (e.g. alias editing), don't show the tooltip */
-	g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
-	if (editable)
-		return FALSE;
-
-	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y + (gtkblist->tip_rect.height/2), 
-		&path, NULL, NULL, NULL))
-		return FALSE;
-	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
-	val.g_type = 0;
-	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
-	node = g_value_get_pointer(&val);
-
-	pidgin_blist_draw_tooltip(node, gtkblist->window);
-
-	gtk_tree_path_free(path);
-	return FALSE;
-}
-
 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
 {
-	int scr_w, scr_h, w, h, x, y;
-#if GTK_CHECK_VERSION(2,2,0)
-	int mon_num;
-	GdkScreen *screen = NULL;
-#endif
-	gboolean tooltip_top = FALSE;
-	struct _pidgin_blist_node *gtknode;
-	GdkRectangle mon_size;
-	int sig;
-	const char *name;
-	
-	if (node == NULL)
-		return;
-
-	/*
-	 * Attempt to free the previous tooltip.  I have a feeling
-	 * this is never needed... but just in case.
-	 */
-	pidgin_blist_tooltip_destroy();
-
-	gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
-
-	if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		struct tooltip_data *td = create_tip_for_node(node, TRUE);
-		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
-		w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE +
-			MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER;
-		h = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height))
-			+ TOOLTIP_BORDER;
-	} else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleBlistNode *child;
-		PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
-		int max_text_width = 0;
-		int max_avatar_width = 0;
-		w = h = 0;
-
-		for(child = node->child; child; child = child->next)
-		{
-			if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
-				struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
-				if (b == (PurpleBuddy *)child) {
-					gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
-				} else {
-					gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
-				}
-				max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
-				max_avatar_width = MAX(max_avatar_width, td->avatar_width);
-				h += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height),
-						TOOLTIP_BORDER + td->height + td->name_height);
-			}
-		}
-		h += TOOLTIP_BORDER;
-		w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
-	} else {
-		gtk_widget_destroy(gtkblist->tipwindow);
-		gtkblist->tipwindow = NULL;
-		return;
-	}
-
-	if (gtkblist->tooltipdata == NULL) {
-		gtk_widget_destroy(gtkblist->tipwindow);
-		gtkblist->tipwindow = NULL;
-		return;
-	}
-
-	gtknode = node->ui_data;
-
-	name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
-	gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
-	gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List"));
-	gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
-	gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
-	g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
-			G_CALLBACK(pidgin_blist_paint_tip), NULL);
-	gtk_widget_ensure_style (gtkblist->tipwindow);
-
-#if GTK_CHECK_VERSION(2,2,0)
-	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
-	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
-	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
-
-	scr_w = mon_size.width + mon_size.x;
-	scr_h = mon_size.height + mon_size.y;
-#else
-	scr_w = gdk_screen_width();
-	scr_h = gdk_screen_height();
-	gdk_window_get_pointer(NULL, &x, &y, NULL);
-	mon_size.x = 0;
-	mon_size.y = 0;
-#endif
-
-#if GTK_CHECK_VERSION(2,2,0)
-	if (w > mon_size.width)
-	  w = mon_size.width - 10;
-
-	if (h > mon_size.height)
-	  h = mon_size.height - 10;
-#endif
-
-	x -= ((w >> 1) + 4);
-
-	if ((y + h + 4) > scr_h || tooltip_top)
-		y = y - h - 5;
-	else
-		y = y + 6;
-
-	if (y < mon_size.y)
-		y = mon_size.y;
-
-	if (y != mon_size.y) {
-		if ((x + w) > scr_w)
-			x -= (x + w + 5) - scr_w;
-		else if (x < mon_size.x)
-			x = mon_size.x;
-	} else {
-		x -= (w / 2 + 10);
-		if (x < mon_size.x)
-			x = mon_size.x;
-	}
-
-	gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
-	gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
-	gtk_widget_show(gtkblist->tipwindow);
-
-	/* Hide the tooltip when the widget is destroyed */
-	sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_blist_tooltip_destroy), NULL);
-	g_signal_connect_swapped(G_OBJECT(gtkblist->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
-
-	return;
+	pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
 }
 
 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
@@ -3033,31 +2953,34 @@
 	return FALSE;
 }
 
-static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
-{
-	GtkTreePath *path;
-	int delay;
-
-	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-
-	if (delay == 0)
+static gboolean
+pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
+		gpointer null, int *w, int *h)
+{
+	GtkTreeIter iter;
+	PurpleBlistNode *node;
+	GValue val;
+	gboolean editable = FALSE;
+
+	/* If we're editing a cell (e.g. alias editing), don't show the tooltip */
+	g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
+	if (editable)
 		return FALSE;
 
-	if (gtkblist->timeout) {
-		if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
-			return FALSE;
-		/* We've left the cell.  Remove the timeout and create a new one below */
-		pidgin_blist_tooltip_destroy();
-		g_source_remove(gtkblist->timeout);
-	}
-
-	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
-	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
-
-	if (path)
-		gtk_tree_path_free(path);
-	gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_tooltip_timeout, tv);
-
+	if (gtkblist->tooltipdata) {
+		gtkblist->tipwindow = NULL;
+		pidgin_blist_destroy_tooltip_data();
+	}
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
+	val.g_type = 0;
+	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
+	node = g_value_get_pointer(&val);
+	return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
+}
+
+static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
+{
 	if (gtkblist->mouseover_contact) {
 		if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
@@ -3068,9 +2991,8 @@
 	return FALSE;
 }
 
-static void pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
-{
-
+static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
+{
 	if (gtkblist->timeout) {
 		g_source_remove(gtkblist->timeout);
 		gtkblist->timeout = 0;
@@ -3081,14 +3003,13 @@
 		gtkblist->drag_timeout = 0;
 	}
 
-	pidgin_blist_tooltip_destroy();
-
 	if (gtkblist->mouseover_contact &&
 		!((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
 		 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
 		gtkblist->mouseover_contact = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -3371,10 +3292,44 @@
 		g_free(tmp);
 
 		purple_notify_user_info_destroy(user_info);
-	}
-
-	purple_signal_emit(pidgin_blist_get_handle(),
-			 "drawing-tooltip", node, str, full);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		GSList *accounts;
+		PurpleGroup *group = (PurpleGroup*)node;
+		PurpleNotifyUserInfo *user_info;
+
+		user_info = purple_notify_user_info_new();
+
+		/* Total buddies (from online accounts) in group */
+		tmp = g_strdup_printf("%d",
+		                      purple_blist_get_group_size(group, FALSE));
+		purple_notify_user_info_add_pair(user_info, _("Total Buddies"),
+		                                 tmp);
+		g_free(tmp);
+
+		/* Online buddies in group */
+		tmp = g_strdup_printf("%d",
+		                      purple_blist_get_group_online_count(group));
+		purple_notify_user_info_add_pair(user_info, _("Online Buddies"),
+		                                 tmp);
+		g_free(tmp);
+
+		/* Accounts with buddies in group */
+		accounts = purple_group_get_accounts(group);
+		for (; accounts != NULL;
+		     accounts = g_slist_delete_link(accounts, accounts)) {
+			PurpleAccount *account = accounts->data;
+			purple_notify_user_info_add_pair(user_info, _("Account"), purple_account_get_username(account));
+		}
+
+		tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
+		g_string_append(str, tmp);
+		g_free(tmp);
+
+		purple_notify_user_info_destroy(user_info);
+	}
+
+	purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
+	                   node, str, full);
 
 	return g_string_free(str, FALSE);
 }
@@ -4898,6 +4853,9 @@
 #endif
 
 	gtk_tooltips_force_window (tooltips);
+#if GTK_CHECK_VERSION(2, 12, 0)
+	gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
+#endif
 	gtk_widget_ensure_style (tooltips->tip_window);
 	style = gtk_widget_get_style (tooltips->tip_window);
 
@@ -5082,8 +5040,9 @@
 	gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
 	gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
-	g_signal_connect(gtkblist->headline_hbox,
-			 "style-set",
+	g_signal_connect(gtkblist->headline_label,   /* connecting on headline_hbox doesn't work, because
+	                                                the signal is not emitted when theme is changed */
+			"style-set",
 			 G_CALLBACK(headline_style_set),
 			 NULL);
 	g_signal_connect (gtkblist->headline_hbox,
@@ -5165,12 +5124,14 @@
 #ifdef _WIN32
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
 #endif
-
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
 
 	/* Tooltips */
-	g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
+	pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
+			pidgin_blist_create_tooltip,
+			pidgin_blist_paint_tip);
 
 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
 
--- a/pidgin/gtkcertmgr.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkcertmgr.c	Wed Jan 02 22:15:06 2008 +0000
@@ -430,7 +430,7 @@
 		/* Set up the display columns */
 		renderer = gtk_cell_renderer_text_new();
 		column = gtk_tree_view_column_new_with_attributes(
-			"Hostname",
+			_("Hostname"),
 			renderer,
 			"text", TPM_HOSTNAME_COLUMN,
 			NULL);
@@ -545,12 +545,13 @@
    So if it is set, don't open another one! */
 CertMgrDialog *certmgr_dialog = NULL;
 
-static void
+static gboolean
 certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
 {
 	/* TODO: Ignoring the arguments to this function may not be ideal,
 	   but there *should* only be "one dialog to rule them all" at a time*/
 	pidgin_certmgr_hide();
+	return FALSE;
 }
 
 void
@@ -559,7 +560,6 @@
 	CertMgrDialog *dlg;
 	GtkWidget *win;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 
 	/* Enumerate all the certificates on file */
 	{
@@ -599,7 +599,7 @@
 	dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
 
 	win = dlg->window =
-		pidgin_create_window(_("Certificate Manager"),/* Title */
+		pidgin_create_dialog(_("Certificate Manager"),/* Title */
 				     PIDGIN_HIG_BORDER, /*Window border*/
 				     "certmgr",         /* Role */
 				     TRUE); /* Allow resizing */
@@ -611,9 +611,7 @@
 	gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
 
 	/* Main vbox */
-	vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER );
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Notebook of various certificate managers */
 	dlg->notebook = gtk_notebook_new();
@@ -622,19 +620,9 @@
 			   0);
 	gtk_widget_show(dlg->notebook);
 
-	/* Box for the close button */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Close button */
-	dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0);
-	gtk_widget_show(dlg->closebutton);
-	g_signal_connect(G_OBJECT(dlg->closebutton), "clicked",
-			 G_CALLBACK(certmgr_close_cb), dlg);
+	dlg->closebutton = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
+			G_CALLBACK(certmgr_close_cb), dlg);
 
 	/* Add the defined certificate managers */
 	/* TODO: Find a way of determining whether each is shown or not */
--- a/pidgin/gtkconn.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkconn.c	Wed Jan 02 22:15:06 2008 +0000
@@ -162,11 +162,6 @@
 		if (info != NULL)
 			g_hash_table_remove(auto_reconns, account);
 
-		/*
-		 * TODO: Do we really want to disable the account when it's
-		 * disconnected by wants_to_die?  This happens when you sign
-		 * on from somewhere else, or when you enter an invalid password.
-		 */
 		purple_account_set_enabled(account, PIDGIN_UI, FALSE);
 	}
 
--- a/pidgin/gtkconv.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkconv.c	Wed Jan 02 22:15:06 2008 +0000
@@ -67,6 +67,7 @@
 #include "gtkthemes.h"
 #include "gtkutils.h"
 #include "pidginstock.h"
+#include "pidgintooltip.h"
 
 #include "gtknickcolors.h"
 
@@ -153,6 +154,7 @@
 static void conv_set_unseen(PurpleConversation *gtkconv, PidginUnseenState state);
 static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state);
 static void update_typing_icon(PidginConversation *gtkconv);
+static void update_typing_message(PidginConversation *gtkconv, const char *message);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
 static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data);
@@ -163,8 +165,6 @@
 static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win);
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
 static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv);
-static gboolean pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv);
-static void pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv);
 static void hide_conv(PidginConversation *gtkconv, gboolean closetimer);
 
 static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
@@ -3377,6 +3377,34 @@
 }
 
 static void
+update_typing_message(PidginConversation *gtkconv, const char *message)
+{
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml));
+	GtkTextMark *stmark, *enmark;
+
+	stmark = gtk_text_buffer_get_mark(buffer, "typing-notification-start");
+	enmark = gtk_text_buffer_get_mark(buffer, "typing-notification-end");
+	if (stmark && enmark) {
+		GtkTextIter start, end;
+		gtk_text_buffer_get_iter_at_mark(buffer, &start, stmark);
+		gtk_text_buffer_get_iter_at_mark(buffer, &end, enmark);
+		gtk_text_buffer_delete_mark(buffer, stmark);
+		gtk_text_buffer_delete_mark(buffer, enmark);
+		gtk_text_buffer_delete(buffer, &start, &end);
+	} else if (message && *message == '\n' && !*(message + 1))
+		message = NULL;
+
+	if (message) {
+		GtkTextIter iter;
+		gtk_text_buffer_get_end_iter(buffer, &iter);
+		gtk_text_buffer_create_mark(buffer, "typing-notification-start", &iter, TRUE);
+		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, message, -1, "TYPING-NOTIFICATION", NULL);
+		gtk_text_buffer_get_end_iter(buffer, &iter);
+		gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE);
+	}
+}
+
+static void
 update_typing_icon(PidginConversation *gtkconv)
 {
 	PidginWindow *gtkwin;
@@ -3384,6 +3412,7 @@
 	PurpleConversation *conv = gtkconv->active_conv;
 	char *stock_id;
 	const char *tooltip;
+	char *message = NULL;
 
 	gtkwin = gtkconv->win;
 
@@ -3402,6 +3431,7 @@
 			g_source_remove(gtkconv->u.im->typing_timer);
 			gtkconv->u.im->typing_timer = 0;
 		}
+		update_typing_message(gtkconv, "\n");
 		return;
 	}
 
@@ -3411,9 +3441,11 @@
 		}
 		stock_id = PIDGIN_STOCK_ANIMATION_TYPING1;
 		tooltip = _("User is typing...");
+		message = g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(conv));
 	} else {
 		stock_id = PIDGIN_STOCK_ANIMATION_TYPING5;
 		tooltip = _("User has typed something and stopped");
+		message = g_strdup_printf(_("\n%s has typed something and stopped"), purple_conversation_get_title(conv));
 		if (gtkconv->u.im->typing_timer != 0) {
 			g_source_remove(gtkconv->u.im->typing_timer);
 			gtkconv->u.im->typing_timer = 0;
@@ -3436,6 +3468,8 @@
 	}
 
 	gtk_widget_show(gtkwin->menu.typing_icon);
+	update_typing_message(gtkconv, message);
+	g_free(message);
 }
 
 static gboolean
@@ -4399,6 +4433,36 @@
 	}
 }
 
+static gboolean
+pidgin_conv_userlist_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
+		gpointer userdata, int *w, int *h)
+{
+	PidginConversation *gtkconv = userdata;
+	GtkTreeIter iter;
+	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv->u.chat->list));
+	PurpleConversation *conv = gtkconv->active_conv;
+	PurpleBlistNode *node;
+	PurplePluginProtocolInfo *prpl_info;
+	PurpleAccount *account = purple_conversation_get_account(conv);
+	char *who = NULL;
+
+	if (account->gc == NULL)
+		return FALSE;
+
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
+		return FALSE;
+
+	gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &who, -1);
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
+	node = (PurpleBlistNode*)(purple_find_buddy(conv->account, who));
+	if (node && prpl_info && (prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME))
+		pidgin_blist_draw_tooltip(node, gtkconv->infopane);
+
+	g_free(who);
+	return FALSE;
+}
+
 static void
 setup_chat_userlist(PidginConversation *gtkconv, GtkWidget *hpaned)
 {
@@ -4453,14 +4517,13 @@
 
 	g_signal_connect(G_OBJECT(list), "button_press_event",
 					 G_CALLBACK(right_click_chat_cb), gtkconv);
-	g_signal_connect(G_OBJECT(list), "motion-notify-event",
-					 G_CALLBACK(pidgin_userlist_motion_cb), gtkconv);
-	g_signal_connect(G_OBJECT(list), "leave-notify-event",
-					 G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
 	g_signal_connect(G_OBJECT(list), "popup-menu",
 			 G_CALLBACK(gtkconv_chat_popup_menu_cb), gtkconv);
 	g_signal_connect(G_OBJECT(lbox), "size-allocate", G_CALLBACK(lbox_size_allocate_cb), gtkconv);
 
+	pidgin_tooltip_setup_for_treeview(list, gtkconv,
+			pidgin_conv_userlist_create_tooltip, NULL);
+
 	rend = gtk_cell_renderer_text_new();
 	g_object_set(rend,
 				 "foreground-set", TRUE,
@@ -4496,32 +4559,12 @@
 	gtk_container_add(GTK_CONTAINER(sw), list);
 }
 
-/* Stuff used to display tooltips on the infopane */
-static struct {
-	int timeout;
-	PidginConversation *gtkconv;   /* This is the Pidgin conversation that
-	                                  triggered the tooltip */
-	int userlistx;
-	int userlisty;
-} tooltip;
-
-static void
-reset_tooltip()
-{
-	if (tooltip.timeout != 0) {
-		g_source_remove(tooltip.timeout);
-		tooltip.timeout = 0;
-	}
-	tooltip.gtkconv = NULL;
-}
-
 static gboolean
-pidgin_conv_tooltip_timeout(PidginConversation *gtkconv)
+pidgin_conv_create_tooltip(GtkWidget *tipwindow, gpointer userdata, int *w, int *h)
 {
 	PurpleBlistNode *node = NULL;
 	PurpleConversation *conv;
-
-	g_return_val_if_fail (tooltip.gtkconv == gtkconv, FALSE);
+	PidginConversation *gtkconv = userdata;
 
 	conv = gtkconv->active_conv;
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
@@ -4544,102 +4587,6 @@
 	return FALSE;
 }
 
-static void
-pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv)
-{
-	pidgin_blist_tooltip_destroy();
-	reset_tooltip();
-}
-
-static gboolean 
-pidgin_conv_motion_cb (GtkWidget *infopane, GdkEventMotion *event, PidginConversation *gtkconv)
-{
-	int delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-
-	pidgin_blist_tooltip_destroy();
-	if (delay == 0)
-		return FALSE;
-
-	if (tooltip.timeout != 0)
-		g_source_remove(tooltip.timeout);
-
-	tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_conv_tooltip_timeout, gtkconv);
-	tooltip.gtkconv = gtkconv;
-	return FALSE;
-}
-
-static gboolean
-pidgin_userlist_tooltip_timeout(PidginConversation *gtkconv)
-{
-	PurplePluginProtocolInfo *prpl_info;
-	PurpleConversation *conv = gtkconv->active_conv;
-	PidginChatPane *gtkchat;
-	PurpleBlistNode *node = NULL;
-	PurpleAccount *account;
-	GtkTreePath *path;
-	GtkTreeIter iter;
-	GtkTreeModel *model;
-	GtkTreeViewColumn *column;
-	gchar *who;
-	int x, y;
-
-	gtkchat = gtkconv->u.chat;
-	account = purple_conversation_get_account(conv);
-
-	if (account->gc == NULL)
-		return FALSE;
-
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
-
-	model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
-
-	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat->list),
-								  tooltip.userlistx, tooltip.userlisty, &path, &column, &x, &y))
-		return FALSE;
-
-	gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
-	gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &who, -1);
-
-	node = (PurpleBlistNode*)(purple_find_buddy(conv->account, who));
-	if (node && prpl_info && (prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME))
-		pidgin_blist_draw_tooltip(node, gtkconv->infopane);
-
-	g_free(who);
-	gtk_tree_path_free(path);
-
-
-	return FALSE;
-}
-
-static gboolean
-pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv)
-{
-	PurpleConversation *conv;
-	PurpleAccount *account;
-	int delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-	
-	pidgin_blist_tooltip_destroy();
-	if (delay == 0)
-		return FALSE;
-
-	if (tooltip.timeout != 0)
-		g_source_remove(tooltip.timeout);
-	tooltip.timeout = 0;
-
-	conv = gtkconv->active_conv;
-	account = purple_conversation_get_account(conv);
-
-	if (account->gc == NULL)
-		return FALSE;
-
-	tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_userlist_tooltip_timeout, gtkconv);
-	tooltip.gtkconv = gtkconv;
-	tooltip.userlistx = event->x;
-	tooltip.userlisty = event->y;
-
-	return FALSE;
-}
- 
 static GtkWidget *
 setup_common_pane(PidginConversation *gtkconv)
 {
@@ -4669,10 +4616,8 @@
 	g_signal_connect(G_OBJECT(event_box), "button-press-event",
 	                 G_CALLBACK(infopane_press_cb), gtkconv);
 
-	g_signal_connect(G_OBJECT(event_box), "motion-notify-event", 
-			G_CALLBACK(pidgin_conv_motion_cb), gtkconv);
-	g_signal_connect(G_OBJECT(event_box), "leave-notify-event", 
-			G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
+	pidgin_tooltip_setup_for_widget(event_box, gtkconv,
+		pidgin_conv_create_tooltip, NULL);
 
 	gtkconv->infopane = gtk_cell_view_new();
 	gtkconv->infopane_model = gtk_list_store_new(CONV_NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF);
@@ -5031,6 +4976,13 @@
 	g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received",
 	                 G_CALLBACK(conv_dnd_recv), gtkconv);
 
+	gtk_text_buffer_create_tag(GTK_IMHTML(gtkconv->imhtml)->text_buffer, "TYPING-NOTIFICATION",
+			"foreground", "#888888",
+			"justification", GTK_JUSTIFY_LEFT,  /* XXX: RTL'ify */
+			"weight", PANGO_WEIGHT_BOLD,
+			"scale", PANGO_SCALE_SMALL,
+			NULL);
+
 	/* Setup the container for the tab. */
 	gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	g_object_set_data(G_OBJECT(tab_cont), "PidginConversation", gtkconv);
@@ -5171,9 +5123,6 @@
 		g_source_remove(gtkconv->attach.timer);
 	}
 
-	if (tooltip.gtkconv == gtkconv)
-		reset_tooltip();
-
 	g_free(gtkconv);
 }
 
@@ -5801,6 +5750,7 @@
 		(type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"),
 		account, name, displaying, conv, flags);
 	g_free(displaying);
+	update_typing_message(gtkconv, NULL);
 }
 
 static void
@@ -6881,10 +6831,8 @@
                               GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
 	g_signal_connect(G_OBJECT(event), "button-press-event",
 					 G_CALLBACK(icon_menu), gtkconv);
-	g_signal_connect(G_OBJECT(event), "motion-notify-event",
-			G_CALLBACK(pidgin_conv_motion_cb), gtkconv);
-	g_signal_connect(G_OBJECT(event), "leave-notify-event",
-			G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
+
+	pidgin_tooltip_setup_for_widget(event, gtkconv, pidgin_conv_create_tooltip, NULL);
 	gtk_widget_show(event);
 
 	gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale);
@@ -7873,6 +7821,7 @@
 		/* Set default tab colors */
 		GString *str = g_string_new(NULL);
 		GtkSettings *settings = gtk_settings_get_default();
+		GtkStyle *parent = gtk_rc_get_style_by_paths(settings, "tab-container.tab-label*", NULL, G_TYPE_NONE), *now;
 		struct {
 			const char *stylename;
 			const char *labelname;
@@ -7887,8 +7836,9 @@
 		};
 		int iter;
 		for (iter = 0; styles[iter].stylename; iter++) {
-			if (!gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE))
-				/* Apparently both ACTIVE and NORMAL are required */
+			now = gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE);
+			if (parent == now ||
+					(parent && now && parent->rc_style == now->rc_style)) {
 				g_string_append_printf(str, "style \"%s\" {\n"
 						"fg[ACTIVE] = \"%s\"\n"
 						"}\n"
@@ -7896,6 +7846,7 @@
 						styles[iter].stylename,
 						styles[iter].color,
 						styles[iter].labelname, styles[iter].stylename);
+			}
 		}
 		gtk_rc_parse_string(str->str);
 		g_string_free(str, TRUE);
@@ -8318,7 +8269,7 @@
 	}
 	
 	if (e->button == 3) {
-		/* Right click was pressed. Popup the Send To menu. */
+		/* Right click was pressed. Popup the context menu. */
 		GtkWidget *menu = gtk_menu_new(), *sub;
 		gboolean populated = populate_menu_with_options(menu, gtkconv, TRUE);
 		sub = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv->win->menu.send_to));
@@ -8751,7 +8702,7 @@
 	PurpleConversation *conv = gtkconv->active_conv;
 	const char *text = NULL;
 
-	if (!GTK_WIDGET_VISIBLE(gtkconv->tab_label)) {
+	if (!GTK_WIDGET_VISIBLE(gtkconv->infopane)) {
 		/* There's already an entry for alias. Let's not create another one. */
 		return FALSE;
 	}
@@ -9296,6 +9247,7 @@
 		gtkconv->tabby = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	else
 		gtkconv->tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_widget_set_name(gtkconv->tabby, "tab-container");
 
 	/* select the correct ordering for verticle tabs */
 	if (angle == 90) {
--- a/pidgin/gtkdebug.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkdebug.c	Wed Jan 02 22:15:06 2008 +0000
@@ -686,13 +686,11 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/height");
 
-	PIDGIN_DIALOG(win->window);
+	win->window = pidgin_create_dialog(_("Debug Window"), 0, "debug", TRUE);
 	purple_debug_info("gtkdebug", "Setting dimensions to %d, %d\n",
 					width, height);
 
 	gtk_window_set_default_size(GTK_WINDOW(win->window), width, height);
-	gtk_window_set_role(GTK_WINDOW(win->window), "debug");
-	gtk_window_set_title(GTK_WINDOW(win->window), _("Debug Window"));
 
 	g_signal_connect(G_OBJECT(win->window), "delete_event",
 	                 G_CALLBACK(debug_window_destroy), NULL);
@@ -700,7 +698,7 @@
 	                 G_CALLBACK(configure_cb), win);
 
 	handle = pidgin_debug_get_handle();
-	
+
 #ifdef HAVE_REGEX_H
 	/* the list store for all the messages */
 	win->store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
@@ -716,8 +714,7 @@
 #endif /* HAVE_REGEX_H */
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, 0);
-	gtk_container_add(GTK_CONTAINER(win->window), vbox);
+	vbox = pidgin_dialog_get_vbox(GTK_DIALOG(win->window));
 
 	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/toolbar")) {
 		/* Setup our top button bar thingie. */
--- a/pidgin/gtkdialogs.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkdialogs.c	Wed Jan 02 22:15:06 2008 +0000
@@ -89,6 +89,7 @@
 	{"Megan 'Cae' Schneider",       N_("support/QA"), NULL},
 	{"Evan Schoenberg",		N_("developer"), NULL},
 	{"Kevin 'SimGuy' Stange",	N_("developer & webmaster"),	NULL},
+	{"Will 'resiak' Thompson",	N_("developer"),	NULL},
 	{"Stu 'nosnilmot' Tomlinson",	N_("developer"), NULL},
 	{"Nathan 'faceprint' Walp",		N_("developer"), NULL},
 	{NULL, NULL, NULL}
@@ -98,8 +99,8 @@
 static const struct developer patch_writers[] = {
 	{"Dennis 'EvilDennisR' Ristuccia",	N_("Senior Contributor/QA"),	NULL},
 	{"Peter 'Fmoo' Ruibal",		NULL,	NULL},
+	{"Elliott 'QuLogic' Sales de Andrade",	NULL,	NULL},
 	{"Gabriel 'Nix' Schulhof", 	NULL, 	NULL},
-	{"Will 'resiak' Thompson",	NULL,	NULL},
 	{NULL, NULL, NULL}
 };
 
@@ -336,12 +337,10 @@
 
 void pidgin_dialogs_about()
 {
-	GtkWidget *hbox;
 	GtkWidget *vbox;
 	GtkWidget *logo;
 	GtkWidget *frame;
 	GtkWidget *text;
-	GtkWidget *bbox;
 	GtkWidget *button;
 	GtkTextIter iter;
 	GString *str;
@@ -356,20 +355,12 @@
 		return;
 	}
 
-	PIDGIN_DIALOG(about);
 	tmp = g_strdup_printf(_("About %s"), PIDGIN_NAME);
-	gtk_window_set_title(GTK_WINDOW(about), tmp);
+	about = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "about", TRUE);
 	g_free(tmp);
-	gtk_window_set_role(GTK_WINDOW(about), "about");
-	gtk_window_set_resizable(GTK_WINDOW(about), TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(about), 340, 450);
 
-	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_set_border_width(GTK_CONTAINER(hbox), PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(about), hbox);
-
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(hbox), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(about), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Generate a logo with a version number */
 	logo = gtk_window_new(GTK_WINDOW_TOPLEVEL);
@@ -708,15 +699,9 @@
 	gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), &iter);
 
 	/* Close Button */
-	bbox = gtk_hbutton_box_new();
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(about), GTK_STOCK_CLOSE,
+	                G_CALLBACK(destroy_about), about);
 
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect_swapped(G_OBJECT(button), "clicked",
-							 G_CALLBACK(destroy_about), G_OBJECT(about));
 	g_signal_connect(G_OBJECT(about), "destroy",
 					 G_CALLBACK(destroy_about), G_OBJECT(about));
 
--- a/pidgin/gtkdialogs.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkdialogs.h	Wed Jan 02 22:15:06 2008 +0000
@@ -54,11 +54,10 @@
 
 /* Everything after this should probably be moved elsewhere */
 
-/**
- * Our UI's identifier.
- */
+#ifndef PIDGIN_DISABLE_DEPRECATED
 #define PIDGIN_DIALOG(x)	x = gtk_window_new(GTK_WINDOW_TOPLEVEL); \
 			gtk_window_set_type_hint(GTK_WINDOW(x), GDK_WINDOW_TYPE_HINT_DIALOG)
+#endif
 #define PIDGIN_WINDOW_ICONIFIED(x) (gdk_window_get_state(GTK_WIDGET(x)->window) & GDK_WINDOW_STATE_ICONIFIED)
 
 #endif /* _PIDGINDIALOGS_H_ */
--- a/pidgin/gtkdocklet-x11.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkdocklet-x11.c	Wed Jan 02 22:15:06 2008 +0000
@@ -79,13 +79,14 @@
 	g_idle_add(docklet_x11_recreate_cb, NULL);
 }
 
-static void
+static gboolean
 docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data)
 {
 	if (event->type != GDK_BUTTON_RELEASE)
-		return;
+		return FALSE;
 
 	pidgin_docklet_clicked(event->button);
+	return TRUE;
 }
 
 static void
--- a/pidgin/gtkft.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkft.c	Wed Jan 02 22:15:06 2008 +0000
@@ -156,15 +156,15 @@
 	}
 
 	if (time_remaining != NULL) {
-		if (purple_xfer_get_size(xfer) == 0) {
-			*time_remaining = g_strdup(_("Unknown"));
-		}
-		else if (purple_xfer_is_completed(xfer)) {
+		if (purple_xfer_is_completed(xfer)) {
 			*time_remaining = g_strdup(_("Finished"));
 		}
 		else if (purple_xfer_is_canceled(xfer)) {
 			*time_remaining = g_strdup(_("Canceled"));
 		}
+		else if (purple_xfer_get_size(xfer) == 0 || (kb_sent > 0 && kbps == 0)) {
+			*time_remaining = g_strdup(_("Unknown"));
+		}
 		else if (kb_sent <= 0) {
 			*time_remaining = g_strdup(_("Waiting for transfer to begin"));
 		}
@@ -745,7 +745,6 @@
 	PidginXferDialog *dialog;
 	GtkWidget *window;
 	GtkWidget *vbox1, *vbox2;
-	GtkWidget *bbox;
 	GtkWidget *sw;
 	GtkWidget *button;
 	GtkWidget *expander;
@@ -759,15 +758,13 @@
 		purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished");
 
 	/* Create the window. */
-	dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE);
+	dialog->window = window = pidgin_create_dialog(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
 
 	/* Create the parent vbox for everything. */
-	vbox1 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(window), vbox1);
-	gtk_widget_show(vbox1);
+	vbox1 = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Create the main vbox for top half of the window. */
 	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -812,71 +809,35 @@
 	gtk_container_add(GTK_CONTAINER(expander), table);
 	gtk_widget_show(table);
 
-	/* Now the button box for the buttons */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox1), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Open button */
-	button = gtk_button_new_from_stock(GTK_STOCK_OPEN);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_OPEN, G_CALLBACK(open_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->open_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(open_button_cb), dialog);
-
 	/* Pause button */
-	button = gtk_button_new_with_mnemonic(_("_Pause"));
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Pause"), G_CALLBACK(pause_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->pause_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pause_button_cb), dialog);
-
 	/* Resume button */
-	button = gtk_button_new_with_mnemonic(_("_Resume"));
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Resume"), G_CALLBACK(resume_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->resume_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(resume_button_cb), dialog);
-
 	/* Remove button */
-	button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_REMOVE, G_CALLBACK(remove_button_cb), dialog);
 	gtk_widget_hide(button);
 	dialog->remove_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(remove_button_cb), dialog);
-
 	/* Stop button */
-	button = gtk_button_new_from_stock(GTK_STOCK_STOP);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
 	dialog->stop_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(stop_button_cb), dialog);
-
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog);
 	dialog->close_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_button_cb), dialog);
-
 #ifdef _WIN32
 	g_signal_connect(G_OBJECT(dialog->window), "show",
 		G_CALLBACK(winpidgin_ensure_onscreen), dialog->window);
--- a/pidgin/gtkimhtml.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkimhtml.c	Wed Jan 02 22:15:06 2008 +0000
@@ -812,7 +812,6 @@
 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
 {
 	GtkWidget *menuitem;
-	GtkWidget *mi, *img;
 
 	menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
 	gtk_widget_show(menuitem);
@@ -1012,7 +1011,7 @@
 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext)
 {
 	GtkTextIter iter;
-	GtkIMHtmlOptions flags = plaintext ? 0 : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
+	GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
 
 	if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL))
 		gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
@@ -2997,6 +2996,7 @@
 			pos += tlen;
 			g_free(tag); /* This was allocated back in VALID_TAG() */
 		} else if (imhtml->edit.link == NULL &&
+				!(options & GTK_IMHTML_NO_SMILEY) &&
 				gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) {
 			GtkIMHtmlFontDetail *fd;
 			gchar *sml = NULL;
@@ -4277,33 +4277,6 @@
 	g_object_unref(object);
 }
 
-static void populate_popup_cb(GtkTextView *textview, GtkMenu *menu, gpointer nul)
-{
-	GtkWidget *mi, *img;
-	
-	mi = gtk_menu_item_new();
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(GTK_STOCK_BOLD, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_mnemonic(_("_Font"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_mnemonic(_("_Insert"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_mnemonic(_("S_mile!"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-}
-
 static void imhtml_toggle_bold(GtkIMHtml *imhtml)
 {
 	GtkTextIter start, end;
--- a/pidgin/gtkimhtml.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkimhtml.h	Wed Jan 02 22:15:06 2008 +0000
@@ -225,7 +225,8 @@
 	GTK_IMHTML_RETURN_LOG          = 1 << 7,
 	GTK_IMHTML_USE_POINTSIZE       = 1 << 8,
 	GTK_IMHTML_NO_FORMATTING       = 1 << 9,
-	GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10
+	GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10,
+	GTK_IMHTML_NO_SMILEY           = 1 << 11,
 } GtkIMHtmlOptions;
 
 enum {
--- a/pidgin/gtkimhtmltoolbar.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Jan 02 22:15:06 2008 +0000
@@ -99,7 +99,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_font(GtkWidget *widget, GdkEvent *event,
 					 GtkIMHtmlToolbar *toolbar)
 {
@@ -111,6 +111,7 @@
 		gtk_widget_destroy(toolbar->font_dialog);
 		toolbar->font_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -191,7 +192,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_fgcolor(GtkWidget *widget, GdkEvent *event,
 						GtkIMHtmlToolbar *toolbar)
 {
@@ -203,6 +204,7 @@
 		gtk_widget_destroy(toolbar->fgcolor_dialog);
 		toolbar->fgcolor_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void cancel_toolbar_fgcolor(GtkWidget *widget,
@@ -263,7 +265,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_bgcolor(GtkWidget *widget, GdkEvent *event,
 						GtkIMHtmlToolbar *toolbar)
 {
@@ -279,6 +281,7 @@
 		gtk_widget_destroy(toolbar->bgcolor_dialog);
 		toolbar->bgcolor_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -575,11 +578,12 @@
 	}
 }
 
-static void
+static gboolean
 close_smiley_dialog(GtkWidget *widget, GdkEvent *event,
 					GtkIMHtmlToolbar *toolbar)
 {
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE);
+	return FALSE;
 }
 
 
@@ -705,10 +709,8 @@
 		smileys = smileys->next;
 	}
 
-	PIDGIN_DIALOG(dialog);
+	dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE);
 
-	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
-	gtk_window_set_role(GTK_WINDOW(dialog), "smiley_dialog");
 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
 
 	if (unique_smileys != NULL) {
@@ -765,18 +767,15 @@
 	}
 
 	g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
-	gtk_container_add(GTK_CONTAINER(dialog), smiley_table);
+	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), smiley_table);
 
 	gtk_widget_show(smiley_table);
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
-
 	/* connect signals */
 	g_signal_connect(G_OBJECT(dialog), "delete_event",
 					 G_CALLBACK(close_smiley_dialog), toolbar);
 
 	/* show everything */
-	gtk_window_set_title(GTK_WINDOW(dialog), _("Smile!"));
 	gtk_widget_show_all(dialog);
 	gtk_window_set_transient_for(GTK_WINDOW(dialog),
 			GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
--- a/pidgin/gtknotify.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtknotify.c	Wed Jan 02 22:15:06 2008 +0000
@@ -166,16 +166,18 @@
 	mail_dialog = NULL;
 }
 
-static void
+static gboolean
 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
 {
 	purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
+	return FALSE;
 }
 
-static void
+static gboolean
 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
 {
 	purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
+	return FALSE;
 }
 
 static void
@@ -284,6 +286,8 @@
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
 
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return dialog;
@@ -684,6 +688,8 @@
 	g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
 
 	/* Show the window */
+	pidgin_auto_parent_window(window);
+
 	gtk_widget_show(window);
 
 	return window;
@@ -894,6 +900,8 @@
 	pidgin_notify_searchresults_new_rows(gc, results, data);
 
 	/* Show the window */
+	pidgin_auto_parent_window(window);
+
 	gtk_widget_show(window);
 	return data;
 }
--- a/pidgin/gtkplugin.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkplugin.c	Wed Jan 02 22:15:06 2008 +0000
@@ -31,6 +31,7 @@
 #include "debug.h"
 #include "prefs.h"
 #include "request.h"
+#include "pidgintooltip.h"
 
 #include <string.h>
 
@@ -531,6 +532,58 @@
 	plugin_dialog_response_cb(dialog, PIDGIN_RESPONSE_CONFIGURE, sel);
 }
 
+static gboolean
+pidgin_plugins_paint_tooltip(GtkWidget *tipwindow, gpointer data)
+{
+	PangoLayout *layout = g_object_get_data(G_OBJECT(tipwindow), "tooltip-plugin");
+	gtk_paint_layout(tipwindow->style, tipwindow->window, GTK_STATE_NORMAL, FALSE,
+			NULL, tipwindow, "tooltip",
+			6, 6, layout);
+	return TRUE;
+}
+
+static gboolean
+pidgin_plugins_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
+		gpointer data, int *w, int *h)
+{
+	GtkTreeIter iter;
+	GtkTreeView *treeview = GTK_TREE_VIEW(data);
+	PurplePlugin *plugin = NULL;
+	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
+	PangoLayout *layout;
+	int width, height;
+	char *markup, *name, *desc, *author;
+
+	if (!gtk_tree_model_get_iter(model, &iter, path))
+		return FALSE;
+
+	gtk_tree_model_get(model, &iter, 2, &plugin, -1);
+
+	markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>Description:</b> %s\n<b>Author:</b> %s",
+			name = g_markup_escape_text(purple_plugin_get_name(plugin), -1),
+			desc = g_markup_escape_text(purple_plugin_get_description(plugin), -1),
+			author = g_markup_escape_text(purple_plugin_get_author(plugin), -1));
+
+	layout = gtk_widget_create_pango_layout(tipwindow, NULL);
+	pango_layout_set_markup(layout, markup, -1);
+	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
+	pango_layout_set_width(layout, 600000);
+	pango_layout_get_size(layout, &width, &height);
+	g_object_set_data_full(G_OBJECT(tipwindow), "tooltip-plugin", layout, g_object_unref);
+
+	if (w)
+		*w = PANGO_PIXELS(width) + 12;
+	if (h)
+		*h = PANGO_PIXELS(height) + 12;
+
+	g_free(markup);
+	g_free(name);
+	g_free(desc);
+	g_free(author);
+
+	return TRUE;
+}
+
 void pidgin_plugin_dialog_show()
 {
 	GtkWidget *sw;
@@ -613,6 +666,10 @@
 	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(event_view),
 				pidgin_tree_view_search_equal_func, NULL, NULL);
 
+	pidgin_tooltip_setup_for_treeview(event_view, event_view,
+			pidgin_plugins_create_tooltip,
+			pidgin_plugins_paint_tooltip);
+
 	expander = gtk_expander_new(_("<b>Plugin Details</b>"));
 	gtk_expander_set_use_markup(GTK_EXPANDER(expander), TRUE);
 	plugin_details = gtk_label_new(NULL);
--- a/pidgin/gtkpounce.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkpounce.c	Wed Jan 02 22:15:06 2008 +0000
@@ -1317,7 +1317,6 @@
 pidgin_pounces_manager_show(void)
 {
 	PouncesManager *dialog;
-	GtkWidget *bbox;
 	GtkWidget *button;
 	GtkWidget *list;
 	GtkWidget *vbox;
@@ -1334,7 +1333,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -1343,61 +1342,33 @@
 					 G_CALLBACK(pounces_manager_configure_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* List of saved buddy pounces */
 	list = create_pounces_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
+	/* Add button */
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(pounces_manager_add_cb), dialog);
+	gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL));
 
-	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL));
 	purple_signal_connect(purple_connections_get_handle(), "signed-on",
 						pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button);
 	purple_signal_connect(purple_connections_get_handle(), "signed-off",
 						pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_add_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
-	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(pounces_manager_modify_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_modify_cb), dialog);
+	dialog->modify_button = button;
 
 	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
-	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(pounces_manager_delete_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_delete_cb), dialog);
+	dialog->delete_button = button;
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_close_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(pounces_manager_close_cb), dialog);
 
 	gtk_widget_show(win);
 }
--- a/pidgin/gtkprefs.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkprefs.c	Wed Jan 02 22:15:06 2008 +0000
@@ -2150,7 +2150,6 @@
 void pidgin_prefs_show(void)
 {
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *notebook;
 	GtkWidget *button;
 
@@ -2166,31 +2165,20 @@
 	/* Back to instant-apply! I win!  BU-HAHAHA! */
 
 	/* Create the window */
-	prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE);
+	prefs = pidgin_create_dialog(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE);
 	g_signal_connect(G_OBJECT(prefs), "destroy",
 					 G_CALLBACK(delete_prefs), NULL);
 
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(prefs), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(prefs), FALSE, PIDGIN_HIG_BORDER);
 
 	/* The notebook */
 	prefsnotebook = notebook = gtk_notebook_new ();
 	gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0);
 	gtk_widget_show(prefsnotebook);
 
-	/* The buttons to press! */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
-	gtk_widget_show (bbox);
-
-	button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(prefs), GTK_STOCK_CLOSE, NULL, NULL);
 	g_signal_connect_swapped(G_OBJECT(button), "clicked",
 							 G_CALLBACK(gtk_widget_destroy), prefs);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 
 	prefs_notebook_init();
 
--- a/pidgin/gtkprivacy.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkprivacy.c	Wed Jan 02 22:15:06 2008 +0000
@@ -45,6 +45,7 @@
 	GtkWidget *add_button;
 	GtkWidget *remove_button;
 	GtkWidget *clear_button;
+	GtkWidget *close_button;
 
 	GtkWidget *button_box;
 	GtkWidget *allow_widget;
@@ -259,19 +260,22 @@
 
 	gtk_widget_hide(dialog->allow_widget);
 	gtk_widget_hide(dialog->block_widget);
-	gtk_widget_hide(dialog->button_box);
+	gtk_widget_hide_all(dialog->button_box);
 
 	if (new_type == PURPLE_PRIVACY_ALLOW_USERS) {
 		gtk_widget_show(dialog->allow_widget);
-		gtk_widget_show(dialog->button_box);
+		gtk_widget_show_all(dialog->button_box);
 		dialog->in_allow_list = TRUE;
 	}
 	else if (new_type == PURPLE_PRIVACY_DENY_USERS) {
 		gtk_widget_show(dialog->block_widget);
-		gtk_widget_show(dialog->button_box);
+		gtk_widget_show_all(dialog->button_box);
 		dialog->in_allow_list = FALSE;
 	}
 
+	gtk_widget_show_all(dialog->close_button);
+	gtk_widget_show(dialog->button_box);
+
 	purple_blist_schedule_save();
 	pidgin_blist_refresh(purple_get_blist());
 }
@@ -355,7 +359,6 @@
 privacy_dialog_new(void)
 {
 	PidginPrivacyDialog *dialog;
-	GtkWidget *bbox;
 	GtkWidget *hbox;
 	GtkWidget *vbox;
 	GtkWidget *button;
@@ -367,15 +370,13 @@
 
 	dialog = g_new0(PidginPrivacyDialog, 1);
 
-	dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE);
+	dialog->win = pidgin_create_dialog(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
 					 G_CALLBACK(destroy_cb), dialog);
 
 	/* Main vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(dialog->win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog->win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Description label */
 	label = gtk_label_new(
@@ -433,52 +434,27 @@
 	gtk_box_pack_start(GTK_BOX(vbox), dialog->block_widget, TRUE, TRUE, 0);
 
 	/* Add the button box for Add, Remove, Clear */
-	dialog->button_box = bbox = gtk_hbutton_box_new();
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+	dialog->button_box = pidgin_dialog_get_action_area(GTK_DIALOG(dialog->win));
 
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_ADD, G_CALLBACK(add_cb), dialog);
 	dialog->add_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(add_cb), dialog);
 
 	/* Remove button */
-	button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_REMOVE, G_CALLBACK(remove_cb), dialog);
 	dialog->remove_button = button;
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(remove_cb), dialog);
 
 	/* Clear button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLEAR, G_CALLBACK(clear_cb), dialog);
 	dialog->clear_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(clear_cb), dialog);
-
-	/* Another button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
-	gtk_widget_show(bbox);
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLOSE, G_CALLBACK(close_cb), dialog);
+	dialog->close_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_cb), dialog);
-
+	type_changed_cb(GTK_OPTION_MENU(dialog->type_menu), dialog);
+#if 0
 	if (dialog->account->perm_deny == PURPLE_PRIVACY_ALLOW_USERS) {
 		gtk_widget_show(dialog->allow_widget);
 		gtk_widget_show(dialog->button_box);
@@ -489,7 +465,7 @@
 		gtk_widget_show(dialog->button_box);
 		dialog->in_allow_list = FALSE;
 	}
-
+#endif
 	return dialog;
 }
 
--- a/pidgin/gtkrequest.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkrequest.c	Wed Jan 02 22:15:06 2008 +0000
@@ -251,11 +251,12 @@
 	purple_request_close(PURPLE_REQUEST_FIELDS, data);
 }
 
-static void
+static gboolean
 destroy_multifield_cb(GtkWidget *dialog, GdkEvent *event,
 					  PidginRequestData *data)
 {
 	multifield_cancel_cb(NULL, data);
+	return FALSE;
 }
 
 
@@ -439,6 +440,8 @@
 	pidgin_set_accessible_label (entry, label);
 	data->u.input.entry = entry;
 
+	pidgin_auto_parent_window(dialog);
+
 	/* Show everything. */
 	gtk_widget_show(dialog);
 
@@ -546,6 +549,8 @@
 	g_object_set_data(G_OBJECT(dialog), "radio", radio);
 
 	/* Show everything. */
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return data;
@@ -661,6 +666,8 @@
 		gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_action);
 
 	/* Show everything. */
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return data;
@@ -1059,7 +1066,6 @@
 	GtkWidget *vbox;
 	GtkWidget *vbox2;
 	GtkWidget *hbox;
-	GtkWidget *bbox;
 	GtkWidget *frame;
 	GtkWidget *label;
 	GtkWidget *table;
@@ -1089,9 +1095,9 @@
 
 
 #ifdef _WIN32
-	data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+	data->dialog = win = pidgin_create_dialog(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
 #else /* !_WIN32 */
-	data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+	data->dialog = win = pidgin_create_dialog(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
 #endif /* _WIN32 */
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -1099,7 +1105,7 @@
 
 	/* Setup the main horizontal box */
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), hbox);
+	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(win))), hbox);
 	gtk_widget_show(hbox);
 
 	/* Dialog icon. */
@@ -1382,39 +1388,21 @@
 
 	g_object_unref(sg);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(text_to_stock(cancel_text));
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(multifield_cancel_cb), data);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(cancel_text), G_CALLBACK(multifield_cancel_cb), data);
 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
 
 	/* OK button */
-	button = gtk_button_new_from_stock(text_to_stock(ok_text));
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(ok_text), G_CALLBACK(multifield_ok_cb), data);
 	data->ok_button = button;
-
 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
 	gtk_window_set_default(GTK_WINDOW(win), button);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(multifield_ok_cb), data);
-
 	if (!purple_request_fields_all_required_filled(fields))
 		gtk_widget_set_sensitive(button, FALSE);
 
+	pidgin_auto_parent_window(win);
+
 	gtk_widget_show(win);
 
 	return data;
@@ -1518,7 +1506,7 @@
 }
 
 #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
-static void
+static gboolean
 file_cancel_cb(PidginRequestData *data)
 {
 	generic_response_start(data);
@@ -1527,6 +1515,7 @@
 		((PurpleRequestFileCb)data->cbs[0])(data->user_data, NULL);
 
 	purple_request_close(data->type, data);
+	return FALSE;
 }
 #endif /* FILECHOOSER */
 
@@ -1624,6 +1613,8 @@
 					 G_CALLBACK(file_ok_check_if_exists_cb), data);
 #endif /* FILECHOOSER */
 
+	pidgin_auto_parent_window(filesel);
+
 	data->dialog = filesel;
 	gtk_widget_show(filesel);
 
@@ -1675,6 +1666,8 @@
 #endif
 
 	data->dialog = dirsel;
+	pidgin_auto_parent_window(dirsel);
+
 	gtk_widget_show(dirsel);
 
 	return (void *)data;
--- a/pidgin/gtkroomlist.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkroomlist.c	Wed Jan 02 22:15:06 2008 +0000
@@ -28,6 +28,8 @@
 #include "pidgin.h"
 #include "gtkutils.h"
 #include "pidginstock.h"
+#include "pidgintooltip.h"
+
 #include "debug.h"
 #include "account.h"
 #include "connection.h"
@@ -340,41 +342,20 @@
 	}
 }
 
-static void pidgin_roomlist_tooltip_destroy(PidginRoomlist *grl)
-{
-	if ((grl == NULL) || (grl->tipwindow == NULL))
-		return;
-
-	gtk_widget_destroy(grl->tipwindow);
-	grl->tipwindow = NULL;
-}
-
-static void pidgin_roomlist_tooltip_destroy_cb(GObject *object, PidginRoomlist *grl)
-{
-	if ((grl == NULL) || (grl->tipwindow == NULL))
-		return;
-
-	if (grl->timeout)
-		g_source_remove(grl->timeout);
-	grl->timeout = 0;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-}
-
 #define SMALL_SPACE 6
 #define TOOLTIP_BORDER 12
 
-static void pidgin_roomlist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
+static gboolean
+pidgin_roomlist_paint_tooltip(GtkWidget *widget, gpointer user_data)
 {
-	PidginRoomlist *grl = (PidginRoomlist *)user_data;
+	PurpleRoomlist *list = user_data;
+	PidginRoomlist *grl = list->ui_data;
 	GtkStyle *style;
 	int current_height, max_width;
 	int max_text_width;
 	GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(grl->tree));
 
 	style = grl->tipwindow->style;
-	gtk_paint_flat_box(style, grl->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
-			NULL, grl->tipwindow, "tooltip", 0, 0, -1, -1);
 
 	max_text_width = 0;
 
@@ -404,15 +385,13 @@
 				current_height + grl->tip_name_height,
 				grl->tip_layout);
 	}
-
+	return FALSE;
 }
 
-static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list)
+static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path)
 {
 	PidginRoomlist *grl = list->ui_data;
-	GtkWidget *tv = grl->tree;
 	PurpleRoomlistRoom *room;
-	GtkTreePath *path;
 	GtkTreeIter iter;
 	GValue val;
 	gchar *name, *tmp, *node_name;
@@ -421,10 +400,11 @@
 	gint j;
 	gboolean first = TRUE;
 
+#if 0
 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
 		&path, NULL, NULL, NULL))
 		return FALSE;
-
+#endif
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
 
 	val.g_type = 0;
@@ -460,8 +440,6 @@
 		g_free(label);
 	}
 
-	gtk_tree_path_free(path);
-
 	grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
 	grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
 
@@ -492,156 +470,22 @@
 	return TRUE;
 }
 
-static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget)
+static gboolean
+pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
+		gpointer data, int *w, int *h)
 {
+	PurpleRoomlist *list = data;
 	PidginRoomlist *grl = list->ui_data;
-	int scr_w, scr_h, w, h, x, y;
-#if GTK_CHECK_VERSION(2,2,0)
-	int mon_num;
-	GdkScreen *screen = NULL;
-#endif
-	GdkRectangle mon_size;
-	int sig;
-	const char *name;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-	grl->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
-	gtk_widget_ensure_style (grl->tipwindow);
-
-	if (!pidgin_roomlist_create_tip(list)) {
-		pidgin_roomlist_tooltip_destroy(grl);
-		return;
-	}
-
-	name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
-	gtk_widget_set_app_paintable(grl->tipwindow, TRUE);
-	gtk_window_set_title(GTK_WINDOW(grl->tipwindow), name ? name : _("Room List"));
-	gtk_window_set_resizable(GTK_WINDOW(grl->tipwindow), FALSE);
-	gtk_widget_set_name(grl->tipwindow, "gtk-tooltips");
-	g_signal_connect(G_OBJECT(grl->tipwindow), "expose_event",
-			G_CALLBACK(pidgin_roomlist_paint_tip), grl);
-
-	w = TOOLTIP_BORDER + SMALL_SPACE +
-		MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
-	h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
-		+ TOOLTIP_BORDER;
-
-#if GTK_CHECK_VERSION(2,2,0)
-	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
-	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
-	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
-
-	scr_w = mon_size.width + mon_size.x;
-	scr_h = mon_size.height + mon_size.y;
-#else
-	scr_w = gdk_screen_width();
-	scr_h = gdk_screen_height();
-	gdk_window_get_pointer(NULL, &x, &y, NULL);
-	mon_size.x = 0;
-	mon_size.y = 0;
-#endif
-
-#if GTK_CHECK_VERSION(2,2,0)
-	if (w > mon_size.width)
-		w = mon_size.width - 10;
-
-	if (h > mon_size.height)
-		h = mon_size.height - 10;
-#endif
-	x -= ((w >> 1) + 4);
-
-	if ((y + h + 4) > scr_h)
-		y = y - h - 5;
-	else
-		y = y + 6;
-
-	if (y < mon_size.y)
-		y = mon_size.y;
-
-	if (y != mon_size.y) {
-		if ((x + w) > scr_w)
-			x -= (x + w + 5) - scr_w;
-		else if (x < mon_size.x)
-			x = mon_size.x;
-	} else {
-		x -= (w / 2 + 10);
-		if (x < mon_size.x)
-			x = mon_size.x;
-	}
-
-	gtk_widget_set_size_request(grl->tipwindow, w, h);
-	gtk_window_move(GTK_WINDOW(grl->tipwindow), x, y);
-	gtk_widget_show(grl->tipwindow);
-
-	/* Hide the tooltip when the widget is destroyed */
-	sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_roomlist_tooltip_destroy_cb), grl);
-	g_signal_connect_swapped(G_OBJECT(grl->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
-}
-
-static gboolean pidgin_roomlist_tooltip_timeout(PurpleRoomlist *list)
-{
-	PidginRoomlist *grl = list->ui_data;
-	GtkWidget *tv = grl->tree;
-	GtkTreePath *path;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-
-	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
-	                                   &path, NULL, NULL, NULL))
+	grl->tipwindow = widget;
+	if (!pidgin_roomlist_create_tip(data, path))
 		return FALSE;
-
-	pidgin_roomlist_draw_tooltip(list, GTK_WIDGET(grl->tree));
-
-	return FALSE;
-}
-
-static gboolean row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer user_data)
-{
-	PurpleRoomlist *list = user_data;
-	PidginRoomlist *grl = list->ui_data;
-	GtkTreePath *path;
-	int delay;
-
-	/* XXX: should this be using the blist delay pref? */
-	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-
-	if (delay == 0)
-		return FALSE;
-
-	if (grl->timeout) {
-		if ((event->y > grl->tip_rect.y) && ((event->y - grl->tip_rect.height) < grl->tip_rect.y))
-			return FALSE;
-		/* We've left the cell.  Remove the timeout and create a new one below */
-		pidgin_roomlist_tooltip_destroy(grl);
-	}
-
-	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
-
-	if (path == NULL) {
-		pidgin_roomlist_tooltip_destroy(grl);
-		return FALSE;
-	}
-
-	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &grl->tip_rect);
-
-	if (path)
-		gtk_tree_path_free(path);
-	grl->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_roomlist_tooltip_timeout, list);
-
-	return FALSE;
-}
-
-static void row_leave_cb(GtkWidget *tv, GdkEventCrossing *e, gpointer user_data)
-{
-	PurpleRoomlist *list = user_data;
-	PidginRoomlist *grl = list->ui_data;
-
-	if (grl->timeout) {
-		g_source_remove(grl->timeout);
-		grl->timeout = 0;
-	}
-
-	pidgin_roomlist_tooltip_destroy(grl);
+	if (w)
+		*w = TOOLTIP_BORDER + SMALL_SPACE +
+			MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
+	if (h)
+		*h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
+			+ TOOLTIP_BORDER;
+	return TRUE;
 }
 
 static gboolean account_filter_func(PurpleAccount *account)
@@ -685,15 +529,13 @@
 	dialog->account = account;
 
 	/* Create the window. */
-	dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
+	dialog->window = window = pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
 
 	/* Create the parent vbox for everything. */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(window), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
 
 	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_container_add(GTK_CONTAINER(vbox), vbox2);
@@ -738,19 +580,14 @@
 	gtk_widget_show(dialog->progress);
 
 	/* button box */
-	bbox = gtk_hbutton_box_new();
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window));
 	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
 
 	/* stop button */
-	dialog->stop_button = gtk_button_new_from_stock(GTK_STOCK_STOP);
-	gtk_box_pack_start(GTK_BOX(bbox), dialog->stop_button, FALSE, FALSE, 0);
-	g_signal_connect(G_OBJECT(dialog->stop_button), "clicked",
+	dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
 	                 G_CALLBACK(stop_button_cb), dialog);
 	gtk_widget_set_sensitive(dialog->stop_button, FALSE);
-	gtk_widget_show(dialog->stop_button);
 
 	/* list button */
 	dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH,
@@ -779,11 +616,8 @@
 	gtk_widget_show(dialog->join_button);
 
 	/* close button */
-	dialog->close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), dialog->close_button, FALSE, FALSE, 0);
-	g_signal_connect(G_OBJECT(dialog->close_button), "clicked",
+	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
 					 G_CALLBACK(close_button_cb), dialog);
-	gtk_widget_show(dialog->close_button);
 
 	/* show the dialog window and return the dialog */
 	gtk_widget_show(dialog->window);
@@ -967,6 +801,9 @@
 	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list);
 	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list);
 #endif
+	pidgin_tooltip_setup_for_treeview(tree, list,
+		pidgin_roomlist_create_tooltip,
+		pidgin_roomlist_paint_tooltip);
 
 	/* Enable CTRL+F searching */
 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
--- a/pidgin/gtksavedstatuses.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Wed Jan 02 22:15:06 2008 +0000
@@ -594,7 +594,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -603,18 +603,14 @@
 					 G_CALLBACK(configure_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* List of saved status states */
 	list = create_saved_status_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
 
 	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
 
 	/* Use button */
 	button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
@@ -627,36 +623,23 @@
 					 G_CALLBACK(status_window_use_cb), dialog);
 
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_add_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD,
+			G_CALLBACK(status_window_add_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
+			G_CALLBACK(status_window_modify_cb), dialog);
 	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+
+	/* Delete button */
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
+			G_CALLBACK(status_window_delete_cb), dialog);
+	dialog->delete_button = button;
 	gtk_widget_set_sensitive(button, FALSE);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_modify_cb), dialog);
-
-	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
-	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_set_sensitive(button, FALSE);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_delete_cb), dialog);
-
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_close_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
+			G_CALLBACK(status_window_close_cb), dialog);
 
 	purple_signal_connect(purple_savedstatuses_get_handle(),
 			"savedstatus-changed", status_window,
@@ -1147,14 +1130,13 @@
 	if (edit)
 		dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
 
-	dialog->window = win = pidgin_create_window(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(status_editor_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
@@ -1257,23 +1239,18 @@
 		(saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
 
 	/* Button box */
-	bbox = gtk_hbutton_box_new();
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
 	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
 
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_editor_cancel_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
+			G_CALLBACK(status_editor_cancel_cb), dialog);
 
 	/* Use button */
 	button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
 										   PIDGIN_BUTTON_HORIZONTAL);
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
 	g_signal_connect(G_OBJECT(button), "clicked",
 					 G_CALLBACK(status_editor_ok_cb), dialog);
 
@@ -1284,19 +1261,15 @@
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	if (dialog->original_title == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
 	g_signal_connect(G_OBJECT(button), "clicked",
 					 G_CALLBACK(status_editor_ok_cb), dialog);
 
 	/* Save button */
-	button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
-	dialog->save_button = GTK_BUTTON(button);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
+			G_CALLBACK(status_editor_ok_cb), dialog);
 	if (dialog->original_title == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_editor_ok_cb), dialog);
+	dialog->save_button = GTK_BUTTON(button);
 
 	gtk_widget_show_all(win);
 	g_object_unref(sg);
@@ -1447,8 +1420,6 @@
 	char *tmp;
 	SubStatusEditor *dialog;
 	GtkSizeGroup *sg;
-	GtkWidget *bbox;
-	GtkWidget *button;
 	GtkWidget *combo;
 	GtkWidget *hbox;
 	GtkWidget *frame;
@@ -1486,15 +1457,14 @@
 	dialog->account = account;
 
 	tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
-	dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
+	dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
 	g_free(tmp);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(substatus_editor_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
@@ -1543,25 +1513,13 @@
 	dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar);
 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
 
-	/* Button box */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(substatus_editor_cancel_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
+			G_CALLBACK(substatus_editor_cancel_cb), dialog);
 
 	/* OK button */
-	button = gtk_button_new_from_stock(GTK_STOCK_OK);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(substatus_editor_ok_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK,
+			G_CALLBACK(substatus_editor_ok_cb), dialog);
 
 	/* Seed the input widgets with the current values */
 
--- a/pidgin/gtkscrollbook.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkscrollbook.c	Wed Jan 02 22:15:06 2008 +0000
@@ -64,7 +64,7 @@
 	return scroll_book_type;
 }
 
-static void
+static gboolean
 scroll_left_cb(PidginScrollBook *scroll_book)
 {
 	int index;
@@ -72,9 +72,10 @@
 
 	if (index > 0)
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index - 1);
+	return TRUE;
 }
 
-static void
+static gboolean
 scroll_right_cb(PidginScrollBook *scroll_book)
 {
 	int index, count;
@@ -87,6 +88,7 @@
 
 	if (index + 1 < count)
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index + 1);
+	return TRUE;
 }
 
 static void
@@ -136,10 +138,11 @@
 	refresh_scroll_box(scroll_book, index, count);
 }
 
-static void
+static gboolean
 scroll_close_cb(PidginScrollBook *scroll_book)
 {
 	gtk_widget_destroy(gtk_notebook_get_nth_page(GTK_NOTEBOOK(scroll_book->notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook))));
+	return FALSE;
 }
 
 static void
--- a/pidgin/gtkstatusbox.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkstatusbox.c	Wed Jan 02 22:15:06 2008 +0000
@@ -119,7 +119,10 @@
 	DATA_COLUMN,
 
 	/**
- 	 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored
+ 	 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
+	 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
+	 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the prpl-icon
+	 * (GdkPixbuf) of the account.
  	 */
 	EMBLEM_COLUMN,
 
@@ -606,7 +609,7 @@
 	char aa_color[8];
 	PurpleSavedStatus *saved_status;
 	char *primary, *secondary, *text;
-	GdkPixbuf *pixbuf;
+	GdkPixbuf *pixbuf, *emblem = NULL;
 	GtkTreePath *path;
 	gboolean account_status = FALSE;
 	PurpleAccount *acct = (status_box->token_status_account) ? status_box->token_status_account : status_box->account;
@@ -703,6 +706,7 @@
 		text = g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
 				       purple_account_get_username(status_box->account),
 				       aa_color, secondary ? secondary : primary);
+		emblem = pidgin_create_prpl_icon(status_box->account, PIDGIN_PRPL_ICON_SMALL);
 	} else if (secondary != NULL) {
 		text = g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
 				       primary, aa_color, secondary);
@@ -719,10 +723,14 @@
 	gtk_list_store_set(status_box->store, &(status_box->iter),
 			   ICON_COLUMN, pixbuf,
 			   TEXT_COLUMN, text,
+			   EMBLEM_COLUMN, emblem,
+			   EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
 			   -1);
 	if ((status_box->typing == 0) && (!status_box->connecting))
 		g_object_unref(pixbuf);
 	g_free(text);
+	if (emblem)
+		g_object_unref(emblem);
 
 	/* Make sure to activate the only row in the tree view */
 	path = gtk_tree_path_new_from_string("0");
@@ -1100,7 +1108,7 @@
 	return TRUE;
 }
 
-static int imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
+static gboolean imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
 {
 	if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab)
 	{
@@ -1690,7 +1698,7 @@
 	status_box->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
 
 	status_box->store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, 
-					       G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
+					       G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN);
 	status_box->dropdown_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, GDK_TYPE_PIXBUF, G_TYPE_STRING, 
 							G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
 
@@ -1775,10 +1783,13 @@
 
 	status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
 	status_box->text_rend = gtk_cell_renderer_text_new();
+	emblem_rend = gtk_cell_renderer_pixbuf_new();
 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
+	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, FALSE);
 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "pixbuf", ICON_COLUMN, NULL);
 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
+	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, "pixbuf", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
 #if GTK_CHECK_VERSION(2, 6, 0)
 	g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
 #endif
--- a/pidgin/gtkutils.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkutils.c	Wed Jan 02 22:15:06 2008 +0000
@@ -132,12 +132,9 @@
 	}
 }
 
-GtkWidget *
-pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
+static
+void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
 {
-	GtkWindow *wnd = NULL;
-
-	wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
 	if (title)
 		gtk_window_set_title(wnd, title);
 #ifdef _WIN32
@@ -148,11 +145,63 @@
 	if (role)
 		gtk_window_set_role(wnd, role);
 	gtk_window_set_resizable(wnd, resizable);
+}
+
+GtkWidget *
+pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+
+	return GTK_WIDGET(wnd);
+}
+
+GtkWidget *
+pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_dialog_new());
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+	g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
 
 	return GTK_WIDGET(wnd);
 }
 
 GtkWidget *
+pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
+{
+	GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
+	gtk_box_set_homogeneous(vbox, homogeneous);
+	gtk_box_set_spacing(vbox, spacing);
+	return GTK_WIDGET(vbox);
+}
+
+GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->vbox;
+}
+
+GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->action_area;
+}
+
+GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
+		GCallback callback, gpointer callbackdata)
+{
+	GtkWidget *button = gtk_button_new_from_stock(label);
+	GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	if (callback)
+		g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
+	gtk_widget_show(button);
+	return button;
+}
+
+GtkWidget *
 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
 {
 	GtkWidget *frame;
@@ -3272,3 +3321,107 @@
 	gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
 }
 
+gboolean pidgin_auto_parent_window(GtkWidget *widget)
+{
+#if 0
+	/* This looks at the most recent window that received focus, and makes
+	 * that the parent window. */
+#ifndef _WIN32
+	static GdkAtom _WindowTime = GDK_NONE;
+	static GdkAtom _Cardinal = GDK_NONE;
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	time_t window_time = 0;
+
+	windows = gtk_window_list_toplevels();
+
+	if (_WindowTime == GDK_NONE) {
+		_WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
+	}
+	if (_Cardinal == GDK_NONE) {
+		_Cardinal = gdk_atom_intern("CARDINAL", FALSE);
+	}
+
+	while (windows) {
+		GtkWidget *window = windows->data;
+		guchar *data = NULL;
+		int al = 0;
+		time_t value;
+
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window))
+			continue;
+
+		if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
+				NULL, NULL, &al, &data))
+			continue;
+		value = *(time_t *)data;
+		if (window_time < value) {
+			window_time = value;
+			parent = window;
+		}
+		g_free(data);
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
+			/* The window is in focus, and the new window was not triggered by a keypress/click
+			 * event. So do not set it transient, to avoid focus stealing and all that.
+			 */
+			return FALSE;
+		}
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+	return FALSE;
+#endif
+#else
+	/* This finds the currently active window and makes that the parent window. */
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	GdkEvent *event = gtk_get_current_event();
+	GdkWindow *menu = NULL;
+
+	if (event == NULL)
+		/* The window was not triggered by a user action. */
+		return FALSE;
+
+	/* We need to special case events from a popup menu. */
+	if (event->type == GDK_BUTTON_RELEASE) {
+		/* XXX: Neither of the following works:
+			menu = event->button.window;
+			menu = gdk_window_get_parent(event->button.window);
+			menu = gdk_window_get_toplevel(event->button.window);
+		*/
+	} else if (event->type == GDK_KEY_PRESS)
+		menu = event->key.window;
+
+	windows = gtk_window_list_toplevels();
+	while (windows) {
+		GtkWidget *window = windows->data;
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window)) {
+			continue;
+		}
+
+		if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
+				(menu && menu == window->window)) {
+			parent = window;
+			break;
+		}
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+	return FALSE;
+#endif
+}
+
--- a/pidgin/gtkutils.h	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/gtkutils.h	Wed Jan 02 22:15:06 2008 +0000
@@ -121,6 +121,61 @@
 GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable);
 
 /**
+ * Creates a new dialog window
+ *
+ * @param title        The window title, or @c NULL
+ * @param border_width The window's desired border width
+ * @param role         A string indicating what the window is responsible for doing, or @c NULL
+ * @param resizable    Whether the window should be resizable (@c TRUE) or not (@c FALSE)
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable);
+
+/**
+ * Retrieves the main content box (vbox) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ * @param homogeneous  TRUE if all children are to be given equal space allotments. 
+ * @param spacing      the number of pixels to place by default between children
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing);
+
+/**
+ * Retrieves the main content box (vbox) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog);
+
+/**
+ * Add a button to a dialog created by #pidgin_create_dialog.
+ *
+ * @param dialog         The dialog window
+ * @param label          The stock-id or the label for the button
+ * @param callback       The callback function for the button
+ * @param callbackdata   The user data for the callback function
+ *
+ * @return The created button.
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
+		GCallback callback, gpointer callbackdata);
+
+/**
+ * Retrieves the action area (button box) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog);
+
+/**
  * Toggles the sensitivity of a widget.
  *
  * @param widget    @c NULL. Used for signal handlers.
@@ -319,16 +374,20 @@
 gboolean pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts);
 
 /**
+ * Add autocompletion of screenames to an entry.
+ *
  * @deprecated
- * Add autocompletion of screenames to an entry.
- * The usage of this function is deprecated. For new code, use the equivalent:
- * pidgin_setup_screenname_autocomplete_with_filter(entry, optmenu, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all))
+ *   For new code, use the equivalent:
+ *   #pidgin_setup_screenname_autocomplete_with_filter(@a entry, @a optmenu,
+ *   #pidgin_screenname_autocomplete_default_filter, <tt>GINT_TO_POINTER(@a
+ *   all)</tt>)
  *
  * @param entry     The GtkEntry on which to setup autocomplete.
- * @param optmenu   A menu for accounts, returned by pidgin_account_option_menu_new().
- *                  If @a optmenu is not @c NULL, it'll be updated when a screenname is chosen
- *                  from the autocomplete list.
- * @param all       Whether to include screennames from disconnected accounts. 
+ * @param optmenu   A menu for accounts, returned by
+ *                  pidgin_account_option_menu_new().  If @a optmenu is not @c
+ *                  NULL, it'll be updated when a screenname is chosen from the
+ *                  autocomplete list.
+ * @param all       Whether to include screennames from disconnected accounts.
  */
 void pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *optmenu, gboolean all);
 
@@ -451,7 +510,7 @@
 
 /**
  * A valid GtkMenuPositionFunc.  This is used to determine where 
- * to draw context menu's when the menu is activated with the 
+ * to draw context menus when the menu is activated with the 
  * keyboard (shift+F10).  If the menu is activated with the mouse, 
  * then you should just use GTK's built-in position function, 
  * because it does a better job of positioning the menu.
@@ -725,5 +784,15 @@
  */
 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text);
 
+/**
+ * Automatically make a window transient to a suitable parent window.
+ *
+ * @param window    The window to make transient.
+ *
+ * @return  Whether the window was made transient or not.
+ * @since 2.4.0
+ */
+gboolean pidgin_auto_parent_window(GtkWidget *window);
+
 #endif /* _PIDGINUTILS_H_ */
 
--- a/pidgin/minidialog.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/minidialog.c	Wed Jan 02 22:15:06 2008 +0000
@@ -63,6 +63,7 @@
 			sizeof (PidginMiniDialog),
 			0,      /* n_preallocs */
 			(GInstanceInitFunc) pidgin_mini_dialog_init,
+			NULL,
 		};
 		g_define_type_id = g_type_register_static (GTK_TYPE_VBOX,
 			"PidginMiniDialog", &g_define_type_info, 0);
--- a/pidgin/pidgincombobox.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/pidgincombobox.c	Wed Jan 02 22:15:06 2008 +0000
@@ -2980,7 +2980,7 @@
 
   g_return_if_fail (link != NULL);
 
-  combo_box->priv->cells = g_slist_remove_link (combo_box->priv->cells, link);
+  combo_box->priv->cells = g_slist_delete_link (combo_box->priv->cells, link);
   combo_box->priv->cells = g_slist_insert (combo_box->priv->cells, info,
                                            position);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgintooltip.c	Wed Jan 02 22:15:06 2008 +0000
@@ -0,0 +1,363 @@
+/**
+ * @file pidgintooltip.c Pidgin Tooltip API
+ * @ingroup pidgin
+ */
+
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "prefs.h"
+#include "pidgin.h"
+#include "pidgintooltip.h"
+#include "debug.h"
+
+struct
+{
+	GtkWidget *widget;
+	int timeout;
+	GdkRectangle tip_rect;
+	GtkWidget *tipwindow;
+	PidginTooltipPaint paint_tooltip;
+} pidgin_tooltip;
+
+typedef struct
+{
+	GtkWidget *widget;
+	gpointer userdata;
+	PidginTooltipPaint paint_tooltip;
+	union {
+		struct {
+			PidginTooltipCreateForTree create_tooltip;
+			GtkTreePath *path;
+		} treeview;
+		struct {
+			PidginTooltipCreate create_tooltip;
+		} widget;
+	} common;
+} PidginTooltipData;
+
+static void
+destroy_tooltip_data(PidginTooltipData *data)
+{
+	gtk_tree_path_free(data->common.treeview.path);
+	g_free(data);
+}
+
+void pidgin_tooltip_destroy()
+{
+	if (pidgin_tooltip.timeout > 0) {
+		g_source_remove(pidgin_tooltip.timeout);
+		pidgin_tooltip.timeout = 0;
+	}
+	if (pidgin_tooltip.tipwindow) {
+		gtk_widget_destroy(pidgin_tooltip.tipwindow);
+		pidgin_tooltip.tipwindow = NULL;
+	}
+}
+
+static gboolean
+pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+	if (pidgin_tooltip.paint_tooltip) {
+		gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+				NULL, widget, "tooltip", 0, 0, -1, -1);
+		pidgin_tooltip.paint_tooltip(widget, data);
+	}
+	return FALSE;
+}
+
+static GtkWidget*
+setup_tooltip_window()
+{
+	const char *name;
+	GtkWidget *tipwindow;
+
+	tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
+	name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget));
+#if GTK_CHECK_VERSION(2,10,0)
+	gtk_window_set_type_hint(GTK_WINDOW(tipwindow), GDK_WINDOW_TYPE_HINT_TOOLTIP);
+#endif
+	gtk_widget_set_app_paintable(tipwindow, TRUE);
+	gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip"));
+	gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE);
+	gtk_widget_set_name(tipwindow, "gtk-tooltips");
+	gtk_widget_ensure_style(tipwindow);
+	gtk_widget_realize(tipwindow);
+	return tipwindow;
+}
+
+static void
+setup_tooltip_window_position(gpointer data, int w, int h)
+{
+	int sig;
+	int scr_w, scr_h, x, y;
+#if GTK_CHECK_VERSION(2,2,0)
+	int mon_num;
+	GdkScreen *screen = NULL;
+#endif
+	GdkRectangle mon_size;
+	GtkWidget *tipwindow = pidgin_tooltip.tipwindow;
+
+#if GTK_CHECK_VERSION(2,2,0)
+	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
+	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
+	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
+
+	scr_w = mon_size.width + mon_size.x;
+	scr_h = mon_size.height + mon_size.y;
+#else
+	scr_w = gdk_screen_width();
+	scr_h = gdk_screen_height();
+	gdk_window_get_pointer(NULL, &x, &y, NULL);
+	mon_size.x = 0;
+	mon_size.y = 0;
+#endif
+
+#if GTK_CHECK_VERSION(2,2,0)
+	if (w > mon_size.width)
+		w = mon_size.width - 10;
+
+	if (h > mon_size.height)
+		h = mon_size.height - 10;
+#endif
+	x -= ((w >> 1) + 4);
+
+	if ((y + h + 4) > scr_h)
+		y = y - h - 5;
+	else
+		y = y + 6;
+
+	if (y < mon_size.y)
+		y = mon_size.y;
+
+	if (y != mon_size.y) {
+		if ((x + w) > scr_w)
+			x -= (x + w + 5) - scr_w;
+		else if (x < mon_size.x)
+			x = mon_size.x;
+	} else {
+		x -= (w / 2 + 10);
+		if (x < mon_size.x)
+			x = mon_size.x;
+	}
+
+	gtk_widget_set_size_request(tipwindow, w, h);
+	gtk_window_move(GTK_WINDOW(tipwindow), x, y);
+	gtk_widget_show(tipwindow);
+
+	g_signal_connect(G_OBJECT(tipwindow), "expose_event",
+			G_CALLBACK(pidgin_tooltip_expose_event), data);
+
+	/* Hide the tooltip when the widget is destroyed */
+	sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL);
+	g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
+}
+
+void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	GtkWidget *tipwindow;
+	int w, h;
+
+	pidgin_tooltip_destroy();
+
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(widget);
+	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
+	pidgin_tooltip.paint_tooltip = paint_tooltip;
+
+	if (!create_tooltip(tipwindow, userdata, &w, &h)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+	setup_tooltip_window_position(userdata, w, h);
+}
+
+static void
+reset_data_treepath(PidginTooltipData *data)
+{
+	gtk_tree_path_free(data->common.treeview.path);
+	data->common.treeview.path = NULL;
+}
+
+static void
+pidgin_tooltip_draw(PidginTooltipData *data)
+{
+	GtkWidget *tipwindow;
+	int w, h;
+
+	pidgin_tooltip_destroy();
+
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
+	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
+	pidgin_tooltip.paint_tooltip = data->paint_tooltip;
+
+	if (!data->common.widget.create_tooltip(tipwindow, data->userdata, &w, &h)) {
+		if (tipwindow == pidgin_tooltip.tipwindow)
+			pidgin_tooltip_destroy();
+		return;
+	}
+
+	setup_tooltip_window_position(data->userdata, w, h);
+}
+
+static void
+pidgin_tooltip_draw_tree(PidginTooltipData *data)
+{
+	GtkWidget *tipwindow;
+	GtkTreePath *path = NULL;
+	int w, h;
+
+	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget),
+				pidgin_tooltip.tip_rect.x,
+				pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2),
+				&path, NULL, NULL, NULL)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+
+	if (data->common.treeview.path) {
+		if (gtk_tree_path_compare(data->common.treeview.path, path) == 0) {
+			gtk_tree_path_free(path);
+			return;
+		}
+		gtk_tree_path_free(data->common.treeview.path);
+		data->common.treeview.path = NULL;
+	}
+
+	pidgin_tooltip_destroy();
+
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
+	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
+	pidgin_tooltip.paint_tooltip = data->paint_tooltip;
+
+	if (!data->common.treeview.create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
+		if (tipwindow == pidgin_tooltip.tipwindow)
+			pidgin_tooltip_destroy();
+		gtk_tree_path_free(path);
+		return;
+	}
+
+	setup_tooltip_window_position(data->userdata, w, h);
+
+	data->common.treeview.path = path;
+	g_signal_connect_swapped(G_OBJECT(pidgin_tooltip.tipwindow), "destroy",
+			G_CALLBACK(reset_data_treepath), data);
+}
+
+static gboolean
+pidgin_tooltip_timeout(gpointer data)
+{
+	PidginTooltipData *tdata = data;
+	pidgin_tooltip.timeout = 0;
+	if (GTK_IS_TREE_VIEW(tdata->widget))
+		pidgin_tooltip_draw_tree(data);
+	else
+		pidgin_tooltip_draw(data);
+	return FALSE;
+}
+
+static gboolean
+row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
+{
+	GtkTreePath *path;
+	int delay;
+
+	if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv)))
+		return FALSE;    /* The cursor is probably on the TreeView's header. */
+
+	/* XXX: probably use something more generic? */
+	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+	if (delay == 0)
+		return FALSE;
+
+	if (pidgin_tooltip.timeout) {
+		if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y))
+			return FALSE;
+		/* We've left the cell.  Remove the timeout and create a new one below */
+		pidgin_tooltip_destroy();
+	}
+
+	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
+
+	if (path == NULL) {
+		pidgin_tooltip_destroy();
+		return FALSE;
+	}
+
+	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
+	gtk_tree_path_free(path);
+
+	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
+
+	return FALSE;
+}
+
+static gboolean
+widget_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
+{
+	pidgin_tooltip_destroy();
+	return FALSE;
+}
+
+gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
+		PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
+	tdata->widget = tree;
+	tdata->userdata = userdata;
+	tdata->common.treeview.create_tooltip = create_tooltip;
+	tdata->paint_tooltip = paint_tooltip;
+
+	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata);
+	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(widget_leave_cb), NULL);
+	g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata);
+	return TRUE;
+}
+
+static gboolean
+widget_motion_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+	int delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+
+	pidgin_tooltip_destroy();
+	if (delay == 0)
+		return FALSE;
+
+	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, data);
+	return FALSE;
+}
+
+gboolean pidgin_tooltip_setup_for_widget(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	PidginTooltipData *wdata = g_new0(PidginTooltipData, 1);
+	wdata->widget = widget;
+	wdata->userdata = userdata;
+	wdata->common.widget.create_tooltip = create_tooltip;
+	wdata->paint_tooltip = paint_tooltip;
+
+	g_signal_connect(G_OBJECT(widget), "motion-notify-event", G_CALLBACK(widget_motion_cb), wdata);
+	g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(widget_leave_cb), NULL);
+	g_signal_connect_swapped(G_OBJECT(widget), "destroy", G_CALLBACK(g_free), wdata);
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgintooltip.h	Wed Jan 02 22:15:06 2008 +0000
@@ -0,0 +1,112 @@
+/**
+ * @file pidgintooltip.h Pidgin Tooltip API
+ * @ingroup pidgin
+ */
+
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _PIDGIN_TOOLTIP_H_
+#define _PIDGIN_TOOLTIP_H_
+
+#include <gtk/gtk.h>
+
+/**
+ * @param tipwindow  The window for the tooltip.
+ * @param path       The GtkTreePath representing the row under the cursor.
+ * @param userdata   The userdata set during pidgin_tooltip_setup_for_treeview.
+ * @param w          The value of this should be set to the desired width of the tooltip window.
+ * @param h          The value of this should be set to the desired height of the tooltip window.
+ *
+ * @return  @c TRUE if the tooltip was created correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipCreateForTree)(GtkWidget *tipwindow,
+			GtkTreePath *path, gpointer userdata, int *w, int *h);
+
+/**
+ * @param tipwindow  The window for the tooltip.
+ * @param userdata   The userdata set during pidgin_tooltip_show.
+ * @param w          The value of this should be set to the desired width of the tooltip window.
+ * @param h          The value of this should be set to the desired height of the tooltip window.
+ *
+ * @return  @c TRUE if the tooltip was created correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipCreate)(GtkWidget *tipwindow,
+			gpointer userdata, int *w, int *h);
+
+/**
+ * @param  tipwindow   The window for the tooltip.
+ * @param  userdata    The userdata set during pidgin_tooltip_setup_for_treeview or pidgin_tooltip_show.
+ *
+ * @return  @c TRUE if the tooltip was painted correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipPaint)(GtkWidget *tipwindow, gpointer userdata);
+
+/**
+ * Setup tooltip drawing functions for a treeview.
+ *
+ * @param tree         The treeview
+ * @param userdata     The userdata to send to the callback functions
+ * @param create_cb    Callback function to create the tooltip for a GtkTreePath
+ * @param paint_cb     Callback function to paint the tooltip
+ *
+ * @return   @c TRUE if the tooltip callbacks were setup correctly.
+ * @since 2.4.0
+ */
+gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
+		PidginTooltipCreateForTree create_cb, PidginTooltipPaint paint_cb);
+
+/**
+ * Setup tooltip drawing functions for any widget.
+ *
+ * @param widget       The widget
+ * @param userdata     The userdata to send to the callback functions
+ * @param create_cb    Callback function to create the tooltip for the widget
+ * @param paint_cb     Callback function to paint the tooltip
+ *
+ * @return   @c TRUE if the tooltip callbacks were setup correctly.
+ * @since 2.4.0
+ */
+gboolean pidgin_tooltip_setup_for_widget(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip);
+
+/**
+ * Destroy the tooltip.
+ * @since 2.4.0
+ */
+void pidgin_tooltip_destroy(void);
+
+/**
+ * Create and show a tooltip.
+ *
+ * @param widget      The widget the tooltip is for
+ * @param userdata    The userdata to send to the callback functions
+ * @param create_cb    Callback function to create the tooltip from the GtkTreePath
+ * @param paint_cb     Callback function to paint the tooltip
+ *
+ * @since 2.4.0
+ */
+void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_cb, PidginTooltipPaint paint_cb);
+
+#endif
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Wed Jan 02 22:15:06 2008 +0000
@@ -699,6 +699,7 @@
     Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem"
     Delete "$INSTDIR\ca-certs\GTE_CyberTrust_Global_Root.pem"
     Delete "$INSTDIR\ca-certs\Microsoft_Secure_Server_Authority.pem"
+    Delete "$INSTDIR\ca-certs\StartCom_Free_SSL_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_Class3_Extended_Validation_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_Class3_Primary_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_RSA_Secure_Server_CA.pem"
--- a/pidgin/win32/winpidgin.c	Tue Dec 18 18:18:31 2007 +0000
+++ b/pidgin/win32/winpidgin.c	Wed Jan 02 22:15:06 2008 +0000
@@ -467,7 +467,7 @@
 
 			return FALSE;
 		} else
-			printf("Error (%d) accessing \"pidgin_is_running\" mutex.\n", err);
+			printf("Error (%u) accessing \"pidgin_is_running\" mutex.\n", (UINT) err);
 	}
 	return TRUE;
 }
--- a/po/de.po	Tue Dec 18 18:18:31 2007 +0000
+++ b/po/de.po	Wed Jan 02 22:15:06 2008 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-12-18 16:51+0100\n"
-"PO-Revision-Date: 2007-12-18 16:50+0100\n"
+"POT-Creation-Date: 2007-12-19 10:17+0100\n"
+"PO-Revision-Date: 2007-12-19 10:17+0100\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -1019,6 +1019,9 @@
 msgid "Open File..."
 msgstr "Datei öffnen..."
 
+msgid "Choose Location..."
+msgstr "Wählen Sie einen Ort..."
+
 msgid "Buddy logs in"
 msgstr "Buddy meldet sich an"
 
@@ -9630,7 +9633,7 @@
 msgstr "Verbindung zu %s nicht möglich: %s"
 
 #. 10053
-#, fuzzy, c-format
+#, c-format
 msgid "Connection interrupted by other software on your computer."
 msgstr ""
 "Die Verbindung wurde von einer anderen Software auf ihrem Computer "
@@ -10460,6 +10463,14 @@
 msgid "User has typed something and stopped"
 msgstr "Benutzer hat etwas getippt und wartet nun"
 
+#, c-format
+msgid ""
+"\n"
+"%s has typed something and stopped"
+msgstr ""
+"\n"
+"%s hat etwas getippt und wartet nun"
+
 #. Build the Send To menu
 msgid "S_end To"
 msgstr "S_enden an"
@@ -11425,6 +11436,33 @@
 
 #, c-format
 msgid ""
+"%s %s\n"
+"Usage: %s [OPTION]...\n"
+"\n"
+"  -c, --config=DIR    use DIR for config files\n"
+"  -d, --debug         print debugging messages to stdout\n"
+"  -h, --help          display this help and exit\n"
+"  -m, --multiple      do not ensure single instance\n"
+"  -n, --nologin       don't automatically login\n"
+"  -l, --login[=NAME]  automatically login (optional argument NAME specifies\n"
+"                      account(s) to use, separated by commas)\n"
+"  -v, --version       display the current version and exit\n"
+msgstr ""
+"%s %s\n"
+"Benutzung: %s [OPTION]...\n"
+"\n"
+"  -c, --config=VERZ   benutze VERZ als Konfigurationsverzeichnis\n"
+"  -d, --debug         gibt Debugging-Meldungen nach stdout aus\n"
+"  -h, --help          zeigt diese Hilfe und beendet das Programm\n"
+"  -m, --multiple      mehrere Instanzen erlauben\n"
+"  -n, --nologin       nicht automatisch anmelden\n"
+"  -l, --login[=NAME]  automatische Anmeldung (optionales Argument \n"
+"                      NAME bestimmt Konto(n), die benutzt werden\n"
+"                      sollen, getrennt durch Kommata)\n"
+"  -v, --version       zeigt aktuelle Version und beendet das Programm\n"
+
+#, c-format
+msgid ""
 "%s %s has segfaulted and attempted to dump a core file.\n"
 "This is a bug in the software and has happened through\n"
 "no fault of your own.\n"
@@ -13203,12 +13241,3 @@
 msgid "This plugin is useful for debbuging XMPP servers or clients."
 msgstr ""
 "Dieses Plugin ist nützlich zur Fehlersuche in XMPP-Servern oder -Clients."
-
-#~ msgid "%s changed status from %s to %s"
-#~ msgstr "%s hat den Status von %s zu %s geändert"
-
-#~ msgid "%s is now %s"
-#~ msgstr "%s ist jetzt %s"
-
-#~ msgid "%s is no longer %s"
-#~ msgstr "%s ist nicht mehr %s"
--- a/share/ca-certs/Makefile.am	Tue Dec 18 18:18:31 2007 +0000
+++ b/share/ca-certs/Makefile.am	Wed Jan 02 22:15:06 2008 +0000
@@ -3,6 +3,7 @@
 		Equifax_Secure_CA.pem \
 		GTE_CyberTrust_Global_Root.pem \
 		Microsoft_Secure_Server_Authority.pem \
+		StartCom_Free_SSL_CA.pem \
 		Verisign_RSA_Secure_Server_CA.pem \
 		Verisign_Class3_Primary_CA.pem
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/StartCom_Free_SSL_CA.pem	Wed Jan 02 22:15:06 2008 +0000
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFFjCCBH+gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsDELMAkGA1UEBhMCSUwx
+DzANBgNVBAgTBklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0
+Q29tIEx0ZC4xGjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBG
+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS
+YWRtaW5Ac3RhcnRjb20ub3JnMB4XDTA1MDMxNzE3Mzc0OFoXDTM1MDMxMDE3Mzc0
+OFowgbAxCzAJBgNVBAYTAklMMQ8wDQYDVQQIEwZJc3JhZWwxDjAMBgNVBAcTBUVp
+bGF0MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMRowGAYDVQQLExFDQSBBdXRob3Jp
+dHkgRGVwLjEpMCcGA1UEAxMgRnJlZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkxITAfBgkqhkiG9w0BCQEWEmFkbWluQHN0YXJ0Y29tLm9yZzCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEA7YRgACOeyEpRKSfeOqE5tWmrCbIvNP1h3D3TsM+x
+18LEwrHkllbEvqoUDufMOlDIOmKdw6OsWXuO7lUaHEe+o5c5s7XvIywI6Nivcy+5
+yYPo7QAPyHWlLzRMGOh2iCNJitu27Wjaw7ViKUylS7eYtAkUEKD4/mJ2IhULpNYI
+LzUCAwEAAaOCAjwwggI4MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgHmMB0G
+A1UdDgQWBBQcicOWzL3+MtUNjIExtpidjShkjTCB3QYDVR0jBIHVMIHSgBQcicOW
+zL3+MtUNjIExtpidjShkjaGBtqSBszCBsDELMAkGA1UEBhMCSUwxDzANBgNVBAgT
+BklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4x
+GjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBGcmVlIFNTTCBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSYWRtaW5Ac3Rh
+cnRjb20ub3JnggEAMB0GA1UdEQQWMBSBEmFkbWluQHN0YXJ0Y29tLm9yZzAdBgNV
+HRIEFjAUgRJhZG1pbkBzdGFydGNvbS5vcmcwEQYJYIZIAYb4QgEBBAQDAgAHMC8G
+CWCGSAGG+EIBDQQiFiBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAy
+BglghkgBhvhCAQQEJRYjaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL2NhLWNybC5j
+cmwwKAYJYIZIAYb4QgECBBsWGWh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy8wOQYJ
+YIZIAYb4QgEIBCwWKmh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9pbmRleC5waHA/
+YXBwPTExMTANBgkqhkiG9w0BAQQFAAOBgQBscSXhnjSRIe/bbL0BCFaPiNhBOlP1
+ct8nV0t2hPdopP7rPwl+KLhX6h/BquL/lp9JmeaylXOWxkjHXo0Hclb4g4+fd68p
+00UOpO6wNnQt8M2YI3s3S9r+UZjEHjQ8iP2ZO1CnwYszx8JSFhKVU2Ui77qLzmLb
+cCOxgN8aIDjnfg==
+-----END CERTIFICATE-----