changeset 8143:8633dc570442

[gaim-migrate @ 8851] sf cvs fing sucks. This is the rest of the accessibility stuff from that cool Nathan guy. It makes shift+f10 bring up context menus. Please check over stuff and make sure neither of us broke anything. Also, someone think of a good way to phrase that thing about this that's in the ChangeLog. Thank you, and have a nice day. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Mon, 19 Jan 2004 22:40:17 +0000
parents f2919ca98e78
children 0163686079d5
files src/gtkblist.c src/gtkconv.c src/gtkroomlist.c src/gtkutils.c src/gtkutils.h src/protocols/oscar/oscar.c
diffstat 6 files changed, 544 insertions(+), 232 deletions(-) [+]
line wrap: on
line diff
--- a/src/gtkblist.c	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/gtkblist.c	Mon Jan 19 22:40:17 2004 +0000
@@ -873,13 +873,178 @@
 	return FALSE;
 }
 
-static gboolean gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer null)
+static GtkWidget *
+create_group_menu (GaimBlistNode *node)
+{
+	GtkWidget *menu;
+
+	menu = gtk_menu_new();
+	gaim_new_item_from_stock(menu, _("Add a _Buddy"), GTK_STOCK_ADD,
+				 G_CALLBACK(gaim_gtk_blist_add_buddy_cb), node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("Add a C_hat"), GTK_STOCK_ADD,
+				 G_CALLBACK(gaim_gtk_blist_add_chat_cb), node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
+				 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("_Rename"), NULL,
+				 G_CALLBACK(show_rename_group), node, 0, 0, NULL);
+	return menu;
+}
+
+static GtkWidget *
+create_chat_menu (GaimBlistNode *node)
+{
+	GtkWidget *menu;
+	gboolean autojoin = gaim_blist_node_get_bool(node,
+						     "gtk-autojoin");
+
+	menu = gtk_menu_new();
+	gaim_new_item_from_stock(menu, _("_Join"), GAIM_STOCK_CHAT,
+				 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
+	gaim_new_check_item(menu, _("Auto-Join"),
+			    G_CALLBACK(gtk_blist_menu_autojoin_cb), node,
+				autojoin);
+	gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_EDIT,
+				 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
+				 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
+	return menu;
+}
+
+static GtkWidget *
+create_contact_menu (GaimBlistNode *node)
+{
+	GtkWidget *menu;
+
+	menu = gtk_menu_new();
+	gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_EDIT,
+				 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
+				 G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
+				 node, 0, 0, NULL);
+	gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
+				 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
+	return menu;
+}
+
+static GtkWidget *
+create_buddy_menu (GaimBlistNode *node,
+		   GaimBuddy *b,
+		   GaimPlugin *prpl,
+		   GaimPluginProtocolInfo *prpl_info)
+{
+	struct _gaim_gtk_blist_node *gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
+	GtkWidget *menu;
+	GtkWidget *menuitem;
+	gboolean show_offline = gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies");
+	
+	menu = gtk_menu_new();
+	make_buddy_menu(menu, prpl_info, b);
+
+	if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
+		gaim_separator(menu);
+	
+		if(gtknode->contact_expanded) {
+			gaim_new_item_from_stock(menu, _("_Collapse"),
+						 GTK_STOCK_ZOOM_OUT,
+						 G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
+						 node, 0, 0, NULL);
+		} else {
+			gaim_new_item_from_stock(menu, _("_Expand"),
+						 GTK_STOCK_ZOOM_IN,
+						 G_CALLBACK(gaim_gtk_blist_expand_contact_cb), node,
+						 0, 0, NULL);
+		}
+		if(node->child->next) {
+			GaimBlistNode *bnode;
+
+			for(bnode = node->child; bnode; bnode = bnode->next) {
+				GaimBuddy *buddy = (GaimBuddy*)bnode;
+				GtkWidget *submenu;
+				GtkWidget *image;
+
+				if(buddy == b)
+					continue;
+				if(!buddy->account->gc)
+					continue;
+				if(!show_offline && !GAIM_BUDDY_IS_ONLINE(buddy))
+					continue;
+
+				menuitem = gtk_image_menu_item_new_with_label(buddy->name);
+				image = gtk_image_new_from_pixbuf(
+					gaim_gtk_blist_get_status_icon(bnode,
+								       GAIM_STATUS_ICON_SMALL));
+				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
+							      image);
+				gtk_widget_show(image);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+				gtk_widget_show(menuitem);
+
+				submenu = gtk_menu_new();
+				gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+				gtk_widget_show(submenu);
+
+				prpl = gaim_find_prpl(gaim_account_get_protocol_id(buddy->account));
+				prpl_info = prpl ? GAIM_PLUGIN_PROTOCOL_INFO(prpl) : NULL;
+
+				make_buddy_menu(submenu, prpl_info, buddy);
+			}
+		}
+	}
+	return menu;
+}
+
+static gboolean
+gaim_gtk_blist_show_context_menu(GaimBlistNode *node,
+								 GtkMenuPositionFunc func,
+								 GtkWidget *tv,
+								 guint button,
+								 guint32 time)
+{
+	struct _gaim_gtk_blist_node *gtknode;
+	GtkWidget *menu = NULL;
+	gboolean handled = FALSE;
+
+	gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
+
+	/* Create a menu based on the thing we right-clicked on */
+	if (GAIM_BLIST_NODE_IS_GROUP(node)) {
+		menu = create_group_menu(node);
+	} else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+		menu = create_chat_menu(node);
+	} else if ((GAIM_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
+		menu = create_contact_menu(node);
+	} else if (GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		GaimBuddy *b;
+		GaimPlugin *prpl = NULL;
+		GaimPluginProtocolInfo *prpl_info = NULL;
+
+		if (GAIM_BLIST_NODE_IS_CONTACT(node))
+			b = gaim_contact_get_priority_buddy((GaimContact*)node);
+		else
+			b = (GaimBuddy *)node;
+
+		prpl = gaim_find_prpl(gaim_account_get_protocol_id(b->account));
+		if (prpl != NULL)
+			prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+		menu = create_buddy_menu(node, b, prpl, prpl_info);
+	}
+
+	/* Now display the menu */
+	if (menu != NULL) {
+		gtk_widget_show_all(menu);
+		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
+		handled = TRUE;
+	}
+
+	return handled;
+}
+
+static gboolean gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
 {
 	GtkTreePath *path;
 	GaimBlistNode *node;
 	GValue val = { 0, };
 	GtkTreeIter iter;
-	GtkWidget *menu, *menuitem;
 	GtkTreeSelection *sel;
 	GaimPlugin *prpl = NULL;
 	GaimPluginProtocolInfo *prpl_info = NULL;
@@ -890,152 +1055,50 @@
 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
 		return FALSE;
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
-	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
+	gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
 	node = g_value_get_pointer(&val);
 	gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
 
-	if (GAIM_BLIST_NODE_IS_GROUP(node) &&
-			event->button == 3 && event->type == GDK_BUTTON_PRESS) {
-		menu = gtk_menu_new();
-		gaim_new_item_from_stock(menu, _("Add a _Buddy"), GTK_STOCK_ADD,
-				G_CALLBACK(gaim_gtk_blist_add_buddy_cb), node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("Add a C_hat"), GTK_STOCK_ADD,
-				G_CALLBACK(gaim_gtk_blist_add_chat_cb), node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
-				G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("_Rename"), NULL,
-				G_CALLBACK(show_rename_group), node, 0, 0, NULL);
-		gtk_widget_show_all(menu);
-		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
-
-		handled = TRUE;
-	} else if (GAIM_BLIST_NODE_IS_CHAT(node) &&
-			event->button == 3 && event->type == GDK_BUTTON_PRESS) {
-		GaimChat *chat = (GaimChat *)node;
-		gboolean autojoin = gaim_blist_node_get_bool((GaimBlistNode*)chat,
-				"gtk-autojoin");
-
-		menu = gtk_menu_new();
-		gaim_new_item_from_stock(menu, _("_Join"), GAIM_STOCK_CHAT,
-				G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
-		gaim_new_check_item(menu, _("Auto-Join"),
-				G_CALLBACK(gtk_blist_menu_autojoin_cb), node,
-				autojoin);
-		gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_EDIT,
-				G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
-				G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
-		gtk_widget_show_all(menu);
-		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
-
-		handled = TRUE;
-	} else if (GAIM_BLIST_NODE_IS_CONTACT(node) &&
-			event->state & GDK_CONTROL_MASK && event->button == 2 &&
-			event->type == GDK_BUTTON_PRESS) {
-		if(gtknode->contact_expanded)
+	/* Right click draws a context menu */
+	if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
+		handled = gaim_gtk_blist_show_context_menu(node, NULL, tv, 3, event->time);
+
+	/* CTRL+middle click expands or collapse a contact */
+	} else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
+			   (event->state & GDK_CONTROL_MASK) && (GAIM_BLIST_NODE_IS_CONTACT(node))) {
+		if (gtknode->contact_expanded)
 			gaim_gtk_blist_collapse_contact_cb(NULL, node);
 		else
 			gaim_gtk_blist_expand_contact_cb(NULL, node);
 		handled = TRUE;
-	} else if (GAIM_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded
-			&& event->button == 3 && event->type == GDK_BUTTON_PRESS) {
-		menu = gtk_menu_new();
-		gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_EDIT,
-				G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
-				G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
-				node, 0, 0, NULL);
-		gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
-				G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
-		gtk_widget_show_all(menu);
-		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
-		handled = TRUE;
-	} else if (GAIM_BLIST_NODE_IS_CONTACT(node) ||
-			GAIM_BLIST_NODE_IS_BUDDY(node)) {
+
+	/* Double middle click gets info */
+	} else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
+			   ((GAIM_BLIST_NODE_IS_CONTACT(node)) || (GAIM_BLIST_NODE_IS_BUDDY(node)))) {
 		GaimBuddy *b;
 		if(GAIM_BLIST_NODE_IS_CONTACT(node))
 			b = gaim_contact_get_priority_buddy((GaimContact*)node);
 		else
 			b = (GaimBuddy *)node;
 
-		/* Protocol specific options */
 		prpl = gaim_find_prpl(gaim_account_get_protocol_id(b->account));
-
 		if (prpl != NULL)
 			prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
 
-		if(event->button == 2 && event->type == GDK_2BUTTON_PRESS) {
-			if (prpl && prpl_info->get_info)
-				serv_get_info(b->account->gc, b->name);
-			handled = TRUE;
-		} else if(event->button == 3 && event->type == GDK_BUTTON_PRESS) {
-			gboolean show_offline = gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies");
-			menu = gtk_menu_new();
-			make_buddy_menu(menu, prpl_info, b);
-
-			if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
-				gaim_separator(menu);
-
-				if(gtknode->contact_expanded) {
-					gaim_new_item_from_stock(menu, _("_Collapse"),
-							GTK_STOCK_ZOOM_OUT,
-							G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
-							node, 0, 0, NULL);
-				} else {
-					gaim_new_item_from_stock(menu, _("_Expand"),
-							GTK_STOCK_ZOOM_IN,
-							G_CALLBACK(gaim_gtk_blist_expand_contact_cb), node,
-							0, 0, NULL);
-				}
-				if(node->child->next) {
-					GaimBlistNode *bnode;
-
-					for(bnode = node->child; bnode; bnode = bnode->next) {
-						GaimBuddy *buddy = (GaimBuddy*)bnode;
-						GtkWidget *submenu;
-						GtkWidget *image;
-
-						if(buddy == b)
-							continue;
-						if(!buddy->account->gc)
-							continue;
-						if(!show_offline && !GAIM_BUDDY_IS_ONLINE(buddy))
-							continue;
-
-
-						menuitem = gtk_image_menu_item_new_with_label(buddy->name);
-						image = gtk_image_new_from_pixbuf(
-								gaim_gtk_blist_get_status_icon(bnode,
-									GAIM_STATUS_ICON_SMALL));
-						gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
-								image);
-						gtk_widget_show(image);
-						gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
-						gtk_widget_show(menuitem);
-
-						submenu = gtk_menu_new();
-						gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
-						gtk_widget_show(submenu);
-
-						prpl = gaim_find_prpl(gaim_account_get_protocol_id(buddy->account));
-						prpl_info = prpl ? GAIM_PLUGIN_PROTOCOL_INFO(prpl) : NULL;
-
-						make_buddy_menu(submenu, prpl_info, buddy);
-					}
-				}
-			}
-
-			gtk_widget_show_all(menu);
-
-			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
-					event->time);
-
-			handled = TRUE;
-		}
+		if (prpl && prpl_info->get_info)
+			serv_get_info(b->account->gc, b->name);
+		handled = TRUE;
 	}
 
-#if (1)  /* This code only exists because GTK doesn't work.  If we return FALSE here, as would be normal
-	  * the event propoagates down and somehow gets interpreted as the start of a drag event. */
+#if (1)
+	/*
+	 * This code only exists because GTK doesn't work.  If we return
+	 * FALSE here, as would be normal the event propoagates down and
+	 * somehow gets interpreted as the start of a drag event.
+	 *
+	 * Um, isn't it _normal_ to return TRUE here?  Since the event
+	 * was handled?  --Mark
+	 */
 	if(handled) {
 		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
 		gtk_tree_selection_select_path(sel, path);
@@ -1044,9 +1107,33 @@
 	}
 #endif
 	gtk_tree_path_free(path);
+
 	return FALSE;
 }
 
+static gboolean
+gaim_gtk_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
+{
+	GaimBlistNode *node;
+	GValue val = { 0, };
+	GtkTreeIter iter;
+	GtkTreeSelection *sel;
+	gboolean handled = FALSE;
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
+	if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
+		return FALSE;
+
+	gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel),
+							 &iter, NODE_COLUMN, &val);
+	node = g_value_get_pointer(&val);
+
+	/* Shift+F10 draws a context menu */
+	handled = gaim_gtk_blist_show_context_menu(node, gaim_gtk_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
+
+	return handled;
+}
+
 static void gaim_gtk_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
 {
 	gaim_prefs_set_bool("/gaim/gtk/blist/show_empty_groups",
@@ -1864,7 +1951,7 @@
 	{ N_("/_Buddies"), NULL, NULL, 0, "<Branch>" },
 	{ N_("/Buddies/New Instant _Message..."), "<CTL>M", show_im_dialog, 0, "<StockItem>", GAIM_STOCK_IM },
 	{ N_("/Buddies/Join a _Chat..."), "<CTL>C", join_chat, 0, "<StockItem>", GAIM_STOCK_CHAT },
-	{ N_("/Buddies/Get _User Info..."), "<CTL>J", show_info_dialog, 0, "<StockItem>", GAIM_STOCK_INFO },
+	{ N_("/Buddies/Get User _Info..."), "<CTL>I", show_info_dialog, 0, "<StockItem>", GAIM_STOCK_INFO },
 	{ "/Buddies/sep1", NULL, NULL, 0, "<Separator>" },
 	{ N_("/Buddies/Show _Offline Buddies"), NULL, gaim_gtk_blist_edit_mode_cb, 1, "<CheckItem>"},
 	{ N_("/Buddies/Show _Empty Groups"), NULL, gaim_gtk_blist_show_empty_groups_cb, 1, "<CheckItem>"},
@@ -2668,6 +2755,7 @@
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(gaim_gtk_blist_popup_menu_cb), NULL);
 
 	/* Enable CTRL+F searching */
 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
--- a/src/gtkconv.c	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/gtkconv.c	Mon Jan 19 22:40:17 2004 +0000
@@ -1134,12 +1134,120 @@
 	gtk_widget_grab_focus(GAIM_GTK_CONVERSATION(conv)->entry);
 }
 
+static GtkWidget *
+create_chat_menu(GaimConversation *conv, gchar *who,
+				 GaimPluginProtocolInfo *prpl_info, GaimConnection *gc)
+{
+	static GtkWidget *menu = NULL;
+	GtkWidget *button;
+
+	/*
+	 * If a menu already exists, destroy it before creating a new one,
+	 * thus freeing-up the memory it occupied.
+	 */
+	if (menu)
+		gtk_widget_destroy(menu);
+
+	menu = gtk_menu_new();
+
+	button = gtk_menu_item_new_with_label(_("IM"));
+	g_signal_connect(G_OBJECT(button), "activate",
+						 G_CALLBACK(menu_chat_im_cb), conv);
+	g_object_set_data(G_OBJECT(button), "user_data", who);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
+	gtk_widget_show(button);
+
+	if (gaim_conv_chat_is_user_ignored(GAIM_CONV_CHAT(conv), who))
+		button = gtk_menu_item_new_with_label(_("Un-Ignore"));
+	else
+		button = gtk_menu_item_new_with_label(_("Ignore"));
+
+	g_signal_connect(G_OBJECT(button), "activate",
+						 G_CALLBACK(ignore_cb), conv);
+	g_object_set_data(G_OBJECT(button), "user_data", who);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
+	gtk_widget_show(button);
+
+	if (gc && prpl_info->get_info) {
+		button = gtk_menu_item_new_with_label(_("Info"));
+		g_signal_connect(G_OBJECT(button), "activate",
+							 G_CALLBACK(menu_chat_info_cb), conv);
+		g_object_set_data(G_OBJECT(button), "user_data", who);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
+		gtk_widget_show(button);
+	}
+
+	if (gc && prpl_info->get_cb_away) {
+		button = gtk_menu_item_new_with_label(_("Get Away Msg"));
+		g_signal_connect(G_OBJECT(button), "activate",
+							 G_CALLBACK(menu_chat_get_away_cb), conv);
+		g_object_set_data(G_OBJECT(button), "user_data", who);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
+		gtk_widget_show(button);
+	}
+
+	/* Added by Jonas <jonas@birme.se> */
+	if (gc) {
+		if (gaim_find_buddy(gc->account, who))
+			button = gtk_menu_item_new_with_label(_("Remove"));
+		else
+			button = gtk_menu_item_new_with_label(_("Add"));
+
+		g_signal_connect(G_OBJECT(button), "activate",
+				 G_CALLBACK(menu_chat_add_remove_cb), conv);
+
+		g_object_set_data(G_OBJECT(button), "user_data", who);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
+		gtk_widget_show(button);
+	}
+	/* End Jonas */
+
+	return menu;
+}
+
+
+static gint
+gtkconv_chat_popup_menu_cb(GtkWidget *widget, GaimConversation *conv)
+{
+	GaimGtkConversation *gtkconv;
+	GaimPluginProtocolInfo *prpl_info = NULL;
+	GaimGtkChatPane *gtkchat;
+	GaimConnection *gc;
+	GaimAccount *account;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GtkWidget *menu;
+	gchar *who;
+
+	gtkconv = GAIM_GTK_CONVERSATION(conv);
+	gtkchat = gtkconv->u.chat;
+	account = gaim_conversation_get_account(conv);
+	gc      = account->gc;
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+
+	if (gc != NULL)
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat->list));
+	if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
+		return FALSE;
+
+	gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &who, -1);
+	menu = create_chat_menu (conv, who, prpl_info, gc);
+	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, gaim_gtk_treeview_popup_menu_position_func, widget, 0, GDK_CURRENT_TIME);
+
+	return TRUE;
+}
+
+
 static gint
 right_click_chat_cb(GtkWidget *widget, GdkEventButton *event,
 					GaimConversation *conv)
 {
+	GaimGtkConversation *gtkconv;
 	GaimPluginProtocolInfo *prpl_info = NULL;
-	GaimGtkConversation *gtkconv;
 	GaimGtkChatPane *gtkchat;
 	GaimConnection *gc;
 	GaimAccount *account;
@@ -1175,71 +1283,7 @@
 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
 		chat_do_im(conv, who);
 	} else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
-		static GtkWidget *menu = NULL;
-		GtkWidget *button;
-
-		/*
-		 * If a menu already exists, destroy it before creating a new one,
-		 * thus freeing-up the memory it occupied.
-		 */
-
-		if (menu)
-			gtk_widget_destroy(menu);
-
-		menu = gtk_menu_new();
-
-		button = gtk_menu_item_new_with_label(_("IM"));
-		g_signal_connect(G_OBJECT(button), "activate",
-						 G_CALLBACK(menu_chat_im_cb), conv);
-		g_object_set_data(G_OBJECT(button), "user_data", who);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
-		gtk_widget_show(button);
-
-		if (gaim_conv_chat_is_user_ignored(GAIM_CONV_CHAT(conv), who))
-			button = gtk_menu_item_new_with_label(_("Un-Ignore"));
-		else
-			button = gtk_menu_item_new_with_label(_("Ignore"));
-
-		g_signal_connect(G_OBJECT(button), "activate",
-						 G_CALLBACK(ignore_cb), conv);
-		g_object_set_data(G_OBJECT(button), "user_data", who);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
-		gtk_widget_show(button);
-
-		if (gc && prpl_info->get_info) {
-			button = gtk_menu_item_new_with_label(_("Info"));
-			g_signal_connect(G_OBJECT(button), "activate",
-							 G_CALLBACK(menu_chat_info_cb), conv);
-			g_object_set_data(G_OBJECT(button), "user_data", who);
-			gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
-			gtk_widget_show(button);
-		}
-
-		if (gc && prpl_info->get_cb_away) {
-			button = gtk_menu_item_new_with_label(_("Get Away Msg"));
-			g_signal_connect(G_OBJECT(button), "activate",
-							 G_CALLBACK(menu_chat_get_away_cb), conv);
-			g_object_set_data(G_OBJECT(button), "user_data", who);
-			gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
-			gtk_widget_show(button);
-		}
-
-		/* Added by Jonas <jonas@birme.se> */
-		if (gc) {
-			if (gaim_find_buddy(gc->account, who))
-				button = gtk_menu_item_new_with_label(_("Remove"));
-			else
-				button = gtk_menu_item_new_with_label(_("Add"));
-
-			g_signal_connect(G_OBJECT(button), "activate",
-							 G_CALLBACK(menu_chat_add_remove_cb), conv);
-
-			g_object_set_data(G_OBJECT(button), "user_data", who);
-			gtk_menu_shell_append(GTK_MENU_SHELL(menu), button);
-			gtk_widget_show(button);
-		}
-		/* End Jonas */
-
+		GtkWidget *menu = create_chat_menu (conv, who, prpl_info, gc);
 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
 					   event->button, event->time);
 	}
@@ -1376,15 +1420,15 @@
 				break;
 
 			case GDK_Page_Down:
-			case '[':
-				gaim_conv_window_switch_conversation(win,  (curconv + numconvs - 1) % numconvs);
+			case ']':
+				gaim_conv_window_switch_conversation(win,  (curconv + 1) % numconvs);
 
 				return TRUE;
 				break;
 
 			case GDK_Page_Up:
-			case ']':
-				gaim_conv_window_switch_conversation(win,  (curconv + 1) % numconvs);
+			case '[':
+				gaim_conv_window_switch_conversation(win,  (curconv + numconvs - 1) % numconvs);
 
 				return TRUE;
 				break;
@@ -3588,6 +3632,7 @@
 	GtkListStore *ls;
 	GtkCellRenderer *rend;
 	GtkTreeViewColumn *col;
+	GList *focus_chain;
 
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
 	gtkchat = gtkconv->u.chat;
@@ -3695,6 +3740,8 @@
 
 	g_signal_connect(G_OBJECT(list), "button_press_event",
 					 G_CALLBACK(right_click_chat_cb), conv);
+	g_signal_connect(G_OBJECT(list), "popup-menu",
+			 G_CALLBACK(gtkconv_chat_popup_menu_cb), conv);
 
 	gtk_tree_view_append_column(GTK_TREE_VIEW(list), col);
 
@@ -3804,6 +3851,8 @@
 
 	setup_chat_buttons(conv, gtkconv->bbox);
 
+	focus_chain = g_list_prepend (NULL, sw);
+	gtk_container_set_focus_chain (GTK_CONTAINER(vbox), focus_chain);
 	return vpaned;
 }
 
@@ -3816,6 +3865,7 @@
 	GtkWidget *vbox;
 	GtkWidget *vbox2;
 	GtkWidget *sw;
+	GList *focus_chain;
 
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
 	gtkim   = gtkconv->u.im;
@@ -3912,6 +3962,9 @@
 
 	setup_im_buttons(conv, gtkconv->bbox);
 
+	focus_chain = g_list_prepend (NULL, sw);
+	gtk_container_set_focus_chain (GTK_CONTAINER(vbox2), focus_chain);
+
 	return paned;
 }
 
@@ -4221,9 +4274,6 @@
 		gtkconv->show_formatting_toolbar = gaim_prefs_get_bool(
 				"/gaim/gtk/conversations/show_formatting_toolbar");
 
-		g_signal_connect_swapped(G_OBJECT(pane), "focus",
-					 G_CALLBACK(gtk_widget_grab_focus),
-					 gtkconv->entry);
 	}
 
 	gtkconv->tabby = tabby = gtk_hbox_new(FALSE, 5);
--- a/src/gtkroomlist.c	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/gtkroomlist.c	Mon Jan 19 22:40:17 2004 +0000
@@ -321,7 +321,7 @@
 	gtk_widget_show(bbox);
 
 	/* Get list button */
-	button = gtk_button_new_with_mnemonic(_("Get _list"));
+	button = gtk_button_new_with_mnemonic(_("Get _List"));
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	gtk_widget_show(button);
 	dialog->list_button = button;
--- a/src/gtkutils.c	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/gtkutils.c	Mon Jan 19 22:40:17 2004 +0000
@@ -1389,3 +1389,175 @@
 	atk_relation_set_add (set, relation);
 	g_object_unref (relation);
 }
+
+static void
+gaim_gtk_menu_position_func(GtkMenu *menu,
+							gint *x,
+							gint *y,
+							gboolean *push_in,
+							gpointer data)
+{
+	GtkWidget *widget;
+	GtkRequisition requisition;
+	GdkScreen *screen;
+	GdkRectangle monitor;
+	gint monitor_num;
+	gint space_left, space_right, space_above, space_below;
+	gint needed_width;
+	gint needed_height;
+	gint xthickness;
+	gint ythickness;
+	gboolean rtl;
+
+	g_return_if_fail(GTK_IS_MENU(menu));
+
+	widget     = GTK_WIDGET(menu);
+	screen     = gtk_widget_get_screen(widget);
+	xthickness = widget->style->xthickness;
+	ythickness = widget->style->ythickness;
+	rtl        = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
+
+	/*
+	 * We need the requisition to figure out the right place to
+	 * popup the menu. In fact, we always need to ask here, since
+	 * if a size_request was queued while we weren't popped up,
+	 * the requisition won't have been recomputed yet.
+	 */
+	gtk_widget_size_request (widget, &requisition);
+
+	monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
+
+	push_in = FALSE;
+  
+	/*
+	 * The placement of popup menus horizontally works like this (with
+	 * RTL in parentheses)
+	 *
+	 * - If there is enough room to the right (left) of the mouse cursor,
+	 *   position the menu there.
+	 * 
+	 * - Otherwise, if if there is enough room to the left (right) of the 
+	 *   mouse cursor, position the menu there.
+	 * 
+	 * - Otherwise if the menu is smaller than the monitor, position it
+	 *   on the side of the mouse cursor that has the most space available
+	 *
+	 * - Otherwise (if there is simply not enough room for the menu on the
+	 *   monitor), position it as far left (right) as possible.
+	 *
+	 * Positioning in the vertical direction is similar: first try below
+	 * mouse cursor, then above.
+	 */
+	gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+	space_left = *x - monitor.x;
+	space_right = monitor.x + monitor.width - *x - 1;
+	space_above = *y - monitor.y;
+	space_below = monitor.y + monitor.height - *y - 1;
+
+	/* position horizontally */
+
+	/* the amount of space we need to position the menu. Note the
+	 * menu is offset "xthickness" pixels 
+	 */
+	needed_width = requisition.width - xthickness;
+
+	if (needed_width <= space_left ||
+	    needed_width <= space_right)
+	{
+		if ((rtl  && needed_width <= space_left) ||
+		    (!rtl && needed_width >  space_right))
+		{
+			/* position left */
+			*x = *x + xthickness - requisition.width + 1;
+		}
+		else
+		{
+			/* position right */
+			*x = *x - xthickness;
+		}
+
+		/* x is clamped on-screen further down */
+	}
+	else if (requisition.width <= monitor.width)
+	{
+		/* the menu is too big to fit on either side of the mouse
+		 * cursor, but smaller than the monitor. Position it on
+		 * the side that has the most space
+		 */
+		if (space_left > space_right)
+		{
+			/* left justify */
+			*x = monitor.x;
+		}
+		else
+		{
+			/* right justify */
+			*x = monitor.x + monitor.width - requisition.width;
+		}
+	}
+	else /* menu is simply too big for the monitor */
+	{
+		if (rtl)
+		{
+			/* right justify */
+			*x = monitor.x + monitor.width - requisition.width;
+		}
+		else
+		{
+			/* left justify */
+			*x = monitor.x;
+		}
+	}
+
+	/* Position vertically. The algorithm is the same as above, but
+	 * simpler because we don't have to take RTL into account.
+	 */
+	needed_height = requisition.height - ythickness;
+
+	if (needed_height <= space_above ||
+	    needed_height <= space_below)
+	{
+		if (needed_height <= space_below)
+			*y = *y - ythickness;
+		else
+			*y = *y + ythickness - requisition.height + 1;
+	  
+		*y = CLAMP (*y, monitor.y,
+			   monitor.y + monitor.height - requisition.height);
+	}
+	else if (needed_height > space_below && needed_height > space_above)
+	{
+		if (space_below >= space_above)
+			*y = monitor.y + monitor.height - requisition.height;
+		else
+			*y = monitor.y;
+	}
+	else
+	{
+		*y = monitor.y;
+	}
+}
+
+void
+gaim_gtk_treeview_popup_menu_position_func(GtkMenu *menu,
+										   gint *x,
+										   gint *y,
+										   gboolean *push_in,
+										   gpointer data)
+{
+	GtkWidget *widget = GTK_WIDGET(data);
+	GtkTreeView *tv = GTK_TREE_VIEW(data);
+	GtkTreePath *path;
+	GtkTreeViewColumn *col;
+	GdkRectangle rect;
+	gint ythickness = GTK_WIDGET(menu)->style->ythickness;
+
+	gdk_window_get_origin (widget->window, x, y);
+	gtk_tree_view_get_cursor (tv, &path, &col);
+	gtk_tree_view_get_cell_area (tv, path, col, &rect);
+	
+	*x += rect.x+rect.width;
+	*y += rect.y+rect.height+ythickness;
+	gaim_gtk_menu_position_func (menu, x, y, push_in, data);
+}
--- a/src/gtkutils.h	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/gtkutils.h	Mon Jan 19 22:40:17 2004 +0000
@@ -353,6 +353,27 @@
  * @w The widget that we want to name.
  * @l A GtkLabel that we want to use as the ATK name for the widget.
  */
-void gaim_set_accessible_label (GtkWidget *w, GtkWidget *l);
+void gaim_set_accessible_label(GtkWidget *w, GtkWidget *l);
+
+/**
+ * A valid GtkMenuPositionFunc.  This is used to determine where 
+ * to draw context menu's 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.
+ *
+ * @param menu The menu we are positioning.
+ * @param x Address of the gint representing the horizontal position
+ *        where the menu shall be drawn. This is an output parameter.
+ * @param y Address of the gint representing the vertical position
+ *        where the menu shall be drawn. This is an output parameter.
+ * @param push_in This is an output parameter?
+ * @param user_data Not used by this particular position function.
+ */
+void gaim_gtk_treeview_popup_menu_position_func(GtkMenu *menu,
+												gint *x,
+												gint *y,
+												gboolean *push_in,
+												gpointer user_data);
 
 #endif /* _GAIM_GTK_UTILS_H_ */
--- a/src/protocols/oscar/oscar.c	Mon Jan 19 22:13:50 2004 +0000
+++ b/src/protocols/oscar/oscar.c	Mon Jan 19 22:40:17 2004 +0000
@@ -4668,7 +4668,7 @@
 	}
 
 #ifdef NOSSI
-	aim_add_buddy(od->sess, od->conn, name);
+	aim_buddylist_addbuddy(od->sess, od->conn, name);
 #else
 	if ((od->sess->ssi.received_data) && !(aim_ssi_itemlist_exists(od->sess->ssi.local, name))) {
 		GaimBuddy *buddy = gaim_find_buddy(gc->account, name);
@@ -4711,7 +4711,7 @@
 static void oscar_remove_buddy(GaimConnection *gc, const char *name, const char *group) {
 	OscarData *od = (OscarData *)gc->proto_data;
 #ifdef NOSSI
-	aim_remove_buddy(od->sess, od->conn, name);
+	aim_buddylist_removebuddy(od->sess, od->conn, name);
 #else
 	if (od->sess->ssi.received_data) {
 		gaim_debug(GAIM_DEBUG_INFO, "oscar",
@@ -4726,7 +4726,7 @@
 #ifdef NOSSI
 	GList *cur;
 	for (cur=buddies; cur; cur=cur->next)
-		aim_remove_buddy(od->sess, od->conn, cur->data);
+		aim_buddylist_removebuddy(od->sess, od->conn, cur->data);
 #else
 	if (od->sess->ssi.received_data) {
 		while (buddies) {
@@ -6047,7 +6047,7 @@
 	GaimAccount *account = gaim_connection_get_account(gc);
 	OscarData *od = (OscarData *)gc->proto_data;
 #ifdef NOSSI
-	GSList *list, *g = gaim_blist_groups(), *g1;
+	GSList *list;
 	char buf[MAXMSGLEN];
 	int at;
 
@@ -6076,28 +6076,9 @@
 		}
 		aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf);
 		break;
-	case 5:
-		g1 = g;
-		at = 0;
-		while (g1) {
-			list = gaim_blist_members((struct group *)g->data);
-			GSList list1 = list;
-			while (list1) {
-				struct buddy *b = list1->data;
-				if(b->account == account)
-					at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", b->name);
-				list1 = list1->next;
-			}
-			g_slist_free(list);
-			g1 = g1->next;
-		}
-		g_slist_free(g);
-		aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf);
-		break;
 	default:
 		break;
 	}
-	signoff_blocked(gc);
 #else
 	if (od->sess->ssi.received_data)
 		aim_ssi_setpermdeny(od->sess, account->perm_deny, 0xffffffff);
@@ -6106,7 +6087,7 @@
 
 static void oscar_add_permit(GaimConnection *gc, const char *who) {
 #ifdef NOSSI
-	if (gc->account->permdeny == 3)
+	if (gc->account->perm_deny == 3)
 		oscar_set_permit_deny(gc);
 #else
 	OscarData *od = (OscarData *)gc->proto_data;
@@ -6118,7 +6099,7 @@
 
 static void oscar_add_deny(GaimConnection *gc, const char *who) {
 #ifdef NOSSI
-	if (gc->account->permdeny == 4)
+	if (gc->account->perm_deny == 4)
 		oscar_set_permit_deny(gc);
 #else
 	OscarData *od = (OscarData *)gc->proto_data;
@@ -6130,7 +6111,7 @@
 
 static void oscar_rem_permit(GaimConnection *gc, const char *who) {
 #ifdef NOSSI
-	if (gc->account->permdeny == 3)
+	if (gc->account->perm_deny == 3)
 		oscar_set_permit_deny(gc);
 #else
 	OscarData *od = (OscarData *)gc->proto_data;
@@ -6142,7 +6123,7 @@
 
 static void oscar_rem_deny(GaimConnection *gc, const char *who) {
 #ifdef NOSSI
-	if (gc->account->permdeny == 4)
+	if (gc->account->perm_deny == 4)
 		oscar_set_permit_deny(gc);
 #else
 	OscarData *od = (OscarData *)gc->proto_data;