changeset 21947:c6b408d16011

merge of '791803b6696dd26a5193ad76f80dbc7c6cb2687a' and 'e7dd3b30ece9f95dd95249583812659312c457e6'
author Stu Tomlinson <stu@nosnilmot.com>
date Fri, 28 Dec 2007 02:49:23 +0000
parents 1981f277ab01 (current diff) ddd53c9174bc (diff)
children af2c4ef3e61d
files
diffstat 23 files changed, 384 insertions(+), 298 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Fri Dec 28 02:49:10 2007 +0000
+++ b/ChangeLog.API	Fri Dec 28 02:49:23 2007 +0000
@@ -21,8 +21,9 @@
 		  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 and
-		  pidgin_tooltip_show to simplify the process of drawing tooltips.
+		* pidgin_tooltip_setup_for_treeview, pidgin_tooltip_destroy,
+		  pidgin_tooltip_show and pidgin_tooltip_setup_for_widget to simplify
+		  the process of drawing tooltips.
 
 	Finch:
 		libgnt:
--- a/configure.ac	Fri Dec 28 02:49:10 2007 +0000
+++ b/configure.ac	Fri Dec 28 02:49:23 2007 +0000
@@ -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/libpurple/account.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/account.c	Fri Dec 28 02:49:23 2007 +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	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/blist.h	Fri Dec 28 02:49:23 2007 +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	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/certificate.c	Fri Dec 28 02:49:23 2007 +0000
@@ -1228,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.
@@ -1268,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);
@@ -1280,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)
 {
@@ -1292,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) ) {
@@ -1341,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) ) {
@@ -1440,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,
--- a/libpurple/cmds.h	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/cmds.h	Fri Dec 28 02:49:23 2007 +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/prefs.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/prefs.c	Fri Dec 28 02:49:23 2007 +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/msn/soap2.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/protocols/msn/soap2.c	Fri Dec 28 02:49:23 2007 +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/qq/group_opt.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Fri Dec 28 02:49:23 2007 +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/xmlnode.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/libpurple/xmlnode.c	Fri Dec 28 02:49:23 2007 +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);
--- a/pidgin/gtkaccount.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkaccount.c	Fri Dec 28 02:49:23 2007 +0000
@@ -1759,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
@@ -2102,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;
--- a/pidgin/gtkblist.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkblist.c	Fri Dec 28 02:49:23 2007 +0000
@@ -409,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);
 }
 
@@ -534,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);
@@ -2575,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);
 
@@ -2647,8 +2659,6 @@
 		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;
@@ -2687,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);
@@ -2704,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,
@@ -2712,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,
@@ -2777,8 +2791,15 @@
 	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)) {
+	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 +
@@ -3271,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);
 }
--- a/pidgin/gtkconv.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkconv.c	Fri Dec 28 02:49:23 2007 +0000
@@ -67,6 +67,7 @@
 #include "gtkthemes.h"
 #include "gtkutils.h"
 #include "pidginstock.h"
+#include "pidgintooltip.h"
 
 #include "gtknickcolors.h"
 
@@ -164,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 gboolean 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,
@@ -4434,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)
 {
@@ -4488,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,
@@ -4531,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) {
@@ -4579,103 +4587,6 @@
 	return FALSE;
 }
 
-static gboolean
-pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv)
-{
-	pidgin_blist_tooltip_destroy();
-	reset_tooltip();
-	return FALSE;
-}
-
-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)
 {
@@ -4705,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);
@@ -5214,9 +5123,6 @@
 		g_source_remove(gtkconv->attach.timer);
 	}
 
-	if (tooltip.gtkconv == gtkconv)
-		reset_tooltip();
-
 	g_free(gtkconv);
 }
 
@@ -6925,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);
--- a/pidgin/gtkft.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkft.c	Fri Dec 28 02:49:23 2007 +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"));
 		}
--- a/pidgin/gtkplugin.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkplugin.c	Fri Dec 28 02:49:23 2007 +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/gtkroomlist.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/gtkroomlist.c	Fri Dec 28 02:49:23 2007 +0000
@@ -356,8 +356,6 @@
 	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;
 
--- a/pidgin/minidialog.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/minidialog.c	Fri Dec 28 02:49:23 2007 +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	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/pidgincombobox.c	Fri Dec 28 02:49:23 2007 +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);
 
--- a/pidgin/pidgintooltip.c	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/pidgintooltip.c	Fri Dec 28 02:49:23 2007 +0000
@@ -43,15 +43,22 @@
 {
 	GtkWidget *widget;
 	gpointer userdata;
-	PidginTooltipCreateForTree create_tooltip;
 	PidginTooltipPaint paint_tooltip;
-	GtkTreePath *path;
+	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->path);
+	gtk_tree_path_free(data->common.treeview.path);
 	g_free(data);
 }
 
@@ -70,8 +77,11 @@
 static gboolean
 pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
 {
-	if (pidgin_tooltip.paint_tooltip)
+	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;
 }
 
@@ -184,14 +194,35 @@
 static void
 reset_data_treepath(PidginTooltipData *data)
 {
-	gtk_tree_path_free(data->path);
-	data->path = NULL;
+	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;
 
@@ -203,13 +234,13 @@
 		return;
 	}
 
-	if (data->path) {
-		if (gtk_tree_path_compare(data->path, path) == 0) {
+	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->path);
-		data->path = NULL;
+		gtk_tree_path_free(data->common.treeview.path);
+		data->common.treeview.path = NULL;
 	}
 
 	pidgin_tooltip_destroy();
@@ -218,24 +249,29 @@
 	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
 	pidgin_tooltip.paint_tooltip = data->paint_tooltip;
 
-	if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
-		pidgin_tooltip_destroy();
+	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->path = path;
-	g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy",
+	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;
-	pidgin_tooltip_draw(data);
+	if (GTK_IS_TREE_VIEW(tdata->widget))
+		pidgin_tooltip_draw_tree(data);
+	else
+		pidgin_tooltip_draw(data);
 	return FALSE;
 }
 
@@ -276,7 +312,7 @@
 }
 
 static gboolean
-row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
+widget_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
 {
 	pidgin_tooltip_destroy();
 	return FALSE;
@@ -288,12 +324,40 @@
 	PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
 	tdata->widget = tree;
 	tdata->userdata = userdata;
-	tdata->create_tooltip = create_tooltip;
+	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(row_leave_cb), NULL);
+	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;
+}
+
--- a/pidgin/pidgintooltip.h	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/pidgintooltip.h	Fri Dec 28 02:49:23 2007 +0000
@@ -77,6 +77,20 @@
 		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
  */
@@ -94,4 +108,5 @@
  */
 void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
 		PidginTooltipCreate create_cb, PidginTooltipPaint paint_cb);
+
 #endif
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Fri Dec 28 02:49:10 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Fri Dec 28 02:49:23 2007 +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/share/ca-certs/Makefile.am	Fri Dec 28 02:49:10 2007 +0000
+++ b/share/ca-certs/Makefile.am	Fri Dec 28 02:49:23 2007 +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	Fri Dec 28 02:49:23 2007 +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-----