changeset 16912:f9e7a7dacda3

merge of '45c1f04f71cf4ccbd70b37318474175c0cd4d0a9' and '7f6aca10dadc1d0a7dbbf6abc0d31311c53d0d70'
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 05 May 2007 18:14:48 +0000
parents 9f0686d3d91b (diff) 0a3ae69a25ea (current diff)
children 1dd4480dd80c 11a0f2b4ac83
files
diffstat 11 files changed, 342 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat May 05 18:14:18 2007 +0000
+++ b/ChangeLog	Sat May 05 18:14:48 2007 +0000
@@ -1,5 +1,9 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.0.1 (??/??/????):
+	* Buddy list update speedups when buddy icons are not being
+	  displayed.  (Scott Wolchok)
+
 version 2.0.0 (5/3/2007):
 	* The project has new names - libpurple for the core, Pidgin for the
 	  GTK+ UI and Finch for the ncurses based console UI (AOL LLC)
--- a/configure.ac	Sat May 05 18:14:18 2007 +0000
+++ b/configure.ac	Sat May 05 18:14:48 2007 +0000
@@ -46,8 +46,8 @@
 m4_define([purple_lt_current], [0])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [0])
-m4_define([purple_micro_version], [0])
-m4_define([purple_version_suffix], [])
+m4_define([purple_micro_version], [1])
+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]))
@@ -55,8 +55,8 @@
 m4_define([gnt_lt_current], [0])
 m4_define([gnt_major_version], [1])
 m4_define([gnt_minor_version], [0])
-m4_define([gnt_micro_version], [0])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_micro_version], [1])
+m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
 m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
@@ -1187,7 +1187,7 @@
 	PERL_CFLAGS=`$perlpath -MExtUtils::Embed -e ccopts 2>/dev/null`
 	if test "_$PERL_CFLAGS" = _ ; then
 		AC_MSG_RESULT([not found, building without perl.])
-		enable_perl = no
+		enable_perl=no
 	else
 		PERL_LIBS=`$perlpath -MExtUtils::Embed -e ldopts 2>/dev/null |$sedpath 's/-lgdbm //'`
 		PERL_LIBS=`echo $PERL_LIBS |$sedpath 's/-ldb //'`
--- a/doc/funniest_home_convos.txt	Sat May 05 18:14:18 2007 +0000
+++ b/doc/funniest_home_convos.txt	Sat May 05 18:14:48 2007 +0000
@@ -460,3 +460,15 @@
 23:59 -!- beta7 was kicked from #pidgin by elb [getting feisty, are we?]
 23:59 -!- mode/#pidgin [-o elb] by ChanServ
 23:59 <marv> elb: good job getting beta7 out
+
+14:07 <elb> rizzo: I actually prefer elb, and used it for a long time -- but 
+            switched to Paco-Paco because people kept asking me what an 'elb'
+            was
+14:08 <rizzo> elb: tell them it's short for "elbow", and don't explain any 
+              further
+14:08 <elb> rizzo: I actually had people ASK about that
+14:08 <elb> 'is your nick elbow?'
+14:08 <elb> "... yes"
+14:08 <elb> I mean, what do you say
+14:08 <Robot101> elb: was their nick "idi"?
+          
--- a/finch/finch.c	Sat May 05 18:14:18 2007 +0000
+++ b/finch/finch.c	Sat May 05 18:14:48 2007 +0000
@@ -303,7 +303,7 @@
 			char *text = g_strdup_printf(_(
 				"%s encountered errors migrating your settings "
 				"from %s to %s. Please investigate and complete the "
-				"migration by hand. Please report this error at http://developer.pidgin.im"), _("Finch"),
+				"migration by hand. Please report this error at http://developer.pidgin.im"), "Finch",
 				old, purple_user_dir());
 
 			g_free(old);
--- a/finch/gntconv.c	Sat May 05 18:14:18 2007 +0000
+++ b/finch/gntconv.c	Sat May 05 18:14:48 2007 +0000
@@ -37,13 +37,23 @@
 #include "gntplugin.h"
 #include "gntprefs.h"
 #include "gntstatus.h"
+#include "gntpounce.h"
 
 #include "gnt.h"
 #include "gntbox.h"
 #include "gntentry.h"
+#include "gntlabel.h"
+#include "gntmenu.h"
+#include "gntmenuitem.h"
+#include "gntmenuitemcheck.h"
 #include "gnttextview.h"
+#include "gnttree.h"
+#include "gntutils.h"
+#include "gntwindow.h"
 
 #define PREF_ROOT	"/finch/conversations"
+#define PREF_CHAT   PREF_ROOT "/chats"
+#define PREF_USERLIST PREF_CHAT "/userlist"
 
 #include "config.h"
 
@@ -266,6 +276,151 @@
 }
 
 static void
+clear_scrollback_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *ggc = ggconv;
+	gnt_text_view_clear(GNT_TEXT_VIEW(ggc->tv));
+}
+
+static void
+send_file_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *ggc = ggconv;
+	serv_send_file(purple_conversation_get_gc(ggc->active_conv),
+			purple_conversation_get_name(ggc->active_conv), NULL);
+}
+
+static void
+add_pounce_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *ggc = ggconv;
+	finch_pounce_editor_show(
+			purple_conversation_get_account(ggc->active_conv),
+			purple_conversation_get_name(ggc->active_conv), NULL);
+}
+
+static void
+get_info_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *ggc = ggconv;
+	serv_get_info(purple_conversation_get_gc(ggc->active_conv),
+			purple_conversation_get_name(ggc->active_conv));
+}
+
+static void
+toggle_timestamps_cb(GntMenuItem *item, gpointer ggconv)
+{
+	purple_prefs_set_bool(PREF_ROOT "/timestamps",
+		!purple_prefs_get_bool(PREF_ROOT "/timestamps"));
+}
+
+static void
+send_to_cb(GntMenuItem *m, gpointer n)
+{
+	PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account");
+	gchar *buddy = g_object_get_data(G_OBJECT(m), "purple_buddy_name");
+	PurpleConversation *conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, buddy);
+	finch_conversation_set_active(conv);
+}
+
+static void
+generate_send_to_menu(FinchConv *ggc)
+{
+	GntWidget *sub, *menu = ggc->menu;
+	GntMenuItem *item;
+	GSList *buds;
+	GList *list = NULL;
+
+	buds = purple_find_buddies(ggc->active_conv->account, ggc->active_conv->name);
+	if (!buds)
+		return;
+
+	item = gnt_menuitem_new(_("Send To"));
+	gnt_menu_add_item(GNT_MENU(menu), item);
+	sub = gnt_menu_new(GNT_MENU_POPUP);
+	gnt_menuitem_set_submenu(item, GNT_MENU(sub));
+
+	for (; buds; buds = buds->next) {
+		PurpleBlistNode *node = (PurpleBlistNode *)purple_buddy_get_contact((PurpleBuddy *)buds->data);
+		for (node = node->child; node != NULL; node = node->next) {
+			PurpleBuddy *buddy = (PurpleBuddy *)node;
+			PurpleAccount *account = purple_buddy_get_account(buddy);
+			if (purple_account_is_connected(account)) {
+				/* Use the PurplePresence to get unique buddies. */
+				PurplePresence *presence = purple_buddy_get_presence(buddy);
+				if (!g_list_find(list, presence))
+					list = g_list_prepend(list, presence);
+			}
+		}
+	}
+	for (list = g_list_last(list); list != NULL; list = list->prev) {
+		PurplePresence *pre = list->data;
+		PurpleBuddy *buddy = purple_presence_get_buddy(pre);
+		PurpleAccount *account = purple_buddy_get_account(buddy);
+		gchar *name = g_strdup(purple_buddy_get_name(buddy));
+		gchar *text = g_strdup_printf("%s (%s)", purple_buddy_get_name(buddy), purple_account_get_username(account));
+		item = gnt_menuitem_new(text);
+		g_free(text);
+		gnt_menu_add_item(GNT_MENU(sub), item);
+		gnt_menuitem_set_callback(item, send_to_cb, NULL);
+		g_object_set_data(G_OBJECT(item), "purple_account", account);
+		g_object_set_data_full(G_OBJECT(item), "purple_buddy_name", name, g_free);
+	}
+	g_list_free(list);
+	g_slist_free(buds);
+}
+
+static void
+gg_create_menu(FinchConv *ggc)
+{
+	GntWidget *menu, *sub;
+	GntMenuItem *item;
+
+	ggc->menu = menu = gnt_menu_new(GNT_MENU_TOPLEVEL);
+	gnt_window_set_menu(GNT_WINDOW(ggc->window), GNT_MENU(menu));
+
+	item = gnt_menuitem_new(_("Conversation"));
+	gnt_menu_add_item(GNT_MENU(menu), item);
+
+	sub = gnt_menu_new(GNT_MENU_POPUP);
+	gnt_menuitem_set_submenu(item, GNT_MENU(sub));
+
+	item = gnt_menuitem_new(_("Clear Scrollback"));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, clear_scrollback_cb, ggc);
+
+	item = gnt_menuitem_check_new(_("Show Timestamps"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+		purple_prefs_get_bool(PREF_ROOT "/timestamps"));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_timestamps_cb, ggc);
+
+	if (purple_conversation_get_type(ggc->active_conv) == PURPLE_CONV_TYPE_IM) {
+		item = gnt_menuitem_new(_("Send File"));
+		gnt_menu_add_item(GNT_MENU(sub), item);
+		gnt_menuitem_set_callback(item, send_file_cb, ggc);
+
+		item = gnt_menuitem_new(_("Add Buddy Pounce..."));
+		gnt_menu_add_item(GNT_MENU(sub), item);
+		gnt_menuitem_set_callback(item, add_pounce_cb, ggc);
+
+		item = gnt_menuitem_new(_("Get Info"));
+		gnt_menu_add_item(GNT_MENU(sub), item);
+		gnt_menuitem_set_callback(item, get_info_cb, ggc);
+
+		generate_send_to_menu(ggc);
+	}
+}
+
+static void
+create_conv_from_userlist(GntWidget *widget, FinchConv *fc)
+{
+	PurpleAccount *account = purple_conversation_get_account(fc->active_conv);
+	char *name = gnt_tree_get_selection_data(GNT_TREE(widget));
+	purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
+}
+
+static void
 finch_create_conversation(PurpleConversation *conv)
 {
 	FinchConv *ggc = conv->ui_data;
@@ -296,18 +451,37 @@
 	type = purple_conversation_get_type(conv);
 	title = get_conversation_title(conv, account);
 
-	ggc->window = gnt_box_new(FALSE, TRUE);
+	ggc->window = gnt_vwindow_new(FALSE);
 	gnt_box_set_title(GNT_BOX(ggc->window), title);
 	gnt_box_set_toplevel(GNT_BOX(ggc->window), TRUE);
 	gnt_box_set_pad(GNT_BOX(ggc->window), 0);
 	gnt_widget_set_name(ggc->window, "conversation-window");
 
+	gg_create_menu(ggc);
+
 	ggc->tv = gnt_text_view_new();
-	gnt_box_add_widget(GNT_BOX(ggc->window), ggc->tv);
 	gnt_widget_set_name(ggc->tv, "conversation-window-textview");
 	gnt_widget_set_size(ggc->tv, purple_prefs_get_int(PREF_ROOT "/size/width"),
 			purple_prefs_get_int(PREF_ROOT "/size/height"));
 
+	if (type == PURPLE_CONV_TYPE_CHAT) {
+		GntWidget *hbox, *tree;
+		FinchConvChat *fc = ggc->u.chat = g_new0(FinchConvChat, 1);
+		hbox = gnt_hbox_new(FALSE);
+		gnt_box_set_pad(GNT_BOX(hbox), 0);
+		tree = fc->userlist = gnt_tree_new();
+		gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_utf8_collate);
+		gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free);
+		GNT_WIDGET_SET_FLAGS(tree, GNT_WIDGET_NO_BORDER);
+		gnt_box_add_widget(GNT_BOX(hbox), ggc->tv);
+		gnt_box_add_widget(GNT_BOX(hbox), tree);
+		gnt_box_add_widget(GNT_BOX(ggc->window), hbox);
+		g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(create_conv_from_userlist), ggc);
+		gnt_widget_set_visible(tree, purple_prefs_get_bool(PREF_USERLIST));
+	} else {
+		gnt_box_add_widget(GNT_BOX(ggc->window), ggc->tv);
+	}
+
 	ggc->info = gnt_vbox_new(FALSE);
 	gnt_box_add_widget(GNT_BOX(ggc->window), ggc->info);
 
@@ -338,6 +512,7 @@
 	}
 
 	g_free(title);
+	gnt_box_give_focus_to_child(GNT_BOX(ggc->window), ggc->entry);
 }
 
 static void
@@ -350,6 +525,7 @@
 		ggc->active_conv = ggc->list->data;
 	
 	if (ggc->list == NULL) {
+		g_free(ggc->u.chat);
 		gnt_widget_destroy(ggc->window);
 		g_free(ggc);
 	}
@@ -380,8 +556,7 @@
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv), "\n", GNT_TEXT_FLAG_NORMAL);
 
 	/* Unnecessary to print the timestamp for delayed message */
-	if (!(flags & PURPLE_MESSAGE_DELAYED) &&
-			purple_prefs_get_bool("/finch/conversations/timestamps"))
+	if (purple_prefs_get_bool("/finch/conversations/timestamps"))
 		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv),
 					purple_utf8_strftime("(%H:%M:%S) ", localtime(&mtime)), GNT_TEXT_FLAG_DIM);
 
@@ -510,8 +685,11 @@
 	for (; users; users = users->next)
 	{
 		PurpleConvChatBuddy *cbuddy = users->data;
+		GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
 		gnt_entry_add_suggest(entry, cbuddy->name);
 		gnt_entry_add_suggest(entry, cbuddy->alias);
+		gnt_tree_add_row_after(tree, g_strdup(cbuddy->name),
+				gnt_tree_create_row(tree, cbuddy->alias), NULL, NULL);
 	}
 }
 
@@ -521,9 +699,13 @@
 	/* Update the name for string completion */
 	FinchConv *ggc = conv->ui_data;
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
+	GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
 	gnt_entry_remove_suggest(entry, old);
 	gnt_entry_add_suggest(entry, new_n);
 	gnt_entry_add_suggest(entry, new_a);
+	gnt_tree_remove(tree, (gpointer)old);
+	gnt_tree_add_row_after(tree, g_strdup(new_n),
+			gnt_tree_create_row(tree, new_a), NULL, NULL);
 }
 
 static void
@@ -532,8 +714,11 @@
 	/* Remove the name from string completion */
 	FinchConv *ggc = conv->ui_data;
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
-	for (; list; list = list->next)
+	for (; list; list = list->next) {
+		GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
 		gnt_entry_remove_suggest(entry, list->data);
+		gnt_tree_remove(tree, list->data);
+	}
 }
 
 static void
@@ -677,6 +862,23 @@
 	return PURPLE_CMD_STATUS_OK;
 }
 
+static PurpleCmdRet
+users_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, gpointer data)
+{
+	FinchConv *fc = conv->ui_data;
+	FinchConvChat *ch;
+	if (!fc)
+		return PURPLE_CMD_STATUS_FAILED;
+
+	ch = fc->u.chat;
+	gnt_widget_set_visible(ch->userlist,
+			(GNT_WIDGET_IS_FLAG_SET(ch->userlist, GNT_WIDGET_INVISIBLE)));
+	gnt_box_readjust(GNT_BOX(fc->window));
+	gnt_box_give_focus_to_child(GNT_BOX(fc->window), fc->entry);
+	purple_prefs_set_bool(PREF_USERLIST, !(GNT_WIDGET_IS_FLAG_SET(ch->userlist, GNT_WIDGET_INVISIBLE)));
+	return PURPLE_CMD_STATUS_OK;
+}
+
 void finch_conversation_init()
 {
 	purple_prefs_add_none(PREF_ROOT);
@@ -686,6 +888,8 @@
 	purple_prefs_add_none(PREF_ROOT "/position");
 	purple_prefs_add_int(PREF_ROOT "/position/x", 0);
 	purple_prefs_add_int(PREF_ROOT "/position/y", 0);
+	purple_prefs_add_none(PREF_CHAT);
+	purple_prefs_add_bool(PREF_USERLIST, FALSE);
 
 	/* Xerox the commands */
 	purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT,
@@ -703,6 +907,9 @@
 	purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL,
 	                  help_command_cb, _("help &lt;command&gt;:  Help on a specific command."), NULL);
+	purple_cmd_register("users", "", PURPLE_CMD_P_DEFAULT,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL,
+	                  users_command_cb, _("users:  Show the list of users in the chat."), NULL);
 
 	/* Now some commands to bring up some other windows */
 	purple_cmd_register("plugins", "", PURPLE_CMD_P_DEFAULT,
--- a/finch/libgnt/gntentry.c	Sat May 05 18:14:18 2007 +0000
+++ b/finch/libgnt/gntentry.c	Sat May 05 18:14:48 2007 +0000
@@ -44,6 +44,29 @@
 }
 
 static gboolean
+complete_suggest(GntEntry *entry, const char *text)
+{
+	gboolean changed = FALSE;
+	if (entry->word) {
+		char *s = get_beginning_of_word(entry);
+		const char *iter = text;
+		while (*iter && toupper(*s) == toupper(*iter)) {
+			if (*s != *iter)
+				changed = TRUE;
+			*s++ = *iter++;
+		}
+		if (*iter) {
+			gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
+			changed = TRUE;
+		}
+	} else {
+		gnt_entry_set_text_internal(entry, text);
+		changed = TRUE;
+	}
+	return changed;
+}
+
+static gboolean
 show_suggest_dropdown(GntEntry *entry)
 {
 	char *suggest = NULL;
@@ -51,6 +74,8 @@
 	int offset = 0, x, y;
 	int count = 0;
 	GList *iter;
+	const char *text = NULL;
+	const char *sgst = NULL;
 
 	if (entry->word)
 	{
@@ -85,24 +110,28 @@
 
 	for (count = 0, iter = entry->suggests; iter; iter = iter->next)
 	{
-		const char *text = iter->data;
+		text = iter->data;
 		if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len)
 		{
 			gnt_tree_add_row_after(GNT_TREE(entry->ddown), (gpointer)text,
 					gnt_tree_create_row(GNT_TREE(entry->ddown), text),
 					NULL, NULL);
 			count++;
+			sgst = text;
 		}
 	}
 	g_free(suggest);
 
-	if (count == 0)
-	{
+	if (count == 0) {
 		destroy_suggest(entry);
 		return FALSE;
+	} else if (count == 1) {
+		destroy_suggest(entry);
+		return complete_suggest(entry, sgst);
+	} else {
+		gnt_widget_draw(entry->ddown->parent);
 	}
 
-	gnt_widget_draw(entry->ddown->parent);
 	return TRUE;
 }
 
@@ -324,10 +353,7 @@
 {
 	GntEntry *entry = GNT_ENTRY(bind);
 	if (entry->ddown) {
-		if (g_list_length(GNT_TREE(entry->ddown)->list) == 1)
-			gnt_entry_key_pressed(GNT_WIDGET(entry), "\r");
-		else
-			gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down");
+		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down");
 		return TRUE;
 	}
 	return show_suggest_dropdown(entry);
@@ -512,33 +538,11 @@
 	}
 	else
 	{
-		if (text[0] == '\t')
-		{
-			if (entry->ddown)
-				destroy_suggest(entry);
-			else if (entry->suggests)
-				return show_suggest_dropdown(entry);
-
-			return FALSE;
-		}
-		else if (text[0] == '\r' && entry->ddown)
+		if ((text[0] == '\r' || text[0] == ' ') && entry->ddown)
 		{
 			char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown)));
 			destroy_suggest(entry);
-			if (entry->word)
-			{
-				char *s = get_beginning_of_word(entry);
-				char *iter = text;
-				while (*iter && toupper(*s) == toupper(*iter))
-				{
-					*s++ = *iter++;
-				}
-				gnt_entry_key_pressed(widget, iter);
-			}
-			else
-			{
-				gnt_entry_set_text_internal(entry, text);
-			}
+			complete_suggest(entry, text);
 			g_free(text);
 			entry_text_changed(entry);
 			return TRUE;
--- a/finch/libgnt/gntmain.c	Sat May 05 18:14:18 2007 +0000
+++ b/finch/libgnt/gntmain.c	Sat May 05 18:14:48 2007 +0000
@@ -12,13 +12,16 @@
 
 #include "gnt.h"
 #include "gntbox.h"
+#include "gntbutton.h"
 #include "gntcolors.h"
 #include "gntclipboard.h"
 #include "gntkeys.h"
+#include "gntlabel.h"
 #include "gntmenu.h"
 #include "gntstyle.h"
 #include "gnttree.h"
 #include "gntutils.h"
+#include "gntwindow.h"
 #include "gntwm.h"
 
 #include <panel.h>
@@ -303,6 +306,49 @@
 }
 
 static void
+exit_confirmed(gpointer null)
+{
+	gnt_bindable_perform_action_named(GNT_BINDABLE(wm), "wm-quit", NULL);
+}
+
+static void
+exit_win_close(GntWidget *w, GntWidget **win)
+{
+	*win = NULL;
+}
+
+static void
+ask_before_exit()
+{
+	static GntWidget *win = NULL;
+	GntWidget *bbox, *button;
+
+	if (win)
+		goto raise;
+
+	win = gnt_vwindow_new(FALSE);
+	gnt_box_add_widget(GNT_BOX(win), gnt_label_new("Are you sure you want to quit?"));
+	gnt_box_set_title(GNT_BOX(win), "Quit?");
+	gnt_box_set_alignment(GNT_BOX(win), GNT_ALIGN_MID);
+	g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(exit_win_close), &win);
+
+	bbox = gnt_hbox_new(FALSE);
+	gnt_box_add_widget(GNT_BOX(win), bbox);
+
+	button = gnt_button_new("Quit");
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(exit_confirmed), NULL);
+	gnt_box_add_widget(GNT_BOX(bbox), button);
+
+	button = gnt_button_new("Cancel");
+	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gnt_widget_destroy), win);
+	gnt_box_add_widget(GNT_BOX(bbox), button);
+
+	gnt_widget_show(win);
+raise:
+	gnt_wm_raise_window(wm, win);
+}
+
+static void
 sighandler(int sig)
 {
 	switch (sig) {
@@ -318,6 +364,10 @@
 		clean_pid();
 		signal(SIGCHLD, sighandler);
 		break;
+	case SIGINT:
+		ask_before_exit();
+		signal(SIGINT, sighandler);
+		break;
 	}
 }
 
@@ -387,6 +437,7 @@
 	signal(SIGWINCH, sighandler);
 #endif
 	signal(SIGCHLD, sighandler);
+	signal(SIGINT, sighandler);
 	signal(SIGPIPE, SIG_IGN);
 
 	g_type_init();
--- a/libpurple/protocols/jabber/message.c	Sat May 05 18:14:18 2007 +0000
+++ b/libpurple/protocols/jabber/message.c	Sat May 05 18:14:48 2007 +0000
@@ -86,7 +86,6 @@
 			PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
 					from, jm->js->gc->account);
 			if (conv && jid->node && jid->domain) {
-#if 0  /* String freeze... make sure to mark the message for translation */
 				char buf[256];
 				PurpleBuddy *buddy;
 
@@ -100,7 +99,7 @@
 					escaped = g_markup_escape_text(who, -1);
 
 					g_snprintf(buf, sizeof(buf),
-					           "%s has left the conversation.", escaped);
+					           _("%s has left the conversation."), escaped);
 
 					/* At some point when we restructure PurpleConversation,
 					 * this should be able to be implemented by removing the
@@ -108,7 +107,6 @@
 					purple_conversation_write(conv, "", buf,
 					                        PURPLE_MESSAGE_SYSTEM, time(NULL));
 				}
-#endif
 			}
 			serv_got_typing_stopped(jm->js->gc, from);
 			
--- a/pidgin/Makefile.am	Sat May 05 18:14:18 2007 +0000
+++ b/pidgin/Makefile.am	Sat May 05 18:14:48 2007 +0000
@@ -27,6 +27,7 @@
 		win32/nsis/pidgin-installer.nsi \
 		win32/nsis/pidgin-plugin.nsh \
 		win32/nsis/langmacros.nsh \
+		win32/nsis/translations/afrikaans.nsh \
 		win32/nsis/translations/albanian.nsh \
 		win32/nsis/translations/bulgarian.nsh \
 		win32/nsis/translations/catalan.nsh \
--- a/pidgin/gtkblist.c	Sat May 05 18:14:18 2007 +0000
+++ b/pidgin/gtkblist.c	Sat May 05 18:14:48 2007 +0000
@@ -4881,7 +4881,12 @@
 	status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
 						PIDGIN_STATUS_ICON_SMALL);
 
-	avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
+	/* Speed it up if we don't want buddy icons. */
+	if(biglist)
+		avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
+	else
+		avatar = NULL;
+
 	if (!avatar) {
 		g_object_ref(G_OBJECT(gtkblist->empty_avatar));
 		avatar = gtkblist->empty_avatar;
@@ -5059,6 +5064,7 @@
 		GdkPixbuf *avatar;
 		GdkPixbuf *emblem;
 		char *mark;
+		gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
 
 		if(!insert_node(list, node, &iter))
 			return;
@@ -5066,7 +5072,12 @@
 		status = pidgin_blist_get_status_icon(node,
 				 PIDGIN_STATUS_ICON_SMALL);
 		emblem = pidgin_blist_get_emblem(node);
-		avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
+
+		/* Speed it up if we don't want buddy icons. */
+		if(showicons)
+			avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
+		else
+			avatar = NULL;
 
 		mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
 
--- a/pidgin/gtksound.c	Sat May 05 18:14:18 2007 +0000
+++ b/pidgin/gtksound.c	Sat May 05 18:14:48 2007 +0000
@@ -418,6 +418,7 @@
 	if (!strcmp(method, "custom")) {
 		const char *sound_cmd;
 		char *command;
+		char *esc_filename;
 		GError *error = NULL;
 
 		sound_cmd = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/sound/command");
@@ -429,16 +430,19 @@
 			return;
 		}
 
+		esc_filename = g_strdup_printf("'%s'", filename);
+
 		if(strstr(sound_cmd, "%s"))
-			command = purple_strreplace(sound_cmd, "%s", filename);
+			command = purple_strreplace(sound_cmd, "%s", esc_filename);
 		else
-			command = g_strdup_printf("%s %s", sound_cmd, filename);
+			command = g_strdup_printf("%s %s", sound_cmd, esc_filename);
 
 		if(!g_spawn_command_line_async(command, &error)) {
 			purple_debug_error("gtksound", "sound command could not be launched: %s\n", error->message);
 			g_error_free(error);
 		}
 
+		g_free(esc_filename);
 		g_free(command);
 		return;
 	}