evolution-3.6.4/e-util/e-util.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  *		Chris Lahey <clahey@ximian.com>
  18  *
  19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  20  *
  21  */
  22 
  23 /**
  24  * SECTION: e-util
  25  * @include: e-util/e-util.h
  26  **/
  27 
  28 #ifdef HAVE_CONFIG_H
  29 #include <config.h>
  30 #endif
  31 
  32 #include <stdlib.h>
  33 #include <stdio.h>
  34 #include <errno.h>
  35 #include <unistd.h>
  36 #include <ctype.h>
  37 #include <math.h>
  38 #include <string.h>
  39 #include <locale.h>
  40 #include <time.h>
  41 #include <sys/stat.h>
  42 #include <fcntl.h>
  43 
  44 #include <gio/gio.h>
  45 #include <gtk/gtk.h>
  46 #include <glib/gi18n.h>
  47 #include <glib/gstdio.h>
  48 
  49 #ifdef G_OS_WIN32
  50 #include <windows.h>
  51 #endif
  52 
  53 #include <camel/camel.h>
  54 #include <libedataserver/libedataserver.h>
  55 
  56 #include "filter/e-filter-option.h"
  57 
  58 #include "e-util.h"
  59 #include "e-util-private.h"
  60 
  61 typedef struct _WindowData WindowData;
  62 
  63 struct _WindowData {
  64 	GtkWindow *window;
  65 	GSettings *settings;
  66 	ERestoreWindowFlags flags;
  67 	gint premax_width;
  68 	gint premax_height;
  69 	guint timeout_id;
  70 };
  71 
  72 static void
  73 window_data_free (WindowData *data)
  74 {
  75 	if (data->settings != NULL)
  76 		g_object_unref (data->settings);
  77 
  78 	if (data->timeout_id > 0)
  79 		g_source_remove (data->timeout_id);
  80 
  81 	g_slice_free (WindowData, data);
  82 }
  83 
  84 static gboolean
  85 window_update_settings (WindowData *data)
  86 {
  87 	GSettings *settings = data->settings;
  88 
  89 	if (data->flags & E_RESTORE_WINDOW_SIZE) {
  90 		GdkWindowState state;
  91 		GdkWindow *window;
  92 		gboolean maximized;
  93 
  94 		window = gtk_widget_get_window (GTK_WIDGET (data->window));
  95 		state = gdk_window_get_state (window);
  96 		maximized = ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0);
  97 
  98 		g_settings_set_boolean (settings, "maximized", maximized);
  99 
 100 		if (!maximized) {
 101 			gint width, height;
 102 
 103 			gtk_window_get_size (data->window, &width, &height);
 104 
 105 			g_settings_set_int (settings, "width", width);
 106 			g_settings_set_int (settings, "height", height);
 107 		}
 108 	}
 109 
 110 	if (data->flags & E_RESTORE_WINDOW_POSITION) {
 111 		gint x, y;
 112 
 113 		gtk_window_get_position (data->window, &x, &y);
 114 
 115 		g_settings_set_int (settings, "x", x);
 116 		g_settings_set_int (settings, "y", y);
 117 	}
 118 
 119 	data->timeout_id = 0;
 120 
 121 	return FALSE;
 122 }
 123 
 124 static void
 125 window_delayed_update_settings (WindowData *data)
 126 {
 127 	if (data->timeout_id > 0)
 128 		g_source_remove (data->timeout_id);
 129 
 130 	data->timeout_id = g_timeout_add_seconds (
 131 		1, (GSourceFunc) window_update_settings, data);
 132 }
 133 
 134 static gboolean
 135 window_configure_event_cb (GtkWindow *window,
 136                            GdkEventConfigure *event,
 137                            WindowData *data)
 138 {
 139 	window_delayed_update_settings (data);
 140 
 141 	return FALSE;
 142 }
 143 
 144 static gboolean
 145 window_state_event_cb (GtkWindow *window,
 146                        GdkEventWindowState *event,
 147                        WindowData *data)
 148 {
 149 	gboolean window_was_unmaximized;
 150 
 151 	if (data->timeout_id > 0)
 152 		g_source_remove (data->timeout_id);
 153 
 154 	window_was_unmaximized =
 155 		((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) &&
 156 		((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0);
 157 
 158 	if (window_was_unmaximized) {
 159 		gint width, height;
 160 
 161 		width = data->premax_width;
 162 		data->premax_width = 0;
 163 
 164 		height = data->premax_height;
 165 		data->premax_height = 0;
 166 
 167 		/* This only applies when the window is initially restored
 168 		 * as maximized and is then unmaximized.  GTK+ handles the
 169 		 * unmaximized window size thereafter. */
 170 		if (width > 0 && height > 0)
 171 			gtk_window_resize (window, width, height);
 172 	}
 173 
 174 	window_delayed_update_settings (data);
 175 
 176 	return FALSE;
 177 }
 178 
 179 static gboolean
 180 window_unmap_cb (GtkWindow *window,
 181                  WindowData *data)
 182 {
 183 	if (data->timeout_id > 0)
 184 		g_source_remove (data->timeout_id);
 185 
 186 	/* It's too late to record the window position.
 187 	 * gtk_window_get_position() will report (0, 0). */
 188 	data->flags &= ~E_RESTORE_WINDOW_POSITION;
 189 
 190 	window_update_settings (data);
 191 
 192 	return FALSE;
 193 }
 194 
 195 /**
 196  * e_get_accels_filename:
 197  *
 198  * Returns the name of the user data file containing custom keyboard
 199  * accelerator specifications.
 200  *
 201  * Returns: filename for accelerator specifications
 202  **/
 203 const gchar *
 204 e_get_accels_filename (void)
 205 {
 206 	static gchar *filename = NULL;
 207 
 208 	if (G_UNLIKELY (filename == NULL)) {
 209 		const gchar *config_dir = e_get_user_config_dir ();
 210 		filename = g_build_filename (config_dir, "accels", NULL);
 211 	}
 212 
 213 	return filename;
 214 }
 215 
 216 /**
 217  * e_show_uri:
 218  * @parent: a parent #GtkWindow or %NULL
 219  * @uri: the URI to show
 220  *
 221  * Launches the default application to show the given URI.  The URI must
 222  * be of a form understood by GIO.  If the URI cannot be shown, it presents
 223  * a dialog describing the error.  The dialog is set as transient to @parent
 224  * if @parent is non-%NULL.
 225  **/
 226 void
 227 e_show_uri (GtkWindow *parent,
 228             const gchar *uri)
 229 {
 230 	GtkWidget *dialog;
 231 	GdkScreen *screen = NULL;
 232 	GError *error = NULL;
 233 	guint32 timestamp;
 234 
 235 	g_return_if_fail (uri != NULL);
 236 
 237 	timestamp = gtk_get_current_event_time ();
 238 
 239 	if (parent != NULL)
 240 		screen = gtk_widget_get_screen (GTK_WIDGET (parent));
 241 
 242 	if (gtk_show_uri (screen, uri, timestamp, &error))
 243 		return;
 244 
 245 	dialog = gtk_message_dialog_new_with_markup (
 246 		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
 247 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
 248 		"<big><b>%s</b></big>",
 249 		_("Could not open the link."));
 250 
 251 	gtk_message_dialog_format_secondary_text (
 252 		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
 253 
 254 	gtk_dialog_run (GTK_DIALOG (dialog));
 255 
 256 	gtk_widget_destroy (dialog);
 257 	g_error_free (error);
 258 }
 259 
 260 /**
 261  * e_display_help:
 262  * @parent: a parent #GtkWindow or %NULL
 263  * @link_id: help section to present or %NULL
 264  *
 265  * Opens the user documentation to the section given by @link_id, or to the
 266  * table of contents if @link_id is %NULL.  If the user documentation cannot
 267  * be opened, it presents a dialog describing the error.  The dialog is set
 268  * as transient to @parent if @parent is non-%NULL.
 269  **/
 270 void
 271 e_display_help (GtkWindow *parent,
 272                 const gchar *link_id)
 273 {
 274 	GString *uri;
 275 	GtkWidget *dialog;
 276 	GdkScreen *screen = NULL;
 277 	GError *error = NULL;
 278 	guint32 timestamp;
 279 
 280 	uri = g_string_new ("help:" PACKAGE);
 281 	timestamp = gtk_get_current_event_time ();
 282 
 283 	if (parent != NULL)
 284 		screen = gtk_widget_get_screen (GTK_WIDGET (parent));
 285 
 286 	if (link_id != NULL)
 287 		g_string_append_printf (uri, "?%s", link_id);
 288 
 289 	if (gtk_show_uri (screen, uri->str, timestamp, &error))
 290 		goto exit;
 291 
 292 	dialog = gtk_message_dialog_new_with_markup (
 293 		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
 294 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
 295 		"<big><b>%s</b></big>",
 296 		_("Could not display help for Evolution."));
 297 
 298 	gtk_message_dialog_format_secondary_text (
 299 		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
 300 
 301 	gtk_dialog_run (GTK_DIALOG (dialog));
 302 
 303 	gtk_widget_destroy (dialog);
 304 	g_error_free (error);
 305 
 306 exit:
 307 	g_string_free (uri, TRUE);
 308 }
 309 
 310 /**
 311  * e_restore_window:
 312  * @window: a #GtkWindow
 313  * @settings_path: a #GSettings path
 314  * @flags: flags indicating which window features to restore
 315  *
 316  * This function can restore one of or both a window's size and position
 317  * using #GSettings keys at @settings_path which conform to the relocatable
 318  * schema "org.gnome.evolution.window".
 319  *
 320  * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's
 321  * previously recorded size and maximize state.
 322  *
 323  * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to
 324  * the previously recorded screen coordinates.
 325  *
 326  * The respective #GSettings values will be updated when the window is
 327  * resized and/or moved.
 328  **/
 329 void
 330 e_restore_window (GtkWindow *window,
 331                   const gchar *settings_path,
 332                   ERestoreWindowFlags flags)
 333 {
 334 	WindowData *data;
 335 	GSettings *settings;
 336 	const gchar *schema;
 337 
 338 	g_return_if_fail (GTK_IS_WINDOW (window));
 339 	g_return_if_fail (settings_path != NULL);
 340 
 341 	schema = "org.gnome.evolution.window";
 342 	settings = g_settings_new_with_path (schema, settings_path);
 343 
 344 	data = g_slice_new0 (WindowData);
 345 	data->window = window;
 346 	data->settings = g_object_ref (settings);
 347 	data->flags = flags;
 348 
 349 	if (flags & E_RESTORE_WINDOW_SIZE) {
 350 		gint width, height;
 351 
 352 		width = g_settings_get_int (settings, "width");
 353 		height = g_settings_get_int (settings, "height");
 354 
 355 		if (width > 0 && height > 0)
 356 			gtk_window_resize (window, width, height);
 357 
 358 		if (g_settings_get_boolean (settings, "maximized")) {
 359 			GdkScreen *screen;
 360 			GdkRectangle monitor_area;
 361 			gint x, y, monitor;
 362 
 363 			x = g_settings_get_int (settings, "x");
 364 			y = g_settings_get_int (settings, "y");
 365 
 366 			screen = gtk_window_get_screen (window);
 367 			gtk_window_get_size (window, &width, &height);
 368 
 369 			data->premax_width = width;
 370 			data->premax_height = height;
 371 
 372 			monitor = gdk_screen_get_monitor_at_point (screen, x, y);
 373 			if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen))
 374 				monitor = 0;
 375 
 376 			gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area);
 377 
 378 			gtk_window_resize (window, monitor_area.width, monitor_area.height);
 379 			gtk_window_maximize (window);
 380 		}
 381 	}
 382 
 383 	if (flags & E_RESTORE_WINDOW_POSITION) {
 384 		gint x, y;
 385 
 386 		x = g_settings_get_int (settings, "x");
 387 		y = g_settings_get_int (settings, "y");
 388 
 389 		gtk_window_move (window, x, y);
 390 	}
 391 
 392 	g_object_set_data_full (
 393 		G_OBJECT (window),
 394 		"e-util-window-data", data,
 395 		(GDestroyNotify) window_data_free);
 396 
 397 	g_signal_connect (
 398 		window, "configure-event",
 399 		G_CALLBACK (window_configure_event_cb), data);
 400 
 401 	g_signal_connect (
 402 		window, "window-state-event",
 403 		G_CALLBACK (window_state_event_cb), data);
 404 
 405 	g_signal_connect (
 406 		window, "unmap",
 407 		G_CALLBACK (window_unmap_cb), data);
 408 
 409 	g_object_unref (settings);
 410 }
 411 
 412 /**
 413  * e_lookup_action:
 414  * @ui_manager: a #GtkUIManager
 415  * @action_name: the name of an action
 416  *
 417  * Returns the first #GtkAction named @action_name by traversing the
 418  * list of action groups in @ui_manager.  If no such action exists, the
 419  * function emits a critical warning before returning %NULL, since this
 420  * probably indicates a programming error and most code is not prepared
 421  * to deal with lookup failures.
 422  *
 423  * Returns: the first #GtkAction named @action_name
 424  **/
 425 GtkAction *
 426 e_lookup_action (GtkUIManager *ui_manager,
 427                  const gchar *action_name)
 428 {
 429 	GtkAction *action = NULL;
 430 	GList *iter;
 431 
 432 	g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
 433 	g_return_val_if_fail (action_name != NULL, NULL);
 434 
 435 	iter = gtk_ui_manager_get_action_groups (ui_manager);
 436 
 437 	while (iter != NULL) {
 438 		GtkActionGroup *action_group = iter->data;
 439 
 440 		action = gtk_action_group_get_action (
 441 			action_group, action_name);
 442 		if (action != NULL)
 443 			return action;
 444 
 445 		iter = g_list_next (iter);
 446 	}
 447 
 448 	g_critical ("%s: action '%s' not found", G_STRFUNC, action_name);
 449 
 450 	return NULL;
 451 }
 452 
 453 /**
 454  * e_lookup_action_group:
 455  * @ui_manager: a #GtkUIManager
 456  * @group_name: the name of an action group
 457  *
 458  * Returns the #GtkActionGroup in @ui_manager named @group_name.  If no
 459  * such action group exists, the function emits a critical warnings before
 460  * returning %NULL, since this probably indicates a programming error and
 461  * most code is not prepared to deal with lookup failures.
 462  *
 463  * Returns: the #GtkActionGroup named @group_name
 464  **/
 465 GtkActionGroup *
 466 e_lookup_action_group (GtkUIManager *ui_manager,
 467                        const gchar *group_name)
 468 {
 469 	GList *iter;
 470 
 471 	g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
 472 	g_return_val_if_fail (group_name != NULL, NULL);
 473 
 474 	iter = gtk_ui_manager_get_action_groups (ui_manager);
 475 
 476 	while (iter != NULL) {
 477 		GtkActionGroup *action_group = iter->data;
 478 		const gchar *name;
 479 
 480 		name = gtk_action_group_get_name (action_group);
 481 		if (strcmp (name, group_name) == 0)
 482 			return action_group;
 483 
 484 		iter = g_list_next (iter);
 485 	}
 486 
 487 	g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name);
 488 
 489 	return NULL;
 490 }
 491 
 492 /**
 493  * e_action_compare_by_label:
 494  * @action1: a #GtkAction
 495  * @action2: a #GtkAction
 496  *
 497  * Compares the labels for @action1 and @action2 using g_utf8_collate().
 498  *
 499  * Returns: &lt; 0 if @action1 compares before @action2, 0 if they
 500  *          compare equal, &gt; 0 if @action1 compares after @action2
 501  **/
 502 gint
 503 e_action_compare_by_label (GtkAction *action1,
 504                            GtkAction *action2)
 505 {
 506 	gchar *label1;
 507 	gchar *label2;
 508 	gint result;
 509 
 510 	/* XXX This is horribly inefficient but will generally only be
 511 	 *     used on short lists of actions during UI construction. */
 512 
 513 	if (action1 == action2)
 514 		return 0;
 515 
 516 	g_object_get (action1, "label", &label1, NULL);
 517 	g_object_get (action2, "label", &label2, NULL);
 518 
 519 	result = g_utf8_collate (label1, label2);
 520 
 521 	g_free (label1);
 522 	g_free (label2);
 523 
 524 	return result;
 525 }
 526 
 527 /**
 528  * e_action_group_remove_all_actions:
 529  * @action_group: a #GtkActionGroup
 530  *
 531  * Removes all actions from the action group.
 532  **/
 533 void
 534 e_action_group_remove_all_actions (GtkActionGroup *action_group)
 535 {
 536 	GList *list, *iter;
 537 
 538 	/* XXX I've proposed this function for inclusion in GTK+.
 539 	 *     GtkActionGroup stores actions in an internal hash
 540 	 *     table and can do this more efficiently by calling
 541 	 *     g_hash_table_remove_all().
 542 	 *
 543 	 *     http://bugzilla.gnome.org/show_bug.cgi?id=550485 */
 544 
 545 	g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
 546 
 547 	list = gtk_action_group_list_actions (action_group);
 548 	for (iter = list; iter != NULL; iter = iter->next)
 549 		gtk_action_group_remove_action (action_group, iter->data);
 550 	g_list_free (list);
 551 }
 552 
 553 /**
 554  * e_radio_action_get_current_action:
 555  * @radio_action: a #GtkRadioAction
 556  *
 557  * Returns the currently active member of the group to which @radio_action
 558  * belongs.
 559  *
 560  * Returns: the currently active group member
 561  **/
 562 GtkRadioAction *
 563 e_radio_action_get_current_action (GtkRadioAction *radio_action)
 564 {
 565 	GSList *group;
 566 	gint current_value;
 567 
 568 	g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL);
 569 
 570 	group = gtk_radio_action_get_group (radio_action);
 571 	current_value = gtk_radio_action_get_current_value (radio_action);
 572 
 573 	while (group != NULL) {
 574 		gint value;
 575 
 576 		radio_action = GTK_RADIO_ACTION (group->data);
 577 		g_object_get (radio_action, "value", &value, NULL);
 578 
 579 		if (value == current_value)
 580 			return radio_action;
 581 
 582 		group = g_slist_next (group);
 583 	}
 584 
 585 	return NULL;
 586 }
 587 
 588 /**
 589  * e_action_group_add_actions_localized:
 590  * @action_group: a #GtkActionGroup to add @entries to
 591  * @translation_domain: a translation domain to use
 592  *    to translate label and tooltip strings in @entries
 593  * @entries: (array length=n_entries): an array of action descriptions
 594  * @n_entries: the number of entries
 595  * @user_data: data to pass to the action callbacks
 596  *
 597  * Adds #GtkAction-s defined by @entries to @action_group, with action's
 598  * label and tooltip localized in the given translation domain, instead
 599  * of the domain set on the @action_group.
 600  *
 601  * Since: 3.4
 602  **/
 603 void
 604 e_action_group_add_actions_localized (GtkActionGroup *action_group,
 605                                       const gchar *translation_domain,
 606                                       const GtkActionEntry *entries,
 607                                       guint n_entries,
 608                                       gpointer user_data)
 609 {
 610 	GtkActionGroup *tmp_group;
 611 	GList *list, *iter;
 612 	gint ii;
 613 
 614 	g_return_if_fail (action_group != NULL);
 615 	g_return_if_fail (entries != NULL);
 616 	g_return_if_fail (n_entries > 0);
 617 	g_return_if_fail (translation_domain != NULL);
 618 	g_return_if_fail (*translation_domain);
 619 
 620 	tmp_group = gtk_action_group_new ("temporary-group");
 621 	gtk_action_group_set_translation_domain (tmp_group, translation_domain);
 622 	gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data);
 623 
 624 	list = gtk_action_group_list_actions (tmp_group);
 625 	for (iter = list; iter != NULL; iter = iter->next) {
 626 		GtkAction *action = GTK_ACTION (iter->data);
 627 		const gchar *action_name;
 628 
 629 		g_object_ref (action);
 630 
 631 		action_name = gtk_action_get_name (action);
 632 
 633 		for (ii = 0; ii < n_entries; ii++) {
 634 			if (g_strcmp0 (entries[ii].name, action_name) == 0) {
 635 				gtk_action_group_remove_action (
 636 					tmp_group, action);
 637 				gtk_action_group_add_action_with_accel (
 638 					action_group, action,
 639 					entries[ii].accelerator);
 640 				break;
 641 			}
 642 		}
 643 
 644 		g_object_unref (action);
 645 	}
 646 
 647 	g_list_free (list);
 648 	g_object_unref (tmp_group);
 649 }
 650 
 651 /* Helper for e_categories_add_change_hook() */
 652 static void
 653 categories_changed_cb (GObject *useless_opaque_object,
 654                        GHookList *hook_list)
 655 {
 656 	/* e_categories_register_change_listener() is broken because
 657 	 * it requires callbacks to allow for some opaque GObject as
 658 	 * the first argument (not does it document this). */
 659 	g_hook_list_invoke (hook_list, FALSE);
 660 }
 661 
 662 /* Helper for e_categories_add_change_hook() */
 663 static void
 664 categories_weak_notify_cb (GHookList *hook_list,
 665                            gpointer where_the_object_was)
 666 {
 667 	GHook *hook;
 668 
 669 	/* This should not happen, but if we fail to find the hook for
 670 	 * some reason, g_hook_destroy_link() will warn about the NULL
 671 	 * pointer, which is all we would do anyway so no need to test
 672 	 * for it ourselves. */
 673 	hook = g_hook_find_data (hook_list, TRUE, where_the_object_was);
 674 	g_hook_destroy_link (hook_list, hook);
 675 }
 676 
 677 /**
 678  * e_categories_add_change_hook:
 679  * @func: a hook function
 680  * @object: a #GObject to be passed to @func, or %NULL
 681  *
 682  * A saner alternative to e_categories_register_change_listener().
 683  *
 684  * Adds a hook function to be called when a category is added, removed or
 685  * modified.  If @object is not %NULL, the hook function is automatically
 686  * removed when @object is finalized.
 687  **/
 688 void
 689 e_categories_add_change_hook (GHookFunc func,
 690                               gpointer object)
 691 {
 692 	static gboolean initialized = FALSE;
 693 	static GHookList hook_list;
 694 	GHook *hook;
 695 
 696 	g_return_if_fail (func != NULL);
 697 
 698 	if (object != NULL)
 699 		g_return_if_fail (G_IS_OBJECT (object));
 700 
 701 	if (!initialized) {
 702 		g_hook_list_init (&hook_list, sizeof (GHook));
 703 		e_categories_register_change_listener (
 704 			G_CALLBACK (categories_changed_cb), &hook_list);
 705 		initialized = TRUE;
 706 	}
 707 
 708 	hook = g_hook_alloc (&hook_list);
 709 
 710 	hook->func = func;
 711 	hook->data = object;
 712 
 713 	if (object != NULL)
 714 		g_object_weak_ref (
 715 			G_OBJECT (object), (GWeakNotify)
 716 			categories_weak_notify_cb, &hook_list);
 717 
 718 	g_hook_append (&hook_list, hook);
 719 }
 720 
 721 /**
 722  * e_str_without_underscores:
 723  * @string: the string to strip underscores from
 724  *
 725  * Strips underscores from a string in the same way
 726  * @gtk_label_new_with_mnemonics does.  The returned string should be freed
 727  * using g_free().
 728  *
 729  * Returns: a newly-allocated string without underscores
 730  */
 731 gchar *
 732 e_str_without_underscores (const gchar *string)
 733 {
 734 	gchar *new_string;
 735 	const gchar *sp;
 736 	gchar *dp;
 737 
 738 	new_string = g_malloc (strlen (string) + 1);
 739 
 740 	dp = new_string;
 741 	for (sp = string; *sp != '\0'; sp++) {
 742 		if (*sp != '_') {
 743 			*dp = *sp;
 744 			dp++;
 745 		} else if (sp[1] == '_') {
 746 			/* Translate "__" in "_".  */
 747 			*dp = '_';
 748 			dp++;
 749 			sp++;
 750 		}
 751 	}
 752 	*dp = 0;
 753 
 754 	return new_string;
 755 }
 756 
 757 gint
 758 e_str_compare (gconstpointer x,
 759                gconstpointer y)
 760 {
 761 	if (x == NULL || y == NULL) {
 762 		if (x == y)
 763 			return 0;
 764 		else
 765 			return x ? -1 : 1;
 766 	}
 767 
 768 	return strcmp (x, y);
 769 }
 770 
 771 gint
 772 e_str_case_compare (gconstpointer x,
 773                     gconstpointer y)
 774 {
 775 	gchar *cx, *cy;
 776 	gint res;
 777 
 778 	if (x == NULL || y == NULL) {
 779 		if (x == y)
 780 			return 0;
 781 		else
 782 			return x ? -1 : 1;
 783 	}
 784 
 785 	cx = g_utf8_casefold (x, -1);
 786 	cy = g_utf8_casefold (y, -1);
 787 
 788 	res = g_utf8_collate (cx, cy);
 789 
 790 	g_free (cx);
 791 	g_free (cy);
 792 
 793 	return res;
 794 }
 795 
 796 gint
 797 e_collate_compare (gconstpointer x,
 798                    gconstpointer y)
 799 {
 800 	if (x == NULL || y == NULL) {
 801 		if (x == y)
 802 			return 0;
 803 		else
 804 			return x ? -1 : 1;
 805 	}
 806 
 807 	return g_utf8_collate (x, y);
 808 }
 809 
 810 gint
 811 e_int_compare (gconstpointer x,
 812                gconstpointer y)
 813 {
 814 	gint nx = GPOINTER_TO_INT (x);
 815 	gint ny = GPOINTER_TO_INT (y);
 816 
 817 	return (nx == ny) ? 0 : (nx < ny) ? -1 : 1;
 818 }
 819 
 820 /**
 821  * e_color_to_value:
 822  * @color: a #GdkColor
 823  *
 824  * Converts a #GdkColor to a 24-bit RGB color value.
 825  *
 826  * Returns: a 24-bit color value
 827  **/
 828 guint32
 829 e_color_to_value (GdkColor *color)
 830 {
 831 	GdkRGBA rgba;
 832 
 833 	g_return_val_if_fail (color != NULL, 0);
 834 
 835 	rgba.red = color->red / 65535.0;
 836 	rgba.green = color->green / 65535.0;
 837 	rgba.blue = color->blue / 65535.0;
 838 	rgba.alpha = 0.0;
 839 
 840 	return e_rgba_to_value (&rgba);
 841 }
 842 
 843 /**
 844  * e_rgba_to_value:
 845  * @rgba: a #GdkRGBA
 846  *
 847  *
 848  * Converts #GdkRGBA to a 24-bit RGB color value
 849  *
 850  * Returns: a 24-bit color value
 851  **/
 852 guint32
 853 e_rgba_to_value (GdkRGBA *rgba)
 854 {
 855 	guint16 red;
 856 	guint16 green;
 857 	guint16 blue;
 858 
 859 	g_return_val_if_fail (rgba != NULL, 0);
 860 
 861 	red = 255 * rgba->red;
 862 	green = 255 * rgba->green;
 863 	blue = 255 * rgba->blue;
 864 
 865 	return (guint32)
 866 		((((red & 0xFF) << 16) |
 867 		((green & 0xFF) << 8) |
 868 		(blue & 0xFF)) & 0xffffff);
 869 }
 870 
 871 static gint
 872 epow10 (gint number)
 873 {
 874 	gint value = 1;
 875 
 876 	while (number-- > 0)
 877 		value *= 10;
 878 
 879 	return value;
 880 }
 881 
 882 gchar *
 883 e_format_number (gint number)
 884 {
 885 	GList *iterator, *list = NULL;
 886 	struct lconv *locality;
 887 	gint char_length = 0;
 888 	gint group_count = 0;
 889 	gchar *grouping;
 890 	gint last_count = 3;
 891 	gint divider;
 892 	gchar *value;
 893 	gchar *value_iterator;
 894 
 895 	locality = localeconv ();
 896 	grouping = locality->grouping;
 897 	while (number) {
 898 		gchar *group;
 899 		switch (*grouping) {
 900 		default:
 901 			last_count = *grouping;
 902 			grouping++;
 903 		case 0:
 904 			divider = epow10 (last_count);
 905 			if (number >= divider) {
 906 				group = g_strdup_printf (
 907 					"%0*d", last_count, number % divider);
 908 			} else {
 909 				group = g_strdup_printf (
 910 					"%d", number % divider);
 911 			}
 912 			number /= divider;
 913 			break;
 914 		case CHAR_MAX:
 915 			group = g_strdup_printf ("%d", number);
 916 			number = 0;
 917 			break;
 918 		}
 919 		char_length += strlen (group);
 920 		list = g_list_prepend (list, group);
 921 		group_count++;
 922 	}
 923 
 924 	if (list) {
 925 		value = g_new (
 926 			gchar, 1 + char_length + (group_count - 1) *
 927 			strlen (locality->thousands_sep));
 928 
 929 		iterator = list;
 930 		value_iterator = value;
 931 
 932 		strcpy (value_iterator, iterator->data);
 933 		value_iterator += strlen (iterator->data);
 934 		for (iterator = iterator->next; iterator; iterator = iterator->next) {
 935 			strcpy (value_iterator, locality->thousands_sep);
 936 			value_iterator += strlen (locality->thousands_sep);
 937 
 938 			strcpy (value_iterator, iterator->data);
 939 			value_iterator += strlen (iterator->data);
 940 		}
 941 		g_list_foreach (list, (GFunc) g_free, NULL);
 942 		g_list_free (list);
 943 		return value;
 944 	} else {
 945 		return g_strdup ("0");
 946 	}
 947 }
 948 
 949 /* Perform a binary search for key in base which has nmemb elements
 950  * of size bytes each.  The comparisons are done by (*compare)().  */
 951 void
 952 e_bsearch (gconstpointer key,
 953            gconstpointer base,
 954            gsize nmemb,
 955            gsize size,
 956            ESortCompareFunc compare,
 957            gpointer closure,
 958            gsize *start,
 959            gsize *end)
 960 {
 961 	gsize l, u, idx;
 962 	gconstpointer p;
 963 	gint comparison;
 964 	if (!(start || end))
 965 		return;
 966 
 967 	l = 0;
 968 	u = nmemb;
 969 	while (l < u) {
 970 		idx = (l + u) / 2;
 971 		p = (((const gchar *) base) + (idx * size));
 972 		comparison = (*compare) (key, p, closure);
 973 		if (comparison < 0)
 974 			u = idx;
 975 		else if (comparison > 0)
 976 			l = idx + 1;
 977 		else {
 978 			gsize lsave, usave;
 979 			lsave = l;
 980 			usave = u;
 981 			if (start) {
 982 				while (l < u) {
 983 					idx = (l + u) / 2;
 984 					p = (((const gchar *) base) + (idx * size));
 985 					comparison = (*compare) (key, p, closure);
 986 					if (comparison <= 0)
 987 						u = idx;
 988 					else
 989 						l = idx + 1;
 990 				}
 991 				*start = l;
 992 
 993 				l = lsave;
 994 				u = usave;
 995 			}
 996 			if (end) {
 997 				while (l < u) {
 998 					idx = (l + u) / 2;
 999 					p = (((const gchar *) base) + (idx * size));
1000 					comparison = (*compare) (key, p, closure);
1001 					if (comparison < 0)
1002 						u = idx;
1003 					else
1004 						l = idx + 1;
1005 				}
1006 				*end = l;
1007 			}
1008 			return;
1009 		}
1010 	}
1011 
1012 	if (start)
1013 		*start = l;
1014 	if (end)
1015 		*end = l;
1016 }
1017 
1018 /* Function to do a last minute fixup of the AM/PM stuff if the locale
1019  * and gettext haven't done it right. Most English speaking countries
1020  * except the USA use the 24 hour clock (UK, Australia etc). However
1021  * since they are English nobody bothers to write a language
1022  * translation (gettext) file. So the locale turns off the AM/PM, but
1023  * gettext does not turn on the 24 hour clock. Leaving a mess.
1024  *
1025  * This routine checks if AM/PM are defined in the locale, if not it
1026  * forces the use of the 24 hour clock.
1027  *
1028  * The function itself is a front end on strftime and takes exactly
1029  * the same arguments.
1030  *
1031  * TODO: Actually remove the '%p' from the fixed up string so that
1032  * there isn't a stray space.
1033  */
1034 
1035 gsize
1036 e_strftime_fix_am_pm (gchar *str,
1037                       gsize max,
1038                       const gchar *fmt,
1039                       const struct tm *tm)
1040 {
1041 	gchar buf[10];
1042 	gchar *sp;
1043 	gchar *ffmt;
1044 	gsize ret;
1045 
1046 	if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) {
1047 		/* No AM/PM involved - can use the fmt string directly */
1048 		ret = e_strftime (str, max, fmt, tm);
1049 	} else {
1050 		/* Get the AM/PM symbol from the locale */
1051 		e_strftime (buf, 10, "%p", tm);
1052 
1053 		if (buf[0]) {
1054 			/* AM/PM have been defined in the locale
1055 			 * so we can use the fmt string directly. */
1056 			ret = e_strftime (str, max, fmt, tm);
1057 		} else {
1058 			/* No AM/PM defined by locale
1059 			 * must change to 24 hour clock. */
1060 			ffmt = g_strdup (fmt);
1061 			for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) {
1062 				/* Maybe this should be 'k', but I have never
1063 				 * seen a 24 clock actually use that format. */
1064 				sp[1]='H';
1065 			}
1066 			for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) {
1067 				sp[1]='H';
1068 			}
1069 			ret = e_strftime (str, max, ffmt, tm);
1070 			g_free (ffmt);
1071 		}
1072 	}
1073 
1074 	return (ret);
1075 }
1076 
1077 gsize
1078 e_utf8_strftime_fix_am_pm (gchar *str,
1079                            gsize max,
1080                            const gchar *fmt,
1081                            const struct tm *tm)
1082 {
1083 	gsize sz, ret;
1084 	gchar *locale_fmt, *buf;
1085 
1086 	locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL);
1087 	if (!locale_fmt)
1088 		return 0;
1089 
1090 	ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm);
1091 	if (!ret) {
1092 		g_free (locale_fmt);
1093 		return 0;
1094 	}
1095 
1096 	buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL);
1097 	if (!buf) {
1098 		g_free (locale_fmt);
1099 		return 0;
1100 	}
1101 
1102 	if (sz >= max) {
1103 		gchar *tmp = buf + max - 1;
1104 		tmp = g_utf8_find_prev_char (buf, tmp);
1105 		if (tmp)
1106 			sz = tmp - buf;
1107 		else
1108 			sz = 0;
1109 	}
1110 	memcpy (str, buf, sz);
1111 	str[sz] = '\0';
1112 	g_free (locale_fmt);
1113 	g_free (buf);
1114 	return sz;
1115 }
1116 
1117 /**
1118  * e_get_month_name:
1119  * @month: month index
1120  * @abbreviated: if %TRUE, abbreviate the month name
1121  *
1122  * Returns the localized name for @month.  If @abbreviated is %TRUE,
1123  * returns the locale's abbreviated month name.
1124  *
1125  * Returns: localized month name
1126  **/
1127 const gchar *
1128 e_get_month_name (GDateMonth month,
1129                   gboolean abbreviated)
1130 {
1131 	/* Make the indices correspond to the enum values. */
1132 	static const gchar *abbr_names[G_DATE_DECEMBER + 1];
1133 	static const gchar *full_names[G_DATE_DECEMBER + 1];
1134 	static gboolean first_time = TRUE;
1135 
1136 	g_return_val_if_fail (month >= G_DATE_JANUARY, NULL);
1137 	g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL);
1138 
1139 	if (G_UNLIKELY (first_time)) {
1140 		gchar buffer[256];
1141 		GDateMonth ii;
1142 		GDate date;
1143 
1144 		memset (abbr_names, 0, sizeof (abbr_names));
1145 		memset (full_names, 0, sizeof (full_names));
1146 
1147 		/* First Julian day was in January. */
1148 		g_date_set_julian (&date, 1);
1149 
1150 		for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) {
1151 			g_date_strftime (buffer, sizeof (buffer), "%b", &date);
1152 			abbr_names[ii] = g_intern_string (buffer);
1153 			g_date_strftime (buffer, sizeof (buffer), "%B", &date);
1154 			full_names[ii] = g_intern_string (buffer);
1155 			g_date_add_months (&date, 1);
1156 		}
1157 
1158 		first_time = FALSE;
1159 	}
1160 
1161 	return abbreviated ? abbr_names[month] : full_names[month];
1162 }
1163 
1164 /**
1165  * e_get_weekday_name:
1166  * @weekday: weekday index
1167  * @abbreviated: if %TRUE, abbreviate the weekday name
1168  *
1169  * Returns the localized name for @weekday.  If @abbreviated is %TRUE,
1170  * returns the locale's abbreviated weekday name.
1171  *
1172  * Returns: localized weekday name
1173  **/
1174 const gchar *
1175 e_get_weekday_name (GDateWeekday weekday,
1176                     gboolean abbreviated)
1177 {
1178 	/* Make the indices correspond to the enum values. */
1179 	static const gchar *abbr_names[G_DATE_SUNDAY + 1];
1180 	static const gchar *full_names[G_DATE_SUNDAY + 1];
1181 	static gboolean first_time = TRUE;
1182 
1183 	g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL);
1184 	g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL);
1185 
1186 	if (G_UNLIKELY (first_time)) {
1187 		gchar buffer[256];
1188 		GDateWeekday ii;
1189 		GDate date;
1190 
1191 		memset (abbr_names, 0, sizeof (abbr_names));
1192 		memset (full_names, 0, sizeof (full_names));
1193 
1194 		/* First Julian day was a Monday. */
1195 		g_date_set_julian (&date, 1);
1196 
1197 		for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) {
1198 			g_date_strftime (buffer, sizeof (buffer), "%a", &date);
1199 			abbr_names[ii] = g_intern_string (buffer);
1200 			g_date_strftime (buffer, sizeof (buffer), "%A", &date);
1201 			full_names[ii] = g_intern_string (buffer);
1202 			g_date_add_days (&date, 1);
1203 		}
1204 
1205 		first_time = FALSE;
1206 	}
1207 
1208 	return abbreviated ? abbr_names[weekday] : full_names[weekday];
1209 }
1210 
1211 /* Evolution Locks for crash recovery */
1212 static const gchar *
1213 get_lock_filename (void)
1214 {
1215 	static gchar *filename = NULL;
1216 
1217 	if (G_UNLIKELY (filename == NULL))
1218 		filename = g_build_filename (
1219 			e_get_user_config_dir (), ".running", NULL);
1220 
1221 	return filename;
1222 }
1223 
1224 gboolean
1225 e_file_lock_create (void)
1226 {
1227 	const gchar *filename = get_lock_filename ();
1228 	gboolean status = FALSE;
1229 	FILE *file;
1230 
1231 	file = g_fopen (filename, "w");
1232 	if (file != NULL) {
1233 		/* The lock file also serves as a PID file. */
1234 		g_fprintf (
1235 			file, "%" G_GINT64_FORMAT "\n",
1236 			(gint64) getpid ());
1237 		fclose (file);
1238 		status = TRUE;
1239 	} else {
1240 		const gchar *errmsg = g_strerror (errno);
1241 		g_warning ("Lock file creation failed: %s", errmsg);
1242 	}
1243 
1244 	return status;
1245 }
1246 
1247 void
1248 e_file_lock_destroy (void)
1249 {
1250 	const gchar *filename = get_lock_filename ();
1251 
1252 	if (g_unlink (filename) == -1) {
1253 		const gchar *errmsg = g_strerror (errno);
1254 		g_warning ("Lock file deletion failed: %s", errmsg);
1255 	}
1256 }
1257 
1258 gboolean
1259 e_file_lock_exists (void)
1260 {
1261 	const gchar *filename = get_lock_filename ();
1262 
1263 	return g_file_test (filename, G_FILE_TEST_EXISTS);
1264 }
1265 
1266 /**
1267  * e_util_guess_mime_type:
1268  * @filename: a local file name, or URI
1269  * @localfile: %TRUE to check the file content, FALSE to check only the name
1270  *
1271  * Tries to determine the MIME type for @filename.  Free the returned
1272  * string with g_free().
1273  *
1274  * Returns: the MIME type of @filename, or %NULL if the the MIME type could
1275  *          not be determined
1276  **/
1277 gchar *
1278 e_util_guess_mime_type (const gchar *filename,
1279                         gboolean localfile)
1280 {
1281 	gchar *mime_type = NULL;
1282 
1283 	g_return_val_if_fail (filename != NULL, NULL);
1284 
1285 	if (localfile) {
1286 		GFile *file;
1287 		GFileInfo *fi;
1288 
1289 		if (strstr (filename, "://"))
1290 			file = g_file_new_for_uri (filename);
1291 		else
1292 			file = g_file_new_for_path (filename);
1293 
1294 		fi = g_file_query_info (
1295 			file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
1296 			G_FILE_QUERY_INFO_NONE, NULL, NULL);
1297 		if (fi) {
1298 			mime_type = g_content_type_get_mime_type (
1299 				g_file_info_get_content_type (fi));
1300 			g_object_unref (fi);
1301 		}
1302 
1303 		g_object_unref (file);
1304 	}
1305 
1306 	if (!mime_type) {
1307 		/* file doesn't exists locally, thus guess based on the filename */
1308 		gboolean uncertain = FALSE;
1309 		gchar *content_type;
1310 
1311 		content_type = g_content_type_guess (filename, NULL, 0, &uncertain);
1312 		if (content_type) {
1313 			mime_type = g_content_type_get_mime_type (content_type);
1314 			g_free (content_type);
1315 		}
1316 	}
1317 
1318 	return mime_type;
1319 }
1320 
1321 /* XXX: Should e-util/ really depend on filter/ ?? */
1322 GSList *
1323 e_util_get_category_filter_options (void)
1324 {
1325 	GSList *res = NULL;
1326 	GList *clist, *l;
1327 
1328 	clist = e_categories_get_list ();
1329 	for (l = clist; l; l = l->next) {
1330 		const gchar *cname = l->data;
1331 		struct _filter_option *fo;
1332 
1333 		if (!e_categories_is_searchable (cname))
1334 			continue;
1335 
1336 		fo = g_new0 (struct _filter_option, 1);
1337 
1338 		fo->title = g_strdup (cname);
1339 		fo->value = g_strdup (cname);
1340 		res = g_slist_prepend (res, fo);
1341 	}
1342 
1343 	g_list_free (clist);
1344 
1345 	return g_slist_reverse (res);
1346 }
1347 
1348 /**
1349  * e_util_get_searchable_categories:
1350  *
1351  * Returns list of searchable categories only. The list should
1352  * be freed with g_list_free() when done with it, but the items
1353  * are internal strings, names of categories, which should not
1354  * be touched in other than read-only way, in other words the same
1355  * restrictions as for e_categories_get_list() applies here too.
1356  **/
1357 GList *
1358 e_util_get_searchable_categories (void)
1359 {
1360 	GList *res = NULL, *all_categories, *l;
1361 
1362 	all_categories = e_categories_get_list ();
1363 	for (l = all_categories; l; l = l->next) {
1364 		const gchar *cname = l->data;
1365 
1366 		if (e_categories_is_searchable (cname))
1367 			res = g_list_prepend (res, (gpointer) cname);
1368 	}
1369 
1370 	g_list_free (all_categories);
1371 
1372 	return g_list_reverse (res);
1373 }
1374 
1375 /**
1376  * e_binding_transform_color_to_string:
1377  * @binding: a #GBinding
1378  * @source_value: a #GValue of type #GDK_TYPE_COLOR
1379  * @target_value: a #GValue of type #G_TYPE_STRING
1380  * @not_used: not used
1381  *
1382  * Transforms a #GdkColor value to a color string specification.
1383  *
1384  * Returns: %TRUE always
1385  **/
1386 gboolean
1387 e_binding_transform_color_to_string (GBinding *binding,
1388                                      const GValue *source_value,
1389                                      GValue *target_value,
1390                                      gpointer not_used)
1391 {
1392 	const GdkColor *color;
1393 	gchar *string;
1394 
1395 	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
1396 
1397 	color = g_value_get_boxed (source_value);
1398 	if (!color) {
1399 		g_value_set_string (target_value, "");
1400 	} else {
1401 		/* encode color manually, because css styles expect colors in #rrggbb,
1402 		 * not in #rrrrggggbbbb, which is a result of gdk_color_to_string()
1403 		*/
1404 		string = g_strdup_printf (
1405 			"#%02x%02x%02x",
1406 			(gint) color->red * 256 / 65536,
1407 			(gint) color->green * 256 / 65536,
1408 			(gint) color->blue * 256 / 65536);
1409 		g_value_set_string (target_value, string);
1410 		g_free (string);
1411 	}
1412 
1413 	return TRUE;
1414 }
1415 
1416 /**
1417  * e_binding_transform_string_to_color:
1418  * @binding: a #GBinding
1419  * @source_value: a #GValue of type #G_TYPE_STRING
1420  * @target_value: a #GValue of type #GDK_TYPE_COLOR
1421  * @not_used: not used
1422  *
1423  * Transforms a color string specification to a #GdkColor.
1424  *
1425  * Returns: %TRUE if color string specification was valid
1426  **/
1427 gboolean
1428 e_binding_transform_string_to_color (GBinding *binding,
1429                                      const GValue *source_value,
1430                                      GValue *target_value,
1431                                      gpointer not_used)
1432 {
1433 	GdkColor color;
1434 	const gchar *string;
1435 	gboolean success = FALSE;
1436 
1437 	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
1438 
1439 	string = g_value_get_string (source_value);
1440 	if (gdk_color_parse (string, &color)) {
1441 		g_value_set_boxed (target_value, &color);
1442 		success = TRUE;
1443 	}
1444 
1445 	return success;
1446 }
1447 
1448 /**
1449  * e_binding_transform_source_to_uid:
1450  * @binding: a #GBinding
1451  * @source_value: a #GValue of type #E_TYPE_SOURCE
1452  * @target_value: a #GValue of type #G_TYPE_STRING
1453  * @registry: an #ESourceRegistry
1454  *
1455  * Transforms an #ESource object to its UID string.
1456  *
1457  * Returns: %TRUE if @source_value was an #ESource object
1458  **/
1459 gboolean
1460 e_binding_transform_source_to_uid (GBinding *binding,
1461                                    const GValue *source_value,
1462                                    GValue *target_value,
1463                                    ESourceRegistry *registry)
1464 {
1465 	ESource *source;
1466 	const gchar *string;
1467 	gboolean success = FALSE;
1468 
1469 	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
1470 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1471 
1472 	source = g_value_get_object (source_value);
1473 	if (E_IS_SOURCE (source)) {
1474 		string = e_source_get_uid (source);
1475 		g_value_set_string (target_value, string);
1476 		success = TRUE;
1477 	}
1478 
1479 	return success;
1480 }
1481 
1482 /**
1483  * e_binding_transform_uid_to_source:
1484  * @binding: a #GBinding
1485  * @source_value: a #GValue of type #G_TYPE_STRING
1486  * @target_value: a #GValue of type #E_TYPE_SOURCe
1487  * @registry: an #ESourceRegistry
1488  *
1489  * Transforms an #ESource UID string to the corresponding #ESource object
1490  * in @registry.
1491  *
1492  * Returns: %TRUE if @registry had an #ESource object with a matching
1493  *          UID string
1494  **/
1495 gboolean
1496 e_binding_transform_uid_to_source (GBinding *binding,
1497                                    const GValue *source_value,
1498                                    GValue *target_value,
1499                                    ESourceRegistry *registry)
1500 {
1501 	ESource *source;
1502 	const gchar *string;
1503 	gboolean success = FALSE;
1504 
1505 	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
1506 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1507 
1508 	string = g_value_get_string (source_value);
1509 	if (string == NULL || *string == '\0')
1510 		return FALSE;
1511 
1512 	source = e_source_registry_ref_source (registry, string);
1513 	if (source != NULL) {
1514 		g_value_take_object (target_value, source);
1515 		success = TRUE;
1516 	}
1517 
1518 	return success;
1519 }