evolution-3.6.4/plugins/bbdb/bbdb.c

No issues found

  1 /*
  2  * This program is free software; you can redistribute it and/or
  3  * modify it under the terms of the GNU Lesser General Public
  4  * License as published by the Free Software Foundation; either
  5  * version 2 of the License, or (at your option) version 3.
  6  *
  7  * This program is distributed in the hope that it will be useful,
  8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 10  * Lesser General Public License for more details.
 11  *
 12  * You should have received a copy of the GNU Lesser General Public
 13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 14  *
 15  *
 16  * Authors:
 17  *		Nat Friedman <nat@novell.com>
 18  *
 19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 20  *
 21  */
 22 
 23 #ifdef HAVE_CONFIG_H
 24 #include <config.h>
 25 #endif
 26 
 27 #include <gtk/gtk.h>
 28 #include <glib/gi18n.h>
 29 #include <string.h>
 30 
 31 #include <libedataserverui/libedataserverui.h>
 32 
 33 #include <e-util/e-config.h>
 34 #include <addressbook/gui/widgets/eab-config.h>
 35 #include <mail/em-event.h>
 36 #include <composer/e-msg-composer.h>
 37 
 38 #include "bbdb.h"
 39 
 40 #define d(x)
 41 
 42 /* Plugin hooks */
 43 gint e_plugin_lib_enable (EPlugin *ep, gint enable);
 44 void bbdb_handle_send (EPlugin *ep, EMEventTargetComposer *target);
 45 GtkWidget *bbdb_page_factory (EPlugin *ep, EConfigHookItemFactoryData *hook_data);
 46 
 47 /* For internal use */
 48 struct bbdb_stuff {
 49 	EABConfigTargetPrefs *target;
 50 
 51 	GtkWidget *combo_box;
 52 	GtkWidget *gaim_combo_box;
 53 	GtkWidget *check;
 54 	GtkWidget *check_gaim;
 55 };
 56 
 57 /* Static forward declarations */
 58 static gboolean bbdb_timeout (gpointer data);
 59 static void bbdb_do_it (EBookClient *client, const gchar *name, const gchar *email);
 60 static void add_email_to_contact (EContact *contact, const gchar *email);
 61 static void enable_toggled_cb (GtkWidget *widget, gpointer data);
 62 static void source_changed_cb (ESourceComboBox *source_combo_box, struct bbdb_stuff *stuff);
 63 static GtkWidget *create_addressbook_combo_box (struct bbdb_stuff *stuff, gint type);
 64 static void cleanup_cb (GObject *o, gpointer data);
 65 
 66 /* How often check, in minutes. Read only on plugin enable. Use <= 0 to disable polling. */
 67 static gint
 68 get_check_interval (void)
 69 {
 70 	GSettings *settings;
 71 	gint res = BBDB_BLIST_DEFAULT_CHECK_INTERVAL;
 72 
 73 	settings = g_settings_new (CONF_SCHEMA);
 74 	res = g_settings_get_int (settings, CONF_KEY_GAIM_CHECK_INTERVAL);
 75 
 76 	g_object_unref (settings);
 77 
 78 	return res * 60;
 79 }
 80 
 81 gint
 82 e_plugin_lib_enable (EPlugin *ep,
 83                      gint enable)
 84 {
 85 	static guint update_source = 0;
 86 
 87 	if (update_source) {
 88 		g_source_remove (update_source);
 89 		update_source = 0;
 90 	}
 91 
 92 	/* Start up the plugin. */
 93 	if (enable) {
 94 		gint interval;
 95 
 96 		d (fprintf (stderr, "BBDB spinning up...\n"));
 97 
 98 		g_idle_add (bbdb_timeout, ep);
 99 
100 		interval = get_check_interval ();
101 		if (interval > 0)
102 			update_source = g_timeout_add_seconds (interval, (GSourceFunc) bbdb_timeout, NULL);
103 	}
104 
105 	return 0;
106 }
107 
108 static gboolean
109 bbdb_timeout (gpointer data)
110 {
111 	if (bbdb_check_gaim_enabled ())
112 		bbdb_sync_buddy_list_check ();
113 
114 	/* not a NULL for a one-time idle check, thus stop it there */
115 	return data == NULL;
116 }
117 
118 typedef struct
119 {
120 	gchar *name;
121 	gchar *email;
122 } todo_struct;
123 
124 static void
125 free_todo_struct (todo_struct *td)
126 {
127 	if (td) {
128 		g_free (td->name);
129 		g_free (td->email);
130 		g_free (td);
131 	}
132 }
133 
134 static GSList *todo = NULL;
135 G_LOCK_DEFINE_STATIC (todo);
136 
137 static gpointer
138 bbdb_do_in_thread (gpointer data)
139 {
140 	EBookClient *client = data;
141 
142 	/* Open the addressbook */
143 	if (!client || !bbdb_open_book_client (client)) {
144 		G_LOCK (todo);
145 
146 		g_slist_foreach (todo, (GFunc) free_todo_struct, NULL);
147 		g_slist_free (todo);
148 		todo = NULL;
149 
150 		G_UNLOCK (todo);
151 		return NULL;
152 	}
153 
154 	G_LOCK (todo);
155 	while (todo) {
156 		todo_struct *td = todo->data;
157 
158 		todo = g_slist_remove (todo, td);
159 
160 		G_UNLOCK (todo);
161 
162 		if (td) {
163 			bbdb_do_it (client, td->name, td->email);
164 			free_todo_struct (td);
165 		}
166 
167 		G_LOCK (todo);
168 	}
169 	G_UNLOCK (todo);
170 
171 	g_object_unref (client);
172 
173 	return NULL;
174 }
175 
176 static void
177 bbdb_do_thread (const gchar *name,
178                 const gchar *email)
179 {
180 	todo_struct *td;
181 
182 	if (!name && !email)
183 		return;
184 
185 	td = g_new (todo_struct, 1);
186 	td->name = g_strdup (name);
187 	td->email = g_strdup (email);
188 
189 	G_LOCK (todo);
190 	if (todo) {
191 		/* the list isn't empty, which means there is a thread taking
192 		 * care of that, thus just add it to the queue */
193 		todo = g_slist_append (todo, td);
194 	} else {
195 		GError *error = NULL;
196 		EBookClient *client = bbdb_create_book_client (AUTOMATIC_CONTACTS_ADDRESSBOOK);
197 
198 		/* list was empty, add item and create a thread */
199 		todo = g_slist_append (todo, td);
200 		g_thread_create (bbdb_do_in_thread, client, FALSE, &error);
201 
202 		if (error) {
203 			g_warning ("%s: Creation of the thread failed with error: %s", G_STRFUNC, error->message);
204 			g_error_free (error);
205 
206 			G_UNLOCK (todo);
207 			bbdb_do_in_thread (client);
208 			G_LOCK (todo);
209 		}
210 	}
211 	G_UNLOCK (todo);
212 }
213 
214 static void
215 walk_destinations_and_free (EDestination **dests)
216 {
217 	const gchar *name, *addr;
218 	gint i;
219 
220 	if (!dests)
221 		return;
222 
223 	for (i = 0; dests[i] != NULL; i++) {
224 		if (e_destination_is_evolution_list (dests[i])) {
225 			const GList *members;
226 
227 			for (members = e_destination_list_get_dests (dests[i]); members; members = members->next) {
228 				const EDestination *member = members->data;
229 
230 				if (!member)
231 					continue;
232 
233 				name = e_destination_get_name (member);
234 				addr = e_destination_get_email (member);
235 
236 				if (name || addr)
237 					bbdb_do_thread (name, addr);
238 			}
239 		} else {
240 			name = e_destination_get_name (dests[i]);
241 			addr = e_destination_get_email (dests[i]);
242 
243 			if (name || addr)
244 				bbdb_do_thread (name, addr);
245 		}
246 	}
247 
248 	e_destination_freev (dests);
249 }
250 
251 void
252 bbdb_handle_send (EPlugin *ep,
253                   EMEventTargetComposer *target)
254 {
255 	EComposerHeaderTable *table;
256 	GSettings *settings;
257 	gboolean enable;
258 
259 	settings = g_settings_new (CONF_SCHEMA);
260 	enable = g_settings_get_boolean (settings, CONF_KEY_ENABLE);
261 	g_object_unref (settings);
262 
263 	if (!enable)
264 		return;
265 
266 	table = e_msg_composer_get_header_table (target->composer);
267 	g_return_if_fail (table);
268 
269 	/* read information from the composer, not from a generated message */
270 	walk_destinations_and_free (e_composer_header_table_get_destinations_to (table));
271 	walk_destinations_and_free (e_composer_header_table_get_destinations_cc (table));
272 }
273 
274 static void
275 bbdb_do_it (EBookClient *client,
276             const gchar *name,
277             const gchar *email)
278 {
279 	gchar *query_string, *delim, *temp_name = NULL, *uid;
280 	GSList *contacts = NULL;
281 	gboolean status;
282 	EContact *contact;
283 	GError *error = NULL;
284 
285 	g_return_if_fail (client != NULL);
286 
287 	if (email == NULL || !strcmp (email, ""))
288 		return;
289 
290 	if ((delim = strchr (email, '@')) == NULL)
291 		return;
292 
293 	/* don't miss the entry if the mail has only e-mail id and no name */
294 	if (name == NULL || !strcmp (name, "")) {
295 		temp_name = g_strndup (email, delim - email);
296 		name = temp_name;
297 	}
298 
299 	/* If any contacts exists with this email address, don't do anything */
300 	query_string = g_strdup_printf ("(contains \"email\" \"%s\")", email);
301 	status = e_book_client_get_contacts_sync (client, query_string, &contacts, NULL, NULL);
302 	g_free (query_string);
303 	if (contacts != NULL || !status) {
304 		e_client_util_free_object_slist (contacts);
305 		g_free (temp_name);
306 
307 		return;
308 	}
309 
310 	if (g_utf8_strchr (name, -1, '\"')) {
311 		GString *tmp = g_string_new (name);
312 		gchar *p;
313 
314 		while (p = g_utf8_strchr (tmp->str, tmp->len, '\"'), p)
315 			tmp = g_string_erase (tmp, p - tmp->str, 1);
316 
317 		g_free (temp_name);
318 		temp_name = g_string_free (tmp, FALSE);
319 		name = temp_name;
320 	}
321 
322 	contacts = NULL;
323 	/* If a contact exists with this name, add the email address to it. */
324 	query_string = g_strdup_printf ("(is \"full_name\" \"%s\")", name);
325 	status = e_book_client_get_contacts_sync (client, query_string, &contacts, NULL, NULL);
326 	g_free (query_string);
327 	if (contacts != NULL || !status) {
328 		/* FIXME: If there's more than one contact with this
329 		 * name, just give up; we're not smart enough for
330 		 * this. */
331 		if (!status || contacts->next != NULL) {
332 			e_client_util_free_object_slist (contacts);
333 			g_free (temp_name);
334 			return;
335 		}
336 
337 		contact = (EContact *) contacts->data;
338 		add_email_to_contact (contact, email);
339 		if (!e_book_client_modify_contact_sync (client, contact, NULL, &error)) {
340 			g_warning ("bbdb: Could not modify contact: %s\n", error->message);
341 			g_error_free (error);
342 		}
343 
344 		e_client_util_free_object_slist (contacts);
345 		g_free (temp_name);
346 		return;
347 	}
348 
349 	/* Otherwise, create a new contact. */
350 	contact = e_contact_new ();
351 	e_contact_set (contact, E_CONTACT_FULL_NAME, (gpointer) name);
352 	add_email_to_contact (contact, email);
353 	g_free (temp_name);
354 
355 	uid = NULL;
356 	if (!e_book_client_add_contact_sync (client, contact, &uid, NULL, &error)) {
357 		g_warning ("bbdb: Failed to add new contact: %s", error->message);
358 		g_error_free (error);
359 	}
360 
361 	g_object_unref (contact);
362 	g_free (uid);
363 }
364 
365 EBookClient *
366 bbdb_create_book_client (gint type)
367 {
368 	EShell *shell;
369 	ESource *source = NULL;
370 	ESourceRegistry *registry;
371 	EBookClient *client = NULL;
372 	GSettings *settings;
373 	gboolean enable = TRUE;
374 	gchar *uid;
375 	GError *error = NULL;
376 
377 	settings = g_settings_new (CONF_SCHEMA);
378 
379 	/* Check to see if we're supposed to be running */
380 	if (type == AUTOMATIC_CONTACTS_ADDRESSBOOK)
381 		enable = g_settings_get_boolean (settings, CONF_KEY_ENABLE);
382 	if (!enable) {
383 		g_object_unref (settings);
384 		return NULL;
385 	}
386 
387 	/* Open the appropriate addresbook. */
388 	if (type == GAIM_ADDRESSBOOK)
389 		uid = g_settings_get_string (
390 			settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM);
391 	else
392 		uid = g_settings_get_string (
393 			settings, CONF_KEY_WHICH_ADDRESSBOOK);
394 	g_object_unref (settings);
395 
396 	shell = e_shell_get_default ();
397 	registry = e_shell_get_registry (shell);
398 
399 	if (uid != NULL) {
400 		source = e_source_registry_ref_source (registry, uid);
401 		g_free (uid);
402 	}
403 
404 	if (source == NULL)
405 		source = e_source_registry_ref_builtin_address_book (registry);
406 
407 	client = e_book_client_new (source, &error);
408 	if (client == NULL) {
409 		g_warning (
410 			"bbdb: Failed to get addressbook: %s\n",
411 			error->message);
412 		g_error_free (error);
413 	}
414 
415 	g_object_unref (source);
416 
417 	return client;
418 }
419 
420 gboolean
421 bbdb_open_book_client (EBookClient *client)
422 {
423 	GError *error = NULL;
424 
425 	if (!client)
426 		return FALSE;
427 
428 	e_client_open_sync (E_CLIENT (client), FALSE, NULL, &error);
429 
430 	if (error != NULL) {
431 		g_warning (
432 			"bbdb: failed to open addressbook: %s",
433 			error->message);
434 		g_object_unref (client);
435 		g_error_free (error);
436 		return FALSE;
437 	}
438 
439 	return TRUE;
440 }
441 
442 gboolean
443 bbdb_check_gaim_enabled (void)
444 {
445 	GSettings *settings;
446 	gboolean   gaim_enabled;
447 
448 	settings = g_settings_new (CONF_SCHEMA);
449 	gaim_enabled = g_settings_get_boolean (settings, CONF_KEY_ENABLE_GAIM);
450 
451 	g_object_unref (settings);
452 
453 	return gaim_enabled;
454 }
455 
456 static void
457 add_email_to_contact (EContact *contact,
458                       const gchar *email)
459 {
460 	GList *emails;
461 
462 	emails = e_contact_get (contact, E_CONTACT_EMAIL);
463 	emails = g_list_append (emails, (gpointer) email);
464 	e_contact_set (contact, E_CONTACT_EMAIL, (gpointer) emails);
465 }
466 
467 /* Code to implement the configuration user interface follows */
468 
469 static void
470 enable_toggled_cb (GtkWidget *widget,
471                    gpointer data)
472 {
473 	struct bbdb_stuff *stuff = (struct bbdb_stuff *) data;
474 	gboolean active;
475 	ESource *selected_source;
476 	gchar *addressbook;
477 	GSettings *settings = g_settings_new (CONF_SCHEMA);
478 
479 	active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
480 
481 	/* Save the new setting to GSettings */
482 	g_settings_set_boolean (settings, CONF_KEY_ENABLE, active);
483 
484 	gtk_widget_set_sensitive (stuff->combo_box, active);
485 
486 	addressbook = g_settings_get_string (settings, CONF_KEY_WHICH_ADDRESSBOOK);
487 
488 	if (active && !addressbook) {
489 		selected_source = e_source_combo_box_ref_active (
490 			E_SOURCE_COMBO_BOX (stuff->combo_box));
491 		if (selected_source != NULL) {
492 			g_settings_set_string (
493 				settings, CONF_KEY_WHICH_ADDRESSBOOK,
494 				e_source_get_uid (selected_source));
495 			g_object_unref (selected_source);
496 		} else {
497 			g_settings_set_string (
498 				settings, CONF_KEY_WHICH_ADDRESSBOOK, "");
499 		}
500 	}
501 
502 	g_free (addressbook);
503 	g_object_unref (settings);
504 }
505 
506 static void
507 enable_gaim_toggled_cb (GtkWidget *widget,
508                         gpointer data)
509 {
510 	struct bbdb_stuff *stuff = (struct bbdb_stuff *) data;
511 	gboolean active;
512 	ESource *selected_source;
513 	gchar *addressbook_gaim;
514 	GSettings *settings = g_settings_new (CONF_SCHEMA);
515 
516 	active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
517 
518 	/* Save the new setting to GSettings */
519 	g_settings_set_boolean (settings, CONF_KEY_ENABLE_GAIM, active);
520 
521 	addressbook_gaim = g_settings_get_string (
522 		settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM);
523 	gtk_widget_set_sensitive (stuff->gaim_combo_box, active);
524 	if (active && !addressbook_gaim) {
525 		selected_source = e_source_combo_box_ref_active (
526 			E_SOURCE_COMBO_BOX (stuff->gaim_combo_box));
527 		if (selected_source != NULL) {
528 			g_settings_set_string (
529 				settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM,
530 				e_source_get_uid (selected_source));
531 			g_object_unref (selected_source);
532 		} else {
533 			g_settings_set_string (
534 				settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM, "");
535 		}
536 	}
537 
538 	g_free (addressbook_gaim);
539 	g_object_unref (settings);
540 }
541 
542 static void
543 synchronize_button_clicked_cb (GtkWidget *button)
544 {
545 	bbdb_sync_buddy_list ();
546 }
547 
548 static void
549 source_changed_cb (ESourceComboBox *source_combo_box,
550                    struct bbdb_stuff *stuff)
551 {
552 	GSettings *settings;
553 	ESource *source;
554 	const gchar *uid;
555 
556 	source = e_source_combo_box_ref_active (source_combo_box);
557 	uid = (source != NULL) ? e_source_get_uid (source) : "";
558 
559 	settings = g_settings_new (CONF_SCHEMA);
560 	g_settings_set_string (settings, CONF_KEY_WHICH_ADDRESSBOOK, uid);
561 	g_object_unref (settings);
562 
563 	if (source != NULL)
564 		g_object_unref (source);
565 }
566 
567 static void
568 gaim_source_changed_cb (ESourceComboBox *source_combo_box,
569                         struct bbdb_stuff *stuff)
570 {
571 	GSettings *settings;
572 	ESource *source;
573 	const gchar *uid;
574 
575 	source = e_source_combo_box_ref_active (source_combo_box);
576 	uid = (source != NULL) ? e_source_get_uid (source) : "";
577 
578 	settings = g_settings_new (CONF_SCHEMA);
579 	g_settings_set_string (settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM, uid);
580 	g_object_unref (settings);
581 
582 	if (source != NULL)
583 		g_object_unref (source);
584 }
585 
586 static GtkWidget *
587 create_addressbook_combo_box (struct bbdb_stuff *stuff,
588                               gint type)
589 {
590 	EShell *shell;
591 	ESource *source;
592 	ESourceRegistry *registry;
593 	GtkWidget *combo_box;
594 	const gchar *extension_name;
595 	gchar *uid;
596 	GSettings *settings = g_settings_new (CONF_SCHEMA);
597 
598 	shell = e_shell_get_default ();
599 	registry = e_shell_get_registry (shell);
600 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
601 	combo_box = e_source_combo_box_new (registry, extension_name);
602 
603 	if (type == GAIM_ADDRESSBOOK)
604 		uid = g_settings_get_string (settings, CONF_KEY_WHICH_ADDRESSBOOK_GAIM);
605 	else
606 		uid = g_settings_get_string (settings, CONF_KEY_WHICH_ADDRESSBOOK);
607 	source = e_source_registry_ref_source (registry, uid);
608 	g_free (uid);
609 
610 	if (source != NULL) {
611 		e_source_combo_box_set_active (
612 			E_SOURCE_COMBO_BOX (combo_box), source);
613 		g_object_unref (source);
614 	}
615 
616 	gtk_widget_show (combo_box);
617 
618 	g_object_unref (settings);
619 
620 	return combo_box;
621 }
622 
623 GtkWidget *
624 bbdb_page_factory (EPlugin *ep,
625                    EConfigHookItemFactoryData *hook_data)
626 {
627 	struct bbdb_stuff *stuff;
628 	EABConfigTargetPrefs *target = (EABConfigTargetPrefs *) hook_data->config->target;
629 	GtkWidget *page;
630 	GtkWidget *tab_label;
631 	GtkWidget *frame;
632 	GtkWidget *frame_label;
633 	GtkWidget *padding_label;
634 	GtkWidget *hbox;
635 	GtkWidget *inner_vbox;
636 	GtkWidget *check;
637 	GtkWidget *combo_box;
638 	GtkWidget *gaim_combo_box;
639 	GtkWidget *check_gaim;
640 	GtkWidget *label;
641 	GtkWidget *gaim_label;
642 	GtkWidget *button;
643 	gchar *str;
644 	GSettings *settings = g_settings_new (CONF_SCHEMA);
645 
646 	/* A structure to pass some stuff around */
647 	stuff = g_new0 (struct bbdb_stuff, 1);
648 	stuff->target = target;
649 
650 	/* Create a new notebook page */
651 	page = gtk_vbox_new (FALSE, 0);
652 	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
653 	tab_label = gtk_label_new (_("Automatic Contacts"));
654 	gtk_notebook_append_page (GTK_NOTEBOOK (hook_data->parent), page, tab_label);
655 
656 	/* Frame */
657 	frame = gtk_vbox_new (FALSE, 6);
658 	gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0);
659 
660 	/* "Automatic Contacts" */
661 	frame_label = gtk_label_new ("");
662 	str = g_strdup_printf ("<span weight=\"bold\">%s</span>", _("Automatic Contacts"));
663 	gtk_label_set_markup (GTK_LABEL (frame_label), str);
664 	g_free (str);
665 	gtk_misc_set_alignment (GTK_MISC (frame_label), 0.0, 0.5);
666 	gtk_box_pack_start (GTK_BOX (frame), frame_label, FALSE, FALSE, 0);
667 
668 	/* Indent/padding */
669 	hbox = gtk_hbox_new (FALSE, 12);
670 	gtk_box_pack_start (GTK_BOX (frame), hbox, FALSE, TRUE, 0);
671 	padding_label = gtk_label_new ("");
672 	gtk_box_pack_start (GTK_BOX (hbox), padding_label, FALSE, FALSE, 0);
673 	inner_vbox = gtk_vbox_new (FALSE, 6);
674 	gtk_box_pack_start (GTK_BOX (hbox), inner_vbox, FALSE, FALSE, 0);
675 
676 	/* Enable BBDB checkbox */
677 	check = gtk_check_button_new_with_mnemonic (_("Create _address book entries when sending mails"));
678 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), g_settings_get_boolean (settings, CONF_KEY_ENABLE));
679 	g_signal_connect (
680 		check, "toggled",
681 		G_CALLBACK (enable_toggled_cb), stuff);
682 	gtk_box_pack_start (GTK_BOX (inner_vbox), check, FALSE, FALSE, 0);
683 	stuff->check = check;
684 
685 	label = gtk_label_new (_("Select Address book for Automatic Contacts"));
686 	gtk_box_pack_start (GTK_BOX (inner_vbox), label, FALSE, FALSE, 0);
687 
688 	/* Source selection combo box */
689 	combo_box = create_addressbook_combo_box (stuff, AUTOMATIC_CONTACTS_ADDRESSBOOK);
690 	g_signal_connect (
691 		combo_box, "changed",
692 		G_CALLBACK (source_changed_cb), stuff);
693 	gtk_widget_set_sensitive (combo_box, g_settings_get_boolean (settings, CONF_KEY_ENABLE));
694 	gtk_box_pack_start (GTK_BOX (inner_vbox), combo_box, FALSE, FALSE, 0);
695 	stuff->combo_box = combo_box;
696 
697 	/* "Instant Messaging Contacts" */
698 	frame = gtk_vbox_new (FALSE, 6);
699 	gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 24);
700 
701 	frame_label = gtk_label_new ("");
702 	str = g_strdup_printf ("<span weight=\"bold\">%s</span>", _("Instant Messaging Contacts"));
703 	gtk_label_set_markup (GTK_LABEL (frame_label), str);
704 	g_free (str);
705 	gtk_misc_set_alignment (GTK_MISC (frame_label), 0.0, 0.5);
706 	gtk_box_pack_start (GTK_BOX (frame), frame_label, FALSE, FALSE, 0);
707 
708 	/* Indent/padding */
709 	hbox = gtk_hbox_new (FALSE, 12);
710 	gtk_box_pack_start (GTK_BOX (frame), hbox, FALSE, TRUE, 0);
711 	padding_label = gtk_label_new ("");
712 	gtk_box_pack_start (GTK_BOX (hbox), padding_label, FALSE, FALSE, 0);
713 	inner_vbox = gtk_vbox_new (FALSE, 6);
714 	gtk_box_pack_start (GTK_BOX (hbox), inner_vbox, FALSE, FALSE, 0);
715 
716 	/* Enable Gaim Checkbox */
717 	check_gaim = gtk_check_button_new_with_mnemonic (_("_Synchronize contact info and images from Pidgin buddy list"));
718 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_gaim), g_settings_get_boolean (settings, CONF_KEY_ENABLE_GAIM));
719 	g_signal_connect (
720 		check_gaim, "toggled",
721 		G_CALLBACK (enable_gaim_toggled_cb), stuff);
722 	gtk_box_pack_start (GTK_BOX (inner_vbox), check_gaim, FALSE, FALSE, 0);
723 	stuff->check_gaim = check_gaim;
724 
725 	gaim_label = gtk_label_new (_("Select Address book for Pidgin buddy list"));
726 	gtk_box_pack_start (GTK_BOX (inner_vbox), gaim_label, FALSE, FALSE, 0);
727 
728 	/* Gaim Source Selection Combo Box */
729 	gaim_combo_box = create_addressbook_combo_box (stuff, GAIM_ADDRESSBOOK);
730 	g_signal_connect (
731 		gaim_combo_box, "changed",
732 		G_CALLBACK (gaim_source_changed_cb), stuff);
733 	gtk_widget_set_sensitive (gaim_combo_box, g_settings_get_boolean (settings, CONF_KEY_ENABLE_GAIM));
734 	gtk_box_pack_start (GTK_BOX (inner_vbox), gaim_combo_box, FALSE, FALSE, 0);
735 	stuff->gaim_combo_box = gaim_combo_box;
736 
737 	/* Synchronize now button. */
738 	button = gtk_button_new_with_mnemonic (_("Synchronize with _buddy list now"));
739 	g_signal_connect (
740 		button, "clicked",
741 		G_CALLBACK (synchronize_button_clicked_cb), stuff);
742 	gtk_box_pack_start (GTK_BOX (inner_vbox), button, FALSE, FALSE, 0);
743 
744 	/* Clean up */
745 	g_signal_connect (
746 		page, "destroy",
747 		G_CALLBACK (cleanup_cb), stuff);
748 
749 	gtk_widget_show_all (page);
750 
751 	g_object_unref (settings);
752 
753 	return page;
754 }
755 
756 static void
757 cleanup_cb (GObject *o,
758             gpointer data)
759 {
760 	struct bbdb_stuff *stuff = data;
761 
762 	g_free (stuff);
763 }