changeset 8089:35db601609e3

[gaim-migrate @ 8788] Gaim-Evolution Buddy List-Addressbook syncronocity by our very own Chip X. Eightysix. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Tue, 13 Jan 2004 01:45:32 +0000
parents a86784e3b98c
children fb0eab758560
files configure.ac plugins/Makefile.am plugins/gevolution/Makefile.am plugins/gevolution/add_buddy_dialog.c plugins/gevolution/assoc-buddy.c plugins/gevolution/gevo-util.c plugins/gevolution/gevolution.c plugins/gevolution/gevolution.h plugins/gevolution/new_person_dialog.c src/gtkblist.c
diffstat 10 files changed, 2588 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Tue Jan 13 01:00:24 2004 +0000
+++ b/configure.ac	Tue Jan 13 01:45:32 2004 +0000
@@ -238,6 +238,18 @@
 AC_SUBST(STARTUP_NOTIFICATION_CFLAGS)
 AC_SUBST(STARTUP_NOTIFICATION_LIBS)
 
+
+dnl Check for stuff needed by the evolution integration plugin.
+build_gevo=no
+
+evo_deps="libxml-2.0 libebook-1.0 libedata-book-1.0"
+PKG_CHECK_MODULES(EVOLUTION_ADDRESSBOOK, $evo_deps, build_gevo=yes, build_gevo=no)
+
+AC_SUBST(EVOLUTION_ADDRESSBOOK_CFLAGS)
+AC_SUBST(EVOLUTION_ADDRESSBOOK_LIBS)
+
+AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$build_gevo" = "xyes")
+
 dnl Check for XScreenSaver
 if test "x$enable_xss" = "xyes" ; then
 	old_LIBS="$LIBS"
@@ -1011,6 +1023,7 @@
 		   pixmaps/status/default/Makefile
 		   plugins/Makefile
 		   plugins/docklet/Makefile
+		   plugins/gevolution/Makefile
 		   plugins/gaim-remote/Makefile
 		   plugins/gestures/Makefile
 		   plugins/perl/Makefile
--- a/plugins/Makefile.am	Tue Jan 13 01:00:24 2004 +0000
+++ b/plugins/Makefile.am	Tue Jan 13 01:45:32 2004 +0000
@@ -1,4 +1,8 @@
-DIST_SUBDIRS = docklet gaim-remote gestures perl ssl tcl ticker
+DIST_SUBDIRS = docklet gevolution gaim-remote gestures perl ssl tcl ticker
+
+if BUILD_GEVOLUTION
+GEVOLUTION_DIR = gevolution
+endif
 
 if USE_PERL
 PERL_DIR = perl
@@ -8,7 +12,9 @@
 TCL_DIR = tcl
 endif
 
-SUBDIRS = docklet gaim-remote gestures $(PERL_DIR) $(TCL_DIR) ssl ticker
+SUBDIRS = \
+	docklet gaim-remote $(GEVOLUTION_DIR) gestures \
+	$(PERL_DIR) $(TCL_DIR) ssl ticker
 
 plugindir = $(libdir)/gaim
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/Makefile.am	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,28 @@
+plugindir = $(libdir)/gaim
+
+gevolution_la_LDFLAGS = \
+	-module -avoid-version \
+	$(EVOLUTION_ADDRESSBOOK_LIBS)
+
+if PLUGINS
+
+plugin_LTLIBRARIES = gevolution.la
+
+gevolution_la_SOURCES = \
+	add_buddy_dialog.c \
+	assoc-buddy.c \
+	gevolution.c \
+	gevo-util.c \
+	new_person_dialog.c
+
+endif
+
+gevolution_la_LIBADD = 
+
+AM_CPPFLAGS = \
+	-DDATADIR=\"$(datadir)\" \
+	-DVERSION=\"$(VERSION)\" \
+	-I$(top_srcdir)/src \
+	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(GTK_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/add_buddy_dialog.c	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,650 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "gtkinternal.h"
+#include "gtkblist.h"
+#include "gtkutils.h"
+
+#include "debug.h"
+
+#include "gevolution.h"
+
+#include <stdlib.h>
+#include <bonobo/bonobo-main.h>
+#include <libebook/e-book.h>
+#include <libebook/e-book-async.h>
+#include <libedataserver/e-source-list.h>
+
+enum
+{
+	COLUMN_NAME,
+	COLUMN_PRPL_ICON,
+	COLUMN_USERNAME,
+	COLUMN_DATA,
+	NUM_COLUMNS
+};
+
+static gint
+delete_win_cb(GtkWidget *w, GdkEvent *event, GevoAddBuddyDialog *dialog)
+{
+	gtk_widget_destroy(dialog->win);
+
+	g_list_foreach(dialog->contacts, (GFunc)g_object_unref, NULL);
+
+	if (dialog->contacts != NULL)
+		g_list_free(dialog->contacts);
+
+	if (dialog->book != NULL)
+		g_object_unref(dialog->book);
+
+	if (dialog->username != NULL)
+		g_free(dialog->username);
+
+	g_free(dialog);
+
+	return 0;
+}
+
+static void
+new_person_cb(GtkWidget *w, GevoAddBuddyDialog *dialog)
+{
+	const char *group_name;
+
+	group_name =
+		gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(dialog->group_combo)->entry));
+
+	gevo_new_person_dialog_show(NULL, dialog->account, NULL,
+								(*group_name ? group_name : NULL),
+								NULL, FALSE);
+
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+cancel_cb(GtkWidget *w, GevoAddBuddyDialog *dialog)
+{
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+select_buddy_cb(GtkWidget *w, GevoAddBuddyDialog *dialog)
+{
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	const char *group_name;
+	const char *fullname;
+	const char *username;
+	EContact *contact;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+
+	gtk_tree_selection_get_selected(selection, NULL, &iter);
+
+	gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
+					   COLUMN_NAME, &fullname,
+					   COLUMN_USERNAME, &username,
+					   COLUMN_DATA, &contact,
+					   -1);
+
+	group_name =
+		gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(dialog->group_combo)->entry));
+
+	if (username == NULL || *username == '\0')
+	{
+		gevo_new_person_dialog_show(NULL, dialog->account, NULL,
+									(*group_name ? group_name : NULL),
+									NULL, FALSE);
+	}
+	else
+	{
+		gevo_add_buddy(dialog->account, group_name, username, fullname);
+	}
+
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+populate_address_books(GevoAddBuddyDialog *dialog)
+{
+	GtkWidget *item;
+	GtkWidget *menu;
+#if notyet
+	ESourceList *addressbooks;
+	GList *groups, *g;
+#endif
+
+	menu =
+		gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->addressbooks_menu));
+
+	item = gtk_menu_item_new_with_label(_("Local Addressbook"));
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+#if notyet
+	if (!e_book_get_addressbooks(&addressbooks, NULL))
+	{
+		gaim_debug_error("evolution",
+						 "Unable to fetch list of address books.\n");
+
+		item = gtk_menu_item_new_with_label(_("None"));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		return;
+	}
+
+	groups = e_source_list_peek_groups(list);
+
+	if (groups == NULL)
+	{
+		item = gtk_menu_item_new_with_label(_("None"));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		return;
+	}
+
+	for (g = groups; g != NULL; g = g->next)
+	{
+		GList *sources, *s;
+
+		sources = e_source_group_peek_sources(g->data);
+
+		for (p = sources; p != NULL; p = p->next)
+		{
+			ESource *source = E_SOURCE(p->data);
+
+			item = gtk_menu_item_new_with_label(e_source_peek_name(source));
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			gtk_widget_show(item);
+		}
+	}
+#endif
+}
+
+static void
+add_columns(GevoAddBuddyDialog *dialog)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+
+	/* Name column */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Name"));
+	gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
+	gtk_tree_view_column_set_sort_column_id(column, COLUMN_NAME);
+
+	renderer = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "text", COLUMN_NAME);
+
+	/* Account column */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Instant Messaging"));
+	gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
+	gtk_tree_view_column_set_sort_column_id(column, COLUMN_USERNAME);
+
+	/* Protocol icon */
+	renderer = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "pixbuf", COLUMN_PRPL_ICON);
+
+	/* Account name */
+	renderer = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "text", COLUMN_USERNAME);
+}
+
+static void
+add_ims(GevoAddBuddyDialog *dialog, EContact *contact, const char *name,
+		GList *list, const char *id)
+{
+	GaimAccount *account = NULL;
+	GList *l;
+	GtkTreeIter iter;
+	GdkPixbuf *pixbuf, *icon = NULL;
+
+	if (list == NULL)
+		return;
+
+	for (l = gaim_connections_get_all(); l != NULL; l = l->next)
+	{
+		GaimConnection *gc = (GaimConnection *)l->data;
+
+		account = gaim_connection_get_account(gc);
+
+		if (!strcmp(gaim_account_get_protocol_id(account), id))
+			break;
+
+		account = NULL;
+	}
+
+	if (account == NULL)
+		return;
+
+	pixbuf = create_prpl_icon(account);
+
+	if (pixbuf != NULL)
+		icon = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
+									   GDK_INTERP_BILINEAR);
+
+	for (l = list; l != NULL; l = l->next)
+	{
+		char *account_name = (char *)l->data;
+
+		if (account_name == NULL)
+			continue;
+
+		if (gaim_find_buddy(dialog->account, account_name) != NULL)
+			continue;
+
+		gtk_list_store_append(dialog->model, &iter);
+
+		gtk_list_store_set(dialog->model, &iter,
+						   COLUMN_NAME, name,
+						   COLUMN_PRPL_ICON, icon,
+						   COLUMN_USERNAME, account_name,
+						   COLUMN_DATA, contact,
+						   -1);
+
+		if (!strcmp(gaim_account_get_protocol_id(account),
+					gaim_account_get_protocol_id(dialog->account)) &&
+			dialog->username != NULL &&
+			!strcmp(account_name, dialog->username))
+		{
+			GtkTreeSelection *selection;
+
+			/* This is it. Select it. */
+			selection = gtk_tree_view_get_selection(
+				GTK_TREE_VIEW(dialog->treeview));
+
+			gtk_tree_selection_select_iter(selection, &iter);
+		}
+	}
+
+	if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf));
+	if (icon != NULL)   g_object_unref(G_OBJECT(icon));
+
+	g_list_foreach(list, (GFunc)g_free, NULL);
+	g_list_free(list);
+}
+
+static void
+populate_treeview(GevoAddBuddyDialog *dialog)
+{
+	EBookQuery *query;
+	EBook *book;
+	gboolean status;
+	GList *cards, *c;
+
+	if (!gevo_load_addressbook(&book, NULL))
+	{
+		gaim_debug_error("evolution",
+						 "Error retrieving default addressbook\n");
+
+		return;
+	}
+
+	query = e_book_query_field_exists(E_CONTACT_FULL_NAME);
+
+	if (query == NULL)
+	{
+		gaim_debug_error("evolution", "Error in creating query\n");
+
+		g_object_unref(book);
+
+		return;
+	}
+
+	status = e_book_get_contacts(book, query, &cards, NULL);
+
+	e_book_query_unref(query);
+
+	if (!status)
+	{
+		gaim_debug_error("evolution", "Error %d in getting card list\n",
+						 status);
+
+		g_object_unref(book);
+
+		return;
+	}
+
+	for (c = cards; c != NULL; c = c->next)
+	{
+		EContact *contact = E_CONTACT(c->data);
+		const char *name;
+		GList *aims, *jabbers, *yahoos, *msns, *icqs;
+
+		name = e_contact_get_const(contact, E_CONTACT_FULL_NAME);
+
+		aims    = e_contact_get(contact, E_CONTACT_IM_AIM);
+		jabbers = e_contact_get(contact, E_CONTACT_IM_JABBER);
+		yahoos  = e_contact_get(contact, E_CONTACT_IM_YAHOO);
+		msns    = e_contact_get(contact, E_CONTACT_IM_MSN);
+		icqs    = e_contact_get(contact, E_CONTACT_IM_ICQ);
+
+		if (aims == NULL && jabbers == NULL && yahoos == NULL &&
+			msns == NULL && icqs == NULL)
+		{
+			GtkTreeIter iter;
+
+			gtk_list_store_append(dialog->model, &iter);
+
+			gtk_list_store_set(dialog->model, &iter,
+							   COLUMN_NAME, name,
+							   COLUMN_DATA, contact,
+							   -1);
+		}
+		else
+		{
+			add_ims(dialog, contact, name, aims,    "prpl-oscar");
+			add_ims(dialog, contact, name, jabbers, "prpl-jabber");
+			add_ims(dialog, contact, name, yahoos,  "prpl-yahoo");
+			add_ims(dialog, contact, name, msns,    "prpl-msn");
+			add_ims(dialog, contact, name, icqs,    "prpl-oscar");
+		}
+	}
+
+	dialog->contacts = cards;
+	dialog->book = book;
+}
+
+static void
+selected_cb(GtkTreeSelection *sel, GevoAddBuddyDialog *dialog)
+{
+	gtk_widget_set_sensitive(dialog->select_button, TRUE);
+}
+
+static void
+search_changed_cb(GtkEntry *entry, GevoAddBuddyDialog *dialog)
+{
+	const char *text = gtk_entry_get_text(entry);
+	GList *l;
+
+	gtk_list_store_clear(dialog->model);
+
+	for (l = dialog->contacts; l != NULL; l = l->next)
+	{
+		EContact *contact = E_CONTACT(l->data);
+		const char *name;
+		GList *aims, *jabbers, *yahoos, *msns, *icqs;
+
+		name = e_contact_get_const(contact, E_CONTACT_FULL_NAME);
+
+		if (text != NULL && *text != '\0' && name != NULL &&
+			g_ascii_strncasecmp(name, text, strlen(text)))
+		{
+			continue;
+		}
+
+		aims    = e_contact_get(contact, E_CONTACT_IM_AIM);
+		jabbers = e_contact_get(contact, E_CONTACT_IM_JABBER);
+		yahoos  = e_contact_get(contact, E_CONTACT_IM_YAHOO);
+		msns    = e_contact_get(contact, E_CONTACT_IM_MSN);
+		icqs    = e_contact_get(contact, E_CONTACT_IM_ICQ);
+
+		if (aims == NULL && jabbers == NULL && yahoos == NULL &&
+			msns == NULL && icqs == NULL)
+		{
+			GtkTreeIter iter;
+
+			gtk_list_store_append(dialog->model, &iter);
+
+			gtk_list_store_set(dialog->model, &iter,
+							   COLUMN_NAME, name,
+							   COLUMN_DATA, contact,
+							   -1);
+		}
+		else
+		{
+			add_ims(dialog, contact, name, aims,    "prpl-oscar");
+			add_ims(dialog, contact, name, jabbers, "prpl-jabber");
+			add_ims(dialog, contact, name, yahoos,  "prpl-yahoo");
+			add_ims(dialog, contact, name, msns,    "prpl-msn");
+			add_ims(dialog, contact, name, icqs,    "prpl-oscar");
+		}
+	}
+}
+
+static void
+clear_cb(GtkWidget *w, GevoAddBuddyDialog *dialog)
+{
+	static gboolean lock = FALSE;
+
+	if (lock)
+		return;
+
+	lock = TRUE;
+	gtk_entry_set_text(GTK_ENTRY(dialog->search_field), "");
+	lock = FALSE;
+}
+
+void
+gevo_add_buddy_dialog_show(GaimAccount *account, const char *username,
+						   const char *group, const char *alias)
+{
+	GevoAddBuddyDialog *dialog;
+	GtkWidget *button;
+	GtkWidget *sw;
+	GtkWidget *label;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *bbox;
+	GtkWidget *menu;
+	GtkWidget *sep;
+	GtkTreeSelection *selection;
+
+	dialog = g_new0(GevoAddBuddyDialog, 1);
+
+	dialog->account =
+		(account != NULL
+		 ? account
+		 : gaim_connection_get_account(gaim_connections_get_all()->data));
+
+	if (username != NULL)
+		dialog->username = g_strdup(username);
+
+	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_role(GTK_WINDOW(dialog->win), "add_buddy");
+	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+	gtk_widget_set_size_request(dialog->win, -1, 400);
+
+	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Setup the vbox */
+	vbox = gtk_vbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(dialog->win), vbox);
+	gtk_widget_show(vbox);
+
+	/* Add the label. */
+	label = gtk_label_new(_("Select a person from your address book below, "
+							"or add a new person."));
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+	gtk_widget_show(label);
+
+	/* Add the search hbox */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+	gtk_widget_show(hbox);
+
+	/* "Search" */
+	label = gtk_label_new(_("Search"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	/* Addressbooks */
+	dialog->addressbooks_menu = gtk_option_menu_new();
+	menu = gtk_menu_new();
+	gtk_option_menu_set_menu(GTK_OPTION_MENU(dialog->addressbooks_menu), menu);
+
+	populate_address_books(dialog);
+
+	gtk_option_menu_set_history(GTK_OPTION_MENU(dialog->addressbooks_menu), 0);
+
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->addressbooks_menu,
+					   FALSE, FALSE, 0);
+	gtk_widget_show(dialog->addressbooks_menu);
+
+	/* Search field */
+	dialog->search_field = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->search_field, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->search_field);
+
+	g_signal_connect(G_OBJECT(dialog->search_field), "changed",
+					 G_CALLBACK(search_changed_cb), dialog);
+
+	/* Clear button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
+	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(clear_cb), dialog);
+
+	/* Scrolled Window */
+	sw = gtk_scrolled_window_new(0, 0);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_AUTOMATIC,
+								   GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+										GTK_SHADOW_IN);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
+
+	/* Create the list model for the treeview. */
+	dialog->model = gtk_list_store_new(NUM_COLUMNS,
+									   G_TYPE_STRING, GDK_TYPE_PIXBUF,
+									   G_TYPE_STRING, G_TYPE_POINTER);
+
+	/* Now for the treeview */
+	dialog->treeview =
+		gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(dialog->treeview), TRUE);
+	gtk_container_add(GTK_CONTAINER(sw), dialog->treeview);
+	gtk_widget_show(dialog->treeview);
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+
+	gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+
+	g_signal_connect(G_OBJECT(selection), "changed",
+					 G_CALLBACK(selected_cb), dialog);
+
+	add_columns(dialog);
+
+	populate_treeview(dialog);
+
+	/* Group box */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new(_("Group:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	dialog->group_combo = gtk_combo_new();
+	gtk_combo_set_popdown_strings(GTK_COMBO(dialog->group_combo),
+								  gevo_get_groups());
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->group_combo, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->group_combo);
+
+	/* Cool. Now we only have a little left... */
+
+	/* Separator. */
+	sep = gtk_hseparator_new();
+	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+	gtk_widget_show(sep);
+
+	/* Button box */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* "New Person" button */
+	button = gaim_pixbuf_button_from_stock(_("New Person"), GTK_STOCK_NEW,
+										   GAIM_BUTTON_HORIZONTAL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(new_person_cb), dialog);
+
+	/* "Cancel" button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(cancel_cb), dialog);
+
+	/* "Select Buddy" button */
+	button = gaim_pixbuf_button_from_stock(_("Select Buddy"), GTK_STOCK_APPLY,
+										   GAIM_BUTTON_HORIZONTAL);
+	dialog->select_button = button;
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_set_sensitive(button, FALSE);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(select_buddy_cb), dialog);
+
+	/* Show it. */
+	gtk_widget_show(dialog->win);
+}
+
+void
+gevo_add_buddy_dialog_add_person(GevoAddBuddyDialog *dialog,
+								 EContact *contact, const char *name,
+								 GaimAccount *account, const char *screenname)
+{
+	GdkPixbuf *pixbuf, *icon = NULL;
+	GtkTreeIter iter;
+
+	pixbuf = create_prpl_icon(account);
+
+	if (pixbuf != NULL)
+		icon = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR);
+
+	gtk_list_store_append(dialog->model, &iter);
+
+	gtk_list_store_set(dialog->model, &iter,
+					   COLUMN_NAME, name,
+					   COLUMN_PRPL_ICON, icon,
+					   COLUMN_DATA, contact,
+					   COLUMN_USERNAME, screenname,
+					   -1);
+
+	if (contact != NULL)
+		dialog->contacts = g_list_append(dialog->contacts, contact);
+
+	if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf));
+	if (icon != NULL)   g_object_unref(G_OBJECT(icon));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/assoc-buddy.c	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,520 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "gtkinternal.h"
+#include "gtkblist.h"
+#include "gtkutils.h"
+#include "gtkimhtml.h"
+#include "gaim-disclosure.h"
+
+#include "debug.h"
+
+#include "gevolution.h"
+
+#include <stdlib.h>
+#include <bonobo/bonobo-main.h>
+#include <libebook/e-book.h>
+#include <libebook/e-book-async.h>
+#include <libedataserver/e-source-list.h>
+
+enum
+{
+	COLUMN_NAME,
+	COLUMN_DATA,
+	NUM_COLUMNS
+};
+
+static gint
+delete_win_cb(GtkWidget *w, GdkEvent *event, GevoAssociateBuddyDialog *dialog)
+{
+	GList *l;
+
+	gtk_widget_destroy(dialog->win);
+
+	g_list_foreach(dialog->contacts, (GFunc)g_free, NULL);
+
+	if (dialog->contacts != NULL)
+		g_list_free(dialog->contacts);
+
+	g_object_unref(dialog->book);
+
+	g_free(dialog);
+
+	return 0;
+}
+
+static void
+populate_address_books(GevoAssociateBuddyDialog *dialog)
+{
+	GtkWidget *item;
+	GtkWidget *menu;
+#if notyet
+	ESourceList *addressbooks;
+	GList *groups, *g;
+#endif
+
+	menu =
+		gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->addressbooks_menu));
+
+	item = gtk_menu_item_new_with_label(_("Local Addressbook"));
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+#if notyet
+	if (!e_book_get_addressbooks(&addressbooks, NULL))
+	{
+		gaim_debug_error("evolution",
+						 "Unable to fetch list of address books.\n");
+
+		item = gtk_menu_item_new_with_label(_("None"));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		return;
+	}
+
+	groups = e_source_list_peek_groups(list);
+
+	if (groups == NULL)
+	{
+		item = gtk_menu_item_new_with_label(_("None"));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		return;
+	}
+
+	for (g = groups; g != NULL; g = g->next)
+	{
+		GList *sources, *s;
+
+		sources = e_source_group_peek_sources(g->data);
+
+		for (p = sources; p != NULL; p = p->next)
+		{
+			ESource *source = E_SOURCE(p->data);
+
+			item = gtk_menu_item_new_with_label(e_source_peek_name(source));
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			gtk_widget_show(item);
+		}
+	}
+#endif
+}
+
+static void
+search_changed_cb(GtkEntry *entry, GevoAssociateBuddyDialog *dialog)
+{
+	const char *text = gtk_entry_get_text(entry);
+	GList *l;
+
+	gtk_list_store_clear(dialog->model);
+
+	for (l = dialog->contacts; l != NULL; l = l->next)
+	{
+		EContact *contact = E_CONTACT(l->data);
+		const char *name;
+		GtkTreeIter iter;
+
+		name = e_contact_get_const(contact, E_CONTACT_FULL_NAME);
+
+		if (text != NULL && *text != '\0' && name != NULL &&
+			g_ascii_strncasecmp(name, text, strlen(text)))
+		{
+			continue;
+		}
+
+		gtk_list_store_append(dialog->model, &iter);
+
+		gtk_list_store_set(dialog->model, &iter,
+						   COLUMN_NAME, name,
+						   COLUMN_DATA, contact,
+						   -1);
+	}
+}
+
+static void
+clear_cb(GtkWidget *w, GevoAssociateBuddyDialog *dialog)
+{
+	static gboolean lock = FALSE;
+
+	if (lock)
+		return;
+
+	lock = TRUE;
+	gtk_entry_set_text(GTK_ENTRY(dialog->search_field), "");
+	lock = FALSE;
+}
+
+static void
+selected_cb(GtkTreeSelection *sel, GevoAssociateBuddyDialog *dialog)
+{
+	gtk_widget_set_sensitive(dialog->assoc_button, TRUE);
+}
+
+static void
+add_columns(GevoAssociateBuddyDialog *dialog)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+
+	/* Name column */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Name"));
+	gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
+	gtk_tree_view_column_set_sort_column_id(column, COLUMN_NAME);
+
+	renderer = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "text", COLUMN_NAME);
+}
+
+static void
+populate_treeview(GevoAssociateBuddyDialog *dialog)
+{
+	EBookQuery *query;
+	EBook *book;
+	const char *prpl_id;
+	gboolean status;
+	GList *cards, *c;
+
+	if (!gevo_load_addressbook(&book, NULL))
+	{
+		gaim_debug_error("evolution",
+						 "Error retrieving default addressbook\n");
+
+		return;
+	}
+
+	query = e_book_query_field_exists(E_CONTACT_FULL_NAME);
+
+	if (query == NULL)
+	{
+		gaim_debug_error("evolution", "Error in creating query\n");
+
+		g_object_unref(book);
+
+		return;
+	}
+
+	status = e_book_get_contacts(book, query, &cards, NULL);
+
+	e_book_query_unref(query);
+
+	if (!status)
+	{
+		gaim_debug_error("evolution", "Error %d in getting card list\n",
+						 status);
+
+		g_object_unref(book);
+
+		return;
+	}
+
+	prpl_id = gaim_account_get_protocol_id(dialog->buddy->account);
+
+	for (c = cards; c != NULL; c = c->next)
+	{
+		EContact *contact = E_CONTACT(c->data);
+		const char *name;
+		GtkTreeIter iter;
+		EContactField protocol_field = 0;
+
+		name = e_contact_get_const(contact, E_CONTACT_FULL_NAME);
+
+		gtk_list_store_append(dialog->model, &iter);
+
+		gtk_list_store_set(dialog->model, &iter,
+						   COLUMN_NAME, name,
+						   COLUMN_DATA, contact,
+						   -1);
+
+		/* See if this user has the buddy in its list. */
+		protocol_field = gevo_prpl_get_field(dialog->buddy->account,
+											 dialog->buddy);
+
+		if (protocol_field > 0)
+		{
+			GList *ims, *l;
+
+			ims = e_contact_get(contact, protocol_field);
+
+			for (l = ims; l != NULL; l = l->next)
+			{
+				if (!strcmp(l->data, dialog->buddy->name))
+				{
+					GtkTreeSelection *selection;
+
+					/* This is it. Select it. */
+					selection = gtk_tree_view_get_selection(
+						GTK_TREE_VIEW(dialog->treeview));
+
+					gtk_tree_selection_select_iter(selection, &iter);
+					break;
+				}
+			}
+		}
+	}
+
+	dialog->contacts = cards;
+	dialog->book = book;
+}
+
+static void
+new_person_cb(GtkWidget *w, GevoAssociateBuddyDialog *dialog)
+{
+	gevo_new_person_dialog_show(NULL, dialog->buddy->account,
+								dialog->buddy->name, NULL, dialog->buddy,
+								TRUE);
+
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+cancel_cb(GtkWidget *w, GevoAssociateBuddyDialog *dialog)
+{
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+assoc_buddy_cb(GtkWidget *w, GevoAssociateBuddyDialog *dialog)
+{
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	GList *list, *new_list = NULL, *l;
+	const char *fullname;
+	EContactField protocol_field;
+	EContact *contact;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+
+	gtk_tree_selection_get_selected(selection, NULL, &iter);
+
+	gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
+					   COLUMN_NAME, &fullname,
+					   COLUMN_DATA, &contact,
+					   -1);
+
+	protocol_field = gevo_prpl_get_field(dialog->buddy->account, dialog->buddy);
+
+	if (protocol_field == 0)
+		return; /* XXX */
+
+	list = e_contact_get(contact, protocol_field);
+
+	/* Make a copy of the list */
+	for (l = list; l != NULL; l = l->next)
+		new_list = g_list_append(new_list, g_strdup((char *)l->data));
+
+	new_list = g_list_append(new_list, dialog->buddy->name);
+
+	e_contact_set(contact, protocol_field, new_list);
+
+	/* Free the list. */
+	g_list_foreach(new_list, (GFunc)g_free, NULL);
+	g_list_free(new_list);
+
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+GevoAssociateBuddyDialog *
+gevo_associate_buddy_dialog_new(GaimBuddy *buddy)
+{
+	GevoAssociateBuddyDialog *dialog;
+	GtkWidget *button;
+	GtkWidget *sw;
+	GtkWidget *label;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *bbox;
+	GtkWidget *menu;
+	GtkWidget *sep;
+	GtkWidget *disclosure;
+	GtkTreeSelection *selection;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	dialog = g_new0(GevoAssociateBuddyDialog, 1);
+
+	dialog->buddy = buddy;
+
+	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_role(GTK_WINDOW(dialog->win), "assoc_buddy");
+	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+
+	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Setup the vbox */
+	vbox = gtk_vbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(dialog->win), vbox);
+	gtk_widget_show(vbox);
+
+	/* Add the label. */
+	label = gtk_label_new(_("Select a person from your address book to "
+							"add this buddy to, or create a new person."));
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+	gtk_widget_show(label);
+
+	/* Add the search hbox */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+	gtk_widget_show(hbox);
+
+	/* "Search" */
+	label = gtk_label_new(_("Search"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	/* Addressbooks */
+	dialog->addressbooks_menu = gtk_option_menu_new();
+	menu = gtk_menu_new();
+	gtk_option_menu_set_menu(GTK_OPTION_MENU(dialog->addressbooks_menu), menu);
+
+	populate_address_books(dialog);
+
+	gtk_option_menu_set_history(GTK_OPTION_MENU(dialog->addressbooks_menu), 0);
+
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->addressbooks_menu,
+					   FALSE, FALSE, 0);
+	gtk_widget_show(dialog->addressbooks_menu);
+
+	/* Search field */
+	dialog->search_field = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->search_field, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->search_field);
+
+	g_signal_connect(G_OBJECT(dialog->search_field), "changed",
+					 G_CALLBACK(search_changed_cb), dialog);
+
+	/* Clear button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
+	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(clear_cb), dialog);
+
+	/* Scrolled Window */
+	sw = gtk_scrolled_window_new(0, 0);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_AUTOMATIC,
+								   GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+										GTK_SHADOW_IN);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
+
+	/* Create the list model for the treeview. */
+	dialog->model = gtk_list_store_new(NUM_COLUMNS,
+									   G_TYPE_STRING, G_TYPE_POINTER);
+
+	/* Now for the treeview */
+	dialog->treeview =
+		gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(dialog->treeview), TRUE);
+	gtk_container_add(GTK_CONTAINER(sw), dialog->treeview);
+	gtk_widget_show(dialog->treeview);
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+
+	gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+
+	g_signal_connect(G_OBJECT(selection), "changed",
+					 G_CALLBACK(selected_cb), dialog);
+
+	add_columns(dialog);
+
+	populate_treeview(dialog);
+
+	/* Add the disclosure */
+	disclosure = gaim_disclosure_new(_("Show user details"),
+									 _("Hide user details"));
+	gtk_box_pack_start(GTK_BOX(vbox), disclosure, FALSE, FALSE, 0);
+	gtk_widget_show(disclosure);
+
+	/*
+	 * User details
+	 */
+
+	/* Scrolled Window */
+	sw = gtk_scrolled_window_new(0, 0);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_NEVER,
+								   GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+										GTK_SHADOW_IN);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gaim_disclosure_set_container(GAIM_DISCLOSURE(disclosure), sw);
+
+	/* Textview */
+	dialog->imhtml = gtk_imhtml_new(NULL, NULL);
+	gtk_container_add(GTK_CONTAINER(sw), dialog->imhtml);
+	gtk_widget_show(dialog->imhtml);
+
+	/* Separator. */
+	sep = gtk_hseparator_new();
+	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+	gtk_widget_show(sep);
+
+	/* Button box */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* "New Person" button */
+	button = gaim_pixbuf_button_from_stock(_("New Person"), GTK_STOCK_NEW,
+										   GAIM_BUTTON_HORIZONTAL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(new_person_cb), dialog);
+
+	/* "Cancel" button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(cancel_cb), dialog);
+
+	/* "Associate Buddy" button */
+	button = gaim_pixbuf_button_from_stock(_("_Associate Buddy"),
+										   GTK_STOCK_APPLY,
+										   GAIM_BUTTON_HORIZONTAL);
+	dialog->assoc_button = button;
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_set_sensitive(button, FALSE);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(assoc_buddy_cb), dialog);
+
+	/* Show it. */
+	gtk_widget_show(dialog->win);
+
+	return dialog;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/gevo-util.c	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,158 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "gtkinternal.h"
+#include "gtkblist.h"
+#include "gtkutils.h"
+
+#include <libebook/e-book.h>
+
+void
+gevo_add_buddy(GaimAccount *account, const char *group_name,
+			   const char *screenname, const char *alias)
+{
+	GaimConversation *conv = NULL;
+	GaimBuddy *buddy;
+	GaimGroup *group;
+
+	conv = gaim_find_conversation_with_account(screenname, account);
+
+	if ((group = gaim_find_group(group_name)) == NULL)
+	{
+		group = gaim_group_new(group_name);
+		gaim_blist_add_group(group, NULL);
+	}
+
+	buddy = gaim_buddy_new(account, screenname, alias);
+	gaim_blist_add_buddy(buddy, NULL, group, NULL);
+	serv_add_buddy(gaim_account_get_connection(account), screenname, group);
+
+	if (conv != NULL)
+	{
+		gaim_buddy_icon_update(gaim_conv_im_get_icon(GAIM_CONV_IM(conv)));
+		gaim_conversation_update(conv, GAIM_CONV_UPDATE_ADD);
+
+		gaim_blist_save();
+	}
+}
+
+GList *
+gevo_get_groups(void)
+{
+	GList *tmp = NULL;
+	char *tmp2;
+	GaimGroup *g;
+	GaimBlistNode *gnode;
+
+	if (gaim_get_blist()->root == NULL)
+	{
+		tmp2 = g_strdup(_("Buddies"));
+		tmp  = g_list_append(tmp, tmp2);
+	}
+	else
+	{
+		for (gnode = gaim_get_blist()->root;
+			 gnode != NULL;
+			 gnode = gnode->next)
+		{
+			if (GAIM_BLIST_NODE_IS_GROUP(gnode))
+			{
+				g    = (GaimGroup *)gnode;
+				tmp2 = g->name;
+				tmp  = g_list_append(tmp, tmp2);
+			}
+		}
+	}
+
+	return tmp;
+}
+
+EContactField
+gevo_prpl_get_field(GaimAccount *account, GaimBuddy *buddy)
+{
+	EContactField protocol_field = 0;
+	const char *protocol_id;
+
+	g_return_val_if_fail(account != NULL, 0);
+
+	protocol_id = gaim_account_get_protocol_id(account);
+
+	if (!strcmp(protocol_id, "prpl-oscar"))
+	{
+		GaimConnection *gc;
+		GaimPluginProtocolInfo *prpl_info;
+
+		gc = gaim_account_get_connection(account);
+
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+		if (!strcmp("aim", prpl_info->list_icon(account, buddy)))
+		{
+			protocol_field = E_CONTACT_IM_AIM;
+		}
+		else
+			protocol_field = E_CONTACT_IM_ICQ;
+	}
+	else if (!strcmp(protocol_id, "prpl-msn"))
+		protocol_field = E_CONTACT_IM_MSN;
+	else if (!strcmp(protocol_id, "prpl-yahoo"))
+		protocol_field = E_CONTACT_IM_YAHOO;
+	else if (!strcmp(protocol_id, "prpl-jabber"))
+		protocol_field = E_CONTACT_IM_JABBER;
+
+	return protocol_field;
+}
+
+gboolean
+gevo_prpl_is_supported(GaimAccount *account, GaimBuddy *buddy)
+{
+	return (gevo_prpl_get_field(account, buddy) != 0);
+}
+
+gboolean
+gevo_load_addressbook(EBook **book, GError **error)
+{
+	char *filename;
+	char *uri;
+	gboolean result;
+
+	g_return_val_if_fail(book != NULL, FALSE);
+
+	*book = e_book_new();
+
+	filename = g_build_filename(g_get_home_dir(),
+			".evolution/addressbook/local/OnThisComputer/Personal", NULL);
+
+	uri = g_strdup_printf("file://%s", filename);
+
+	g_free(filename);
+
+	result = e_book_load_uri(*book, uri, FALSE, error);
+
+	g_free(uri);
+
+	if (!result)
+	{
+		g_object_unref(*book);
+		*book = NULL;
+	}
+
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/gevolution.c	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,497 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "internal.h"
+#include "gtkinternal.h"
+#include "gtkblist.h"
+
+#include "connection.h"
+#include "debug.h"
+#include "prefs.h"
+#include "signals.h"
+
+#include "gtkconv.h"
+#include "gtkplugin.h"
+#include "gtkutils.h"
+
+#include "gevolution.h"
+
+#include <libedata-book/Evolution-DataServer-Addressbook.h>
+
+#include <libebook/e-book-listener.h>
+#include <libebook/e-book-async.h>
+#include <libedata-book/e-data-book-factory.h>
+#include <bonobo/bonobo-main.h>
+
+#define GEVOLUTION_PLUGIN_ID "gtk-x11-gevolution"
+
+#define E_DATA_BOOK_FACTORY_OAF_ID \
+	"OAFIID:GNOME_Evolution_DataServer_BookFactory"
+
+enum
+{
+	COLUMN_AUTOADD,
+	COLUMN_ICON,
+	COLUMN_SCREENNAME,
+	COLUMN_DATA,
+	NUM_COLUMNS
+};
+
+static GaimBlistUiOps *backup_blist_ui_ops = NULL;
+static GaimBlistUiOps *blist_ui_ops = NULL;
+static EBook *book = NULL;
+static gulong timer = 0;
+static gulong book_view_tag = 0;
+static EBookView *book_view = NULL;
+
+static void
+update_ims_from_contact(EContact *contact, const char *name,
+						const char *prpl_id, EContactField field)
+{
+	GList *ims = e_contact_get(contact, field);
+	GList *l, *l2;
+
+	if (ims == NULL)
+		return;
+
+	for (l = gaim_connections_get_all(); l != NULL; l = l->next)
+	{
+		GaimConnection *gc = (GaimConnection *)l->data;
+		GaimAccount *account = gaim_connection_get_account(gc);
+
+		if (strcmp(gaim_account_get_protocol_id(account), prpl_id))
+			continue;
+
+		if (!gaim_account_get_bool(account, "gevo-autoadd", FALSE))
+			continue;
+
+		for (l2 = ims; l2 != NULL; l2 = l2->next)
+		{
+			if (gaim_find_buddy(account, l2->data) != NULL)
+				continue;
+
+			gevo_add_buddy(account, _("Buddies"), l2->data, name);
+		}
+	}
+
+	g_list_foreach(ims, (GFunc)g_free, NULL);
+	g_list_free(ims);
+}
+
+static void
+update_buddies_from_contact(EContact *contact)
+{
+	const char *name;
+
+	name = e_contact_get_const(contact, E_CONTACT_FULL_NAME);
+
+	update_ims_from_contact(contact, name, "prpl-oscar",  E_CONTACT_IM_AIM);
+	update_ims_from_contact(contact, name, "prpl-jabber", E_CONTACT_IM_JABBER);
+	update_ims_from_contact(contact, name, "prpl-yahoo",  E_CONTACT_IM_YAHOO);
+	update_ims_from_contact(contact, name, "prpl-msn",    E_CONTACT_IM_MSN);
+	update_ims_from_contact(contact, name, "prpl-oscar",  E_CONTACT_IM_ICQ);
+}
+
+static void
+contacts_changed_cb(EBookView *book_view, const GList *contacts)
+{
+	const GList *l;
+
+	if (gaim_connections_get_all() == NULL)
+		return;
+
+	for (l = contacts; l != NULL; l = l->next)
+	{
+		EContact *contact = (EContact *)l->data;
+
+		update_buddies_from_contact(contact);
+	}
+}
+
+static void
+request_add_buddy(GaimAccount *account, const char *username,
+				  const char *group, const char *alias)
+{
+	if (book == NULL)
+	{
+		backup_blist_ui_ops->request_add_buddy(account, username, group,
+											   alias);
+	}
+	else
+	{
+		gevo_add_buddy_dialog_show(account, username, group, alias);
+	}
+}
+
+static void
+got_book_view_cb(EBook *book, EBookStatus status, EBookView *view,
+				 gpointer user_data)
+{
+	book_view_tag = 0;
+
+	if (status != E_BOOK_ERROR_OK)
+	{
+		gaim_debug_error("evolution", "Unable to retrieve book view! :(\n");
+
+		return;
+	}
+
+	book_view = view;
+
+	g_object_ref(book_view);
+
+	g_signal_connect(G_OBJECT(book_view), "contacts_changed",
+					 G_CALLBACK(contacts_changed_cb), book);
+
+	g_signal_connect(G_OBJECT(book_view), "contacts_added",
+					 G_CALLBACK(contacts_changed_cb), book);
+
+	e_book_view_start(view);
+}
+
+static void
+signed_on_cb(GaimConnection *gc)
+{
+	EBookQuery *query;
+	gboolean status;
+	GList *contacts;
+	GList *l;
+
+	if (book == NULL)
+		return;
+
+	query = e_book_query_any_field_contains("");
+
+	status = e_book_get_contacts(book, query, &contacts, NULL);
+
+	e_book_query_unref(query);
+
+	if (!status)
+		return;
+
+	for (l = contacts; l != NULL; l = l->next)
+	{
+		EContact *contact = E_CONTACT(l->data);
+
+		update_buddies_from_contact(contact);
+
+		g_object_unref(contact);
+	}
+
+	g_list_free(contacts);
+}
+
+static void
+menu_item_activate_cb(GtkWidget *item, GaimBuddy *buddy)
+{
+	gevo_associate_buddy_dialog_new(buddy);
+}
+
+static void
+drawing_menu_cb(GtkWidget *menu, GaimBuddy *buddy)
+{
+	GtkWidget *item;
+
+	if (gevo_prpl_is_supported(buddy->account, buddy))
+	{
+		item = gtk_menu_item_new_with_label(_("Add to Address Book"));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+		g_signal_connect(G_OBJECT(item), "activate",
+						 G_CALLBACK(menu_item_activate_cb), buddy);
+	}
+}
+
+static gboolean
+load_timeout(gpointer data)
+{
+	GaimPlugin *plugin = (GaimPlugin *)data;
+
+	timer = 0;
+
+	if (!gevo_load_addressbook(&book, NULL))
+		return FALSE;
+
+	book_view_tag = e_book_async_get_book_view(book,
+		"(contains \"x-evolution-any-field\" \"\")",
+		got_book_view_cb, NULL);
+
+	gaim_signal_connect(GAIM_GTK_BLIST(gaim_get_blist()), "drawing-menu",
+						plugin, GAIM_CALLBACK(drawing_menu_cb), NULL);
+
+	return FALSE;
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+	if (!bonobo_init_full(NULL, NULL, bonobo_activation_orb_get(),
+						  CORBA_OBJECT_NIL, CORBA_OBJECT_NIL))
+	{
+		gaim_debug_error("evolution", "Unable to initialize bonobo.\n");
+		return FALSE;
+	}
+
+	bonobo_activate();
+
+	backup_blist_ui_ops = gaim_blist_get_ui_ops();
+
+	blist_ui_ops = g_memdup(backup_blist_ui_ops, sizeof(GaimBlistUiOps));
+	blist_ui_ops->request_add_buddy = request_add_buddy;
+
+	gaim_blist_set_ui_ops(blist_ui_ops);
+
+	gaim_signal_connect(gaim_connections_get_handle(), "signed-on",
+						plugin, GAIM_CALLBACK(signed_on_cb), NULL);
+
+	timer = g_timeout_add(1, load_timeout, plugin);
+
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+	gaim_blist_set_ui_ops(backup_blist_ui_ops);
+
+	g_free(blist_ui_ops);
+
+	backup_blist_ui_ops = NULL;
+	blist_ui_ops = NULL;
+
+	if (book_view != NULL)
+	{
+		e_book_view_stop(book_view);
+		g_object_unref(book_view);
+		book_view = NULL;
+	}
+
+	if (book != NULL)
+	{
+		g_object_unref(book);
+		book = NULL;
+	}
+
+	bonobo_debug_shutdown();
+
+	return TRUE;
+}
+
+static void
+plugin_destroy(GaimPlugin *plugin)
+{
+}
+
+static void
+autoadd_toggled_cb(GtkCellRendererToggle *renderer, gchar *path_str,
+				   gpointer data)
+{
+	GaimAccount *account;
+	GtkTreeModel *model = (GtkTreeModel *)data;
+	GtkTreeIter iter;
+	gboolean autoadd;
+
+	gtk_tree_model_get_iter_from_string(model, &iter, path_str);
+	gtk_tree_model_get(model, &iter,
+					   COLUMN_DATA, &account,
+					   COLUMN_AUTOADD, &autoadd,
+					   -1);
+
+	gaim_account_set_bool(account, "gevo-autoadd", !autoadd);
+
+	gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+					   COLUMN_AUTOADD, !autoadd,
+					   -1);
+}
+
+static GtkWidget *
+get_config_frame(GaimPlugin *plugin)
+{
+	GtkWidget *ret;
+	GtkWidget *vbox;
+	GtkWidget *label;
+	GtkWidget *sw;
+	GtkWidget *treeview;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GdkPixbuf *pixbuf, *scale = NULL;
+	GtkListStore *model;
+	GList *l;
+
+	/* Outside container */
+	ret = gtk_vbox_new(FALSE, 18);
+	gtk_container_set_border_width(GTK_CONTAINER(ret), 12);
+
+	/* Configuration frame */
+	vbox = gaim_gtk_make_frame(ret, _("Evolution Integration Configuration"));
+
+	/* Label */
+	label = gtk_label_new(_("Select all accounts that buddies should be "
+							"auto-added to."));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	/* Scrolled window */
+	sw = gtk_scrolled_window_new(0, 0);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_AUTOMATIC,
+								   GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+										GTK_SHADOW_IN);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_set_size_request(sw, 300, 300);
+	gtk_widget_show(sw);
+
+	/* Create the list model for the treeview. */
+	model = gtk_list_store_new(NUM_COLUMNS,
+							   G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF,
+							   G_TYPE_STRING, G_TYPE_POINTER);
+
+	/* Setup the treeview */
+	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+	gtk_widget_show(treeview);
+
+	/* Setup the column */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Account"));
+	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
+
+	/* Checkbox */
+	renderer = gtk_cell_renderer_toggle_new();
+
+	g_signal_connect(G_OBJECT(renderer), "toggled",
+					 G_CALLBACK(autoadd_toggled_cb), model);
+
+	gtk_tree_view_column_pack_start(column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "active", COLUMN_AUTOADD);
+
+	/* Icon */
+	renderer = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "pixbuf", COLUMN_ICON);
+
+	/* Screenname */
+	renderer = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute(column, renderer,
+									   "text", COLUMN_SCREENNAME);
+
+
+	/* Populate */
+	for (l = gaim_accounts_get_all(); l != NULL; l = l->next)
+	{
+		GaimAccount *account = (GaimAccount *)l->data;
+		GtkTreeIter iter;
+
+		gaim_debug_info("evolution", "Adding account\n");
+
+		gtk_list_store_append(model, &iter);
+
+		pixbuf = create_prpl_icon(account);
+
+		if (pixbuf != NULL)
+		{
+			scale = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
+											GDK_INTERP_BILINEAR);
+
+			if (!gaim_account_is_connected(account))
+				gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.0, FALSE);
+		}
+
+		gtk_list_store_set(model, &iter,
+						   COLUMN_AUTOADD,
+						   gaim_account_get_bool(account, "gevo-autoadd",
+												 FALSE),
+						   COLUMN_ICON, scale,
+						   COLUMN_SCREENNAME,
+						   gaim_account_get_username(account),
+						   COLUMN_DATA, account,
+						   -1);
+
+		if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf));
+		if (scale  != NULL) g_object_unref(G_OBJECT(scale));
+	}
+
+	gtk_widget_show_all(ret);
+
+	return ret;
+}
+
+static GaimGtkPluginUiInfo ui_info =
+{
+	get_config_frame
+};
+
+static GaimPluginInfo info =
+{
+	2,                                                /**< api_version    */
+	GAIM_PLUGIN_STANDARD,                             /**< type           */
+	GAIM_GTK_PLUGIN_TYPE,                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
+
+	GEVOLUTION_PLUGIN_ID,                             /**< id             */
+	N_("Evolution Integration"),                      /**< name           */
+	VERSION,                                          /**< version        */
+	                                                  /**  summary        */
+	N_("Provides integration with Ximian Evolution."),
+	                                                  /**  description    */
+	N_("Provides integration with Ximian Evolution."),
+	"Christian Hammond <chipx86@gnupdate.org>",       /**< author         */
+	GAIM_WEBSITE,                                     /**< homepage       */
+
+	plugin_load,                                      /**< load           */
+	plugin_unload,                                    /**< unload         */
+	plugin_destroy,                                   /**< destroy        */
+
+	&ui_info,                                         /**< ui_info        */
+	NULL                                              /**< extra_info     */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+	/* TODO: Change to core-remote when possible. */
+	/* info.dependencies = g_list_append(info.dependencies, "gtk-remote"); */
+
+	/*
+	 * I'm going to rant a little bit here...
+	 *
+	 * For some reason, when we init bonobo from inside a plugin, it will
+	 * segfault when destroyed. The backtraces are within gmodule somewhere.
+	 * There's not much I can do, and I'm not sure where the bug lies.
+	 * However, plugins are only destroyed when Gaim is shutting down. After
+	 * destroying the plugins, gaim ends, and anything else is of course
+	 * freed. That includes this, if we make the module resident, which
+	 * prevents us from being able to actually unload it.
+	 *
+	 * So, in conclusion, this is an evil hack, but it doesn't harm anything
+	 * and it works.
+	 */
+	g_module_make_resident(plugin->handle);
+}
+
+GAIM_INIT_PLUGIN(gevolution, init_plugin, info)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/gevolution.h	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,107 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef _GEVOLUTION_H_
+#define _GEVOLUTION_H_
+
+#include <libebook/e-book.h>
+
+typedef struct
+{
+	GaimAccount *account;
+	char *username;
+
+	EBook *book;
+
+	GtkWidget *win;
+	GtkWidget *treeview;
+	GtkWidget *addressbooks_menu;
+	GtkWidget *search_field;
+	GtkWidget *group_combo;
+	GtkWidget *select_button;
+	GtkWidget *account_optmenu;
+	GtkListStore *model;
+
+	GList *contacts;
+
+} GevoAddBuddyDialog;
+
+typedef struct
+{
+	gboolean person_only;
+
+	GaimAccount *account;
+	GaimBuddy *buddy;
+
+	EContact *contact;
+
+	GtkWidget *win;
+	GtkWidget *accounts_menu;
+	GtkWidget *screenname;
+	GtkWidget *firstname;
+	GtkWidget *lastname;
+	GtkWidget *email;
+	GtkWidget *group_combo;
+	GtkWidget *add_button;
+
+	char *buddy_icon;
+
+} GevoNewPersonDialog;
+
+typedef struct
+{
+	GaimBuddy *buddy;
+
+	EBook *book;
+
+	GtkWidget *win;
+	GtkWidget *treeview;
+	GtkWidget *addressbooks_menu;
+	GtkWidget *search_field;
+	GtkWidget *assoc_button;
+	GtkWidget *imhtml;
+	GtkListStore *model;
+
+	GList *contacts;
+
+} GevoAssociateBuddyDialog;
+
+void gevo_add_buddy_dialog_show(GaimAccount *account, const char *username,
+								const char *group, const char *alias);
+void gevo_add_buddy_dialog_add_person(GevoAddBuddyDialog *dialog,
+									  EContact *contact,
+									  const char *name, GaimAccount *account,
+									  const char *screenname);
+
+void gevo_new_person_dialog_show(EContact *contact, GaimAccount *account,
+								 const char *username, const char *group,
+								 GaimBuddy *buddy, gboolean person_only);
+
+void gevo_add_buddy(GaimAccount *account, const char *group_name,
+					const char *screenname, const char *alias);
+GList *gevo_get_groups(void);
+
+EContactField gevo_prpl_get_field(GaimAccount *account, GaimBuddy *buddy);
+gboolean gevo_prpl_is_supported(GaimAccount *account, GaimBuddy *buddy);
+gboolean gevo_load_addressbook(EBook **book, GError **error);
+
+GevoAssociateBuddyDialog *gevo_associate_buddy_dialog_new(GaimBuddy *buddy);
+
+#endif /* _GEVOLUTION_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gevolution/new_person_dialog.c	Tue Jan 13 01:45:32 2004 +0000
@@ -0,0 +1,427 @@
+/*
+ * Evolution integration plugin for Gaim
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include "gtkinternal.h"
+#include "gtkutils.h"
+
+#include "debug.h"
+
+#include "gevolution.h"
+
+#include <libebook/e-book-async.h>
+#include <libedataserver/e-source-list.h>
+
+static GtkWidget *
+add_pref_box(GtkSizeGroup *sg, GtkWidget *parent, const char *text,
+			 GtkWidget *widget)
+{
+	GtkWidget *hbox;
+	GtkWidget *label;
+
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(parent), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new_with_mnemonic(text);
+	gtk_size_group_add_widget(sg, label);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
+	gtk_widget_show(widget);
+
+	return hbox;
+}
+
+static gint
+delete_win_cb(GtkWidget *w, GdkEvent *event, GevoNewPersonDialog *dialog)
+{
+	gtk_widget_destroy(dialog->win);
+
+	g_free(dialog);
+
+	return 0;
+}
+
+static void
+cancel_cb(GtkWidget *w, GevoNewPersonDialog *dialog)
+{
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+screenname_changed_cb(GtkEntry *entry, GevoNewPersonDialog *dialog)
+{
+	gtk_widget_set_sensitive(dialog->add_button,
+							 *gtk_entry_get_text(entry) != '\0');
+}
+
+static void
+person_info_changed_cb(GtkEntry *entry, GevoNewPersonDialog *dialog)
+{
+	gtk_widget_set_sensitive(dialog->add_button,
+		(*gtk_entry_get_text(GTK_ENTRY(dialog->firstname)) != '\0' ||
+		 *gtk_entry_get_text(GTK_ENTRY(dialog->lastname))  != '\0'));
+}
+
+static void
+add_cb(GtkWidget *w, GevoNewPersonDialog *dialog)
+{
+	EContact *contact = NULL;
+	EBook *book;
+	const char *screenname;
+	const char *firstname;
+	const char *lastname;
+	const char *email;
+	const char *im_service;
+	gboolean new_contact = FALSE;
+	EContactField field = 0;
+	EContactName *name = NULL;
+	char *full_name = NULL;
+
+	if (dialog->person_only)
+		screenname = dialog->buddy->name;
+	else
+		screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname));
+
+	firstname  = gtk_entry_get_text(GTK_ENTRY(dialog->firstname));
+	lastname   = gtk_entry_get_text(GTK_ENTRY(dialog->lastname));
+	email      = gtk_entry_get_text(GTK_ENTRY(dialog->email));
+
+	if (*firstname || *lastname)
+	{
+		if (!gevo_load_addressbook(&book, NULL))
+		{
+			gaim_debug_error("evolution",
+							 "Error retrieving default addressbook\n");
+
+			return;
+		}
+
+		if (dialog->contact == NULL)
+		{
+			char *file_as;
+
+			dialog->contact = e_contact_new();
+
+			if (lastname != NULL && firstname != NULL)
+				file_as = g_strdup_printf("%s, %s", lastname, firstname);
+			else if (lastname != NULL)
+				file_as = g_strdup(lastname);
+			else
+				file_as = g_strdup(firstname);
+
+			e_contact_set(contact, E_CONTACT_FILE_AS, file_as);
+
+			g_free(file_as);
+
+			new_contact = TRUE;
+		}
+
+		contact = dialog->contact;
+
+		name = e_contact_name_new();
+
+		name->given  = g_strdup(firstname);
+		name->family = g_strdup(lastname);
+
+		full_name = e_contact_name_to_string(name);
+		e_contact_set(contact, E_CONTACT_FULL_NAME, full_name);
+
+		im_service = gaim_account_get_protocol_id(dialog->account);
+
+		if (*email)
+			e_contact_set(contact, E_CONTACT_EMAIL_1, (gpointer)email);
+
+		if (!strcmp(im_service, "prpl-oscar"))
+		{
+			if (isdigit(*screenname))
+				field = E_CONTACT_IM_ICQ;
+			else
+				field = E_CONTACT_IM_AIM;
+		}
+		else if (!strcmp(im_service, "prpl-yahoo"))
+			field = E_CONTACT_IM_YAHOO;
+		else if (!strcmp(im_service, "prpl-jabber"))
+			field = E_CONTACT_IM_JABBER;
+		else if (!strcmp(im_service, "prpl-msn"))
+			field = E_CONTACT_IM_MSN;
+
+		if (field > 0)
+		{
+			GList *list = g_list_append(NULL, g_strdup(screenname));
+
+			e_contact_set(contact, field, list);
+
+			g_free(list->data);
+			g_list_free(list);
+		}
+
+		if (new_contact)
+		{
+			if (!e_book_add_contact(book, contact, NULL))
+			{
+				gaim_debug_error("evolution", "Error adding contact to book\n");
+
+				g_object_unref(contact);
+				delete_win_cb(NULL, NULL, dialog);
+				return;
+			}
+		}
+		else
+		{
+			if (!e_book_commit_contact(book, contact, NULL))
+			{
+				gaim_debug_error("evolution", "Error adding contact to book\n");
+
+				g_object_unref(contact);
+				delete_win_cb(NULL, NULL, dialog);
+				return;
+			}
+		}
+
+		g_object_unref(contact);
+	}
+
+	if (!dialog->person_only)
+	{
+		GtkWidget *entry = GTK_COMBO(dialog->group_combo)->entry;
+		const char *group_name;
+
+		group_name = gtk_entry_get_text(GTK_ENTRY(entry));
+
+		gevo_add_buddy(dialog->account, group_name, screenname, full_name);
+	}
+
+	if (name != NULL)
+		e_contact_name_free(name);
+
+	if (full_name != NULL)
+		g_free(full_name);
+
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+static void
+select_account_cb(GObject *w, GaimAccount *account,
+				  GevoNewPersonDialog *dialog)
+{
+	dialog->account = account;
+}
+
+void
+gevo_new_person_dialog_show(EContact *contact, GaimAccount *account,
+							const char *username, const char *group,
+							GaimBuddy *buddy, gboolean person_only)
+{
+	GevoNewPersonDialog *dialog;
+	GtkWidget *vbox, *vbox2;
+	GtkWidget *hbox;
+	GtkWidget *bbox;
+	GtkWidget *label;
+	GtkWidget *button;
+	GtkWidget *sep;
+	GtkSizeGroup *sg, *sg2;
+	const char *str;
+
+	g_return_if_fail(!person_only || (person_only && buddy));
+
+	dialog = g_new0(GevoNewPersonDialog, 1);
+
+	dialog->account = account;
+	dialog->person_only = person_only;
+	dialog->buddy = buddy;
+
+	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_role(GTK_WINDOW(dialog->win), "new_person");
+	gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE);
+	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+
+	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Setup the vbox */
+	vbox = gtk_vbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(dialog->win), vbox);
+	gtk_widget_show(vbox);
+
+	/* Label */
+	if (person_only)
+	{
+		label = gtk_label_new(
+			_("Please enter the person's information below."));
+	}
+	else
+	{
+		label = gtk_label_new(_("Please enter the buddy's screen name and "
+								"account type below."));
+	}
+
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+	gtk_widget_show(label);
+
+	/* Setup the size groups */
+	sg  = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	sg2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	if (!person_only)
+	{
+		/* Add the account type stuff. */
+		dialog->accounts_menu =
+			gaim_gtk_account_option_menu_new(account, FALSE,
+											 G_CALLBACK(select_account_cb),
+											 NULL, dialog);
+		add_pref_box(sg, vbox, _("Account type:"), dialog->accounts_menu);
+
+		/* Screen Name */
+		dialog->screenname = gtk_entry_new();
+		add_pref_box(sg, vbox, _("Screenname:"), dialog->screenname);
+
+		if (username != NULL)
+			gtk_entry_set_text(GTK_ENTRY(dialog->screenname), username);
+
+		g_signal_connect(G_OBJECT(dialog->screenname), "changed",
+						 G_CALLBACK(screenname_changed_cb), dialog);
+
+		/* Group */
+		dialog->group_combo = gtk_combo_new();
+		gtk_combo_set_popdown_strings(GTK_COMBO(dialog->group_combo),
+									  gevo_get_groups());
+		add_pref_box(sg, vbox, _("Group:"), dialog->group_combo);
+
+		/* Separator */
+		sep = gtk_hseparator_new();
+		gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+		gtk_widget_show(sep);
+
+		/* Optional Information section */
+		label = gtk_label_new(_("Optional information:"));
+		gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+		gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+		gtk_widget_show(label);
+	}
+
+	/* Create the parent hbox for this whole thing. */
+	hbox = gtk_hbox_new(FALSE, 12);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
+	gtk_widget_show(hbox);
+
+#if 0
+	/* Now the left side of the hbox */
+	vbox2 = gtk_vbox_new(FALSE, 12);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
+	gtk_widget_show(vbox2);
+
+	/* Buddy icon button */
+	button = gtk_button_new_from_stock(GTK_STOCK_OPEN);
+	gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	/* Label */
+	label = gtk_label_new(_("Buddy Icon"));
+	gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+#endif
+
+	/* Now the right side. */
+	vbox2 = gtk_vbox_new(FALSE, 12);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0);
+	gtk_widget_show(vbox2);
+
+	/* First Name field */
+	dialog->firstname = gtk_entry_new();
+	add_pref_box(sg2, vbox2, _("First name:"), dialog->firstname);
+
+	if (contact != NULL)
+	{
+		str = e_contact_get_const(contact, E_CONTACT_GIVEN_NAME);
+
+		if (str != NULL)
+			gtk_entry_set_text(GTK_ENTRY(dialog->firstname), str);
+	}
+
+	/* Last Name field */
+	dialog->lastname = gtk_entry_new();
+	add_pref_box(sg2, vbox2, _("Last name:"), dialog->lastname);
+
+	if (contact != NULL)
+	{
+		str = e_contact_get_const(contact, E_CONTACT_FAMILY_NAME);
+
+		if (str != NULL)
+			gtk_entry_set_text(GTK_ENTRY(dialog->lastname), str);
+	}
+
+	if (person_only)
+	{
+		g_signal_connect(G_OBJECT(dialog->firstname), "changed",
+						 G_CALLBACK(person_info_changed_cb), dialog);
+		g_signal_connect(G_OBJECT(dialog->lastname), "changed",
+						 G_CALLBACK(person_info_changed_cb), dialog);
+	}
+
+	/* E-Mail address field */
+	dialog->email = gtk_entry_new();
+	add_pref_box(sg2, vbox2, _("E-mail:"), dialog->email);
+
+	if (contact != NULL)
+	{
+		str = e_contact_get_const(contact, E_CONTACT_EMAIL_1);
+
+		if (str != NULL)
+			gtk_entry_set_text(GTK_ENTRY(dialog->email), str);
+	}
+
+	/* Separator */
+	sep = gtk_hseparator_new();
+	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+	gtk_widget_show(sep);
+
+	/* Button box */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* Cancel button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(cancel_cb), dialog);
+
+	/* Add button */
+	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	dialog->add_button = button;
+	gtk_widget_set_sensitive(button, FALSE);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(add_cb), dialog);
+
+	/* Show it. */
+	gtk_widget_show(dialog->win);
+}
--- a/src/gtkblist.c	Tue Jan 13 01:00:24 2004 +0000
+++ b/src/gtkblist.c	Tue Jan 13 01:45:32 2004 +0000
@@ -1072,6 +1072,138 @@
 	}
 }
 
+static void
+add_buddies_from_vcard(const char *prpl_id, GaimGroup *group, GList *list,
+					   const char *alias)
+{
+	GList *l;
+	GaimAccount *account = NULL;
+	GaimConnection *gc;
+
+	if (list == NULL)
+		return;
+
+	for (l = gaim_connections_get_all(); l != NULL; l = l->next)
+	{
+		gc = (GaimConnection *)l->data;
+		account = gaim_connection_get_account(gc);
+
+		if (!strcmp(gaim_account_get_protocol_id(account), prpl_id))
+			break;
+
+		account = NULL;
+	}
+
+	if (account != NULL)
+	{
+		for (l = list; l != NULL; l = l->next)
+		{
+			gaim_blist_request_add_buddy(account, l->data,
+										 (group ? group->name : NULL),
+										 alias);
+		}
+	}
+
+	g_list_foreach(list, (GFunc)g_free, NULL);
+	g_list_free(list);
+}
+
+static gboolean
+parse_vcard(const char *vcard, GaimGroup *group)
+{
+	char *temp_vcard;
+	char *s, *c;
+	char *alias    = NULL;
+	GList *aims    = NULL;
+	GList *icqs    = NULL;
+	GList *yahoos  = NULL;
+	GList *msns    = NULL;
+	GList *jabbers = NULL;
+
+	s = temp_vcard = g_strdup(vcard);
+
+	while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
+	{
+		char *field, *value;
+
+		field = s;
+
+		/* Grab the field */
+		while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
+			s++;
+
+		if (*s == '\r') s++;
+		if (*s == '\n')
+		{
+			s++;
+			continue;
+		}
+
+		if (*s != '\0') *s++ = '\0';
+
+		if ((c = strchr(field, ';')) != NULL)
+			*c = '\0';
+
+		/* Proceed to the end of the line */
+		value = s;
+
+		while (*s != '\r' && *s != '\n' && *s != '\0')
+			s++;
+
+		if (*s == '\r') *s++ = '\0';
+		if (*s == '\n') *s++ = '\0';
+
+		/* We only want to worry about a few fields here. */
+		if (!strcmp(field, "FN"))
+			alias = g_strdup(value);
+		else if (!strcmp(field, "X-AIM") || !strcmp(field, "X-ICQ") ||
+				 !strcmp(field, "X-YAHOO") || !strcmp(field, "X-MSN") ||
+				 !strcmp(field, "X-JABBER"))
+		{
+			char **values = g_strsplit(value, ":", 0);
+			char **im;
+
+			for (im = values; *im != NULL; im++)
+			{
+				if (!strcmp(field, "X-AIM"))
+					aims = g_list_append(aims, g_strdup(*im));
+				else if (!strcmp(field, "X-ICQ"))
+					icqs = g_list_append(icqs, g_strdup(*im));
+				else if (!strcmp(field, "X-YAHOO"))
+					yahoos = g_list_append(yahoos, g_strdup(*im));
+				else if (!strcmp(field, "X-MSN"))
+					msns = g_list_append(msns, g_strdup(*im));
+				else if (!strcmp(field, "X-JABBER"))
+					jabbers = g_list_append(jabbers, g_strdup(*im));
+			}
+
+			g_strfreev(values);
+		}
+	}
+
+	g_free(temp_vcard);
+
+	if (aims == NULL && icqs == NULL && yahoos == NULL &&
+		msns == NULL && jabbers == NULL)
+	{
+		if (alias != NULL)
+			g_free(alias);
+
+		return FALSE;
+	}
+
+	add_buddies_from_vcard("prpl-oscar",  group, aims,    alias);
+	add_buddies_from_vcard("prpl-oscar",  group, icqs,    alias);
+	add_buddies_from_vcard("prpl-yahoo",  group, yahoos,  alias);
+	add_buddies_from_vcard("prpl-msn",    group, msns,    alias);
+	add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
+
+	if (alias != NULL)
+		g_free(alias);
+
+	return TRUE;
+}
+
 static void gaim_gtk_blist_drag_data_get_cb (GtkWidget *widget,
 					     GdkDragContext *dc,
 					     GtkSelectionData *data,
@@ -1079,7 +1211,8 @@
 					     guint time,
 					     gpointer *null)
 {
-	if (data->target == gdk_atom_intern("GAIM_BLIST_NODE", FALSE)) {
+	if (data->target == gdk_atom_intern("GAIM_BLIST_NODE", FALSE))
+	{
 		GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
 		GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
 		GtkTreeIter iter;
@@ -1098,9 +1231,8 @@
 
 		gtk_tree_path_free(sourcerow);
 	}
-	else if (data->target == gdk_atom_intern("application/x-im-contact",
-											 FALSE)) {
-
+	else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
+	{
 		GtkTreeRowReference *ref;
 		GtkTreePath *sourcerow;
 		GtkTreeIter iter;
@@ -1400,6 +1532,45 @@
 
 		gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
 	}
+	else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
+	{
+		gboolean result;
+		GaimGroup *group = NULL;
+		GtkTreePath *path = NULL;
+		GtkTreeViewDropPosition position;
+
+		if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
+											  x, y, &path, &position))
+		{
+			GtkTreeIter iter;
+			GaimBlistNode *node;
+			GValue val = {0};
+
+			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);
+			node = g_value_get_pointer(&val);
+
+			if (GAIM_BLIST_NODE_IS_BUDDY(node))
+			{
+				group = (GaimGroup *)node->parent->parent;
+			}
+			else if (GAIM_BLIST_NODE_IS_CHAT(node) ||
+					 GAIM_BLIST_NODE_IS_CONTACT(node))
+			{
+				group = (GaimGroup *)node->parent;
+			}
+			else if (GAIM_BLIST_NODE_IS_GROUP(node))
+			{
+				group = (GaimGroup *)node;
+			}
+		}
+
+		result = parse_vcard(sd->data, group);
+
+		gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
+	}
 }
 
 static void gaim_gtk_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, GaimBlistNode *node)
@@ -2335,7 +2506,7 @@
 	}
 }
 
-enum {DRAG_BUDDY, DRAG_ROW};
+enum {DRAG_BUDDY, DRAG_ROW, DRAG_VCARD};
 
 static char *
 item_factory_translate_func (const char *path, gpointer func_data)
@@ -2374,7 +2545,8 @@
 	GtkAccelGroup *accel_group;
 	GtkTreeSelection *selection;
 	GtkTargetEntry gte[] = {{"GAIM_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
-				{"application/x-im-contact", 0, DRAG_BUDDY}};
+				{"application/x-im-contact", 0, DRAG_BUDDY},
+				{"text/x-vcard", 0, DRAG_VCARD }};
 
 	if (gtkblist && gtkblist->window) {
 		gtk_widget_show(gtkblist->window);
@@ -2440,10 +2612,10 @@
 
 	/* Set up dnd */
 	gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
-										   GDK_BUTTON1_MASK, gte, 2, 
+										   GDK_BUTTON1_MASK, gte, 3, 
 										   GDK_ACTION_COPY);
 	gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
-										 gte, 2,
+										 gte, 3,
 										 GDK_ACTION_COPY | GDK_ACTION_MOVE);
 
   	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(gaim_gtk_blist_drag_data_rcv_cb), NULL);