Updated Spanish translation
[evolution.git] / e-util / e-misc-utils.c
blob919eaf89a37e49a75e3054ada5ecfd9a5c5b894d
1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 * Authors:
16 * Chris Lahey <clahey@ximian.com>
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
26 #include "e-misc-utils.h"
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <math.h>
34 #include <string.h>
35 #include <locale.h>
36 #include <time.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
40 #include <gio/gio.h>
41 #include <gtk/gtk.h>
42 #include <glib/gi18n.h>
43 #include <glib/gstdio.h>
45 #ifdef G_OS_WIN32
46 #include <windows.h>
47 #else
48 #include <gio/gdesktopappinfo.h>
49 #endif
51 #include <camel/camel.h>
52 #include <libedataserver/libedataserver.h>
54 #include "e-alert-dialog.h"
55 #include "e-alert-sink.h"
56 #include "e-client-cache.h"
57 #include "e-filter-option.h"
58 #include "e-util-private.h"
60 typedef struct _WindowData WindowData;
62 struct _WindowData {
63 GtkWindow *window;
64 GSettings *settings;
65 ERestoreWindowFlags flags;
66 gint premax_width;
67 gint premax_height;
68 guint timeout_id;
71 static void
72 window_data_free (WindowData *data)
74 if (data->settings != NULL)
75 g_object_unref (data->settings);
77 if (data->timeout_id > 0)
78 g_source_remove (data->timeout_id);
80 g_slice_free (WindowData, data);
83 static gboolean
84 window_update_settings (gpointer user_data)
86 WindowData *data = user_data;
87 GSettings *settings = data->settings;
89 if (data->flags & E_RESTORE_WINDOW_SIZE) {
90 GdkWindowState state;
91 GdkWindow *window;
92 gboolean maximized;
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);
98 g_settings_set_boolean (settings, "maximized", maximized);
100 if (!maximized) {
101 gint width, height;
103 gtk_window_get_size (data->window, &width, &height);
105 g_settings_set_int (settings, "width", width);
106 g_settings_set_int (settings, "height", height);
110 if (data->flags & E_RESTORE_WINDOW_POSITION) {
111 gint x, y;
113 gtk_window_get_position (data->window, &x, &y);
115 g_settings_set_int (settings, "x", x);
116 g_settings_set_int (settings, "y", y);
119 data->timeout_id = 0;
121 return FALSE;
124 static void
125 window_delayed_update_settings (WindowData *data)
127 if (data->timeout_id > 0)
128 g_source_remove (data->timeout_id);
130 data->timeout_id = e_named_timeout_add_seconds (
131 1, window_update_settings, data);
134 static gboolean
135 window_configure_event_cb (GtkWindow *window,
136 GdkEventConfigure *event,
137 WindowData *data)
139 window_delayed_update_settings (data);
141 return FALSE;
144 static gboolean
145 window_state_event_cb (GtkWindow *window,
146 GdkEventWindowState *event,
147 WindowData *data)
149 gboolean window_was_unmaximized;
151 if (data->timeout_id > 0) {
152 g_source_remove (data->timeout_id);
153 data->timeout_id = 0;
156 window_was_unmaximized =
157 ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) &&
158 ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0);
160 if (window_was_unmaximized) {
161 gint width, height;
163 width = data->premax_width;
164 data->premax_width = 0;
166 height = data->premax_height;
167 data->premax_height = 0;
169 /* This only applies when the window is initially restored
170 * as maximized and is then unmaximized. GTK+ handles the
171 * unmaximized window size thereafter. */
172 if (width > 0 && height > 0)
173 gtk_window_resize (window, width, height);
176 window_delayed_update_settings (data);
178 return FALSE;
181 static gboolean
182 window_unmap_cb (GtkWindow *window,
183 WindowData *data)
185 if (data->timeout_id > 0) {
186 g_source_remove (data->timeout_id);
187 data->timeout_id = 0;
190 /* Reset the flags so the window position and size are not
191 * accidentally reverted to their default value at the next run. */
192 data->flags = 0;
194 return FALSE;
198 * e_get_accels_filename:
200 * Returns the name of the user data file containing custom keyboard
201 * accelerator specifications.
203 * Returns: filename for accelerator specifications
205 const gchar *
206 e_get_accels_filename (void)
208 static gchar *filename = NULL;
210 if (G_UNLIKELY (filename == NULL)) {
211 const gchar *config_dir = e_get_user_config_dir ();
212 filename = g_build_filename (config_dir, "accels", NULL);
215 return filename;
219 * e_show_uri:
220 * @parent: a parent #GtkWindow or %NULL
221 * @uri: the URI to show
223 * Launches the default application to show the given URI. The URI must
224 * be of a form understood by GIO. If the URI cannot be shown, it presents
225 * a dialog describing the error. The dialog is set as transient to @parent
226 * if @parent is non-%NULL.
228 void
229 e_show_uri (GtkWindow *parent,
230 const gchar *uri)
232 GtkWidget *dialog;
233 GdkScreen *screen = NULL;
234 GError *error = NULL;
235 guint32 timestamp;
237 g_return_if_fail (uri != NULL);
239 timestamp = gtk_get_current_event_time ();
241 if (parent != NULL)
242 screen = gtk_widget_get_screen (GTK_WIDGET (parent));
244 if (gtk_show_uri (screen, uri, timestamp, &error))
245 return;
247 dialog = gtk_message_dialog_new_with_markup (
248 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
249 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
250 "<big><b>%s</b></big>",
251 _("Could not open the link."));
253 gtk_message_dialog_format_secondary_text (
254 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
256 gtk_dialog_run (GTK_DIALOG (dialog));
258 gtk_widget_destroy (dialog);
259 g_error_free (error);
262 static gboolean
263 e_misc_utils_is_help_package_installed (void)
265 gboolean is_installed;
266 gchar *path;
268 /* Viewing user documentation requires the evolution help
269 * files. Look for one of the files it installs. */
270 path = g_build_filename (EVOLUTION_DATADIR, "help", "C", PACKAGE, "index.page", NULL);
272 is_installed = g_file_test (path, G_FILE_TEST_IS_REGULAR);
274 g_free (path);
276 if (is_installed) {
277 GAppInfo *help_handler;
279 help_handler = g_app_info_get_default_for_uri_scheme ("help");
281 is_installed = help_handler && g_app_info_get_commandline (help_handler);
283 g_clear_object (&help_handler);
286 return is_installed;
290 * e_display_help:
291 * @parent: a parent #GtkWindow or %NULL
292 * @link_id: help section to present or %NULL
294 * Opens the user documentation to the section given by @link_id, or to the
295 * table of contents if @link_id is %NULL. If the user documentation cannot
296 * be opened, it presents a dialog describing the error. The dialog is set
297 * as transient to @parent if @parent is non-%NULL.
299 void
300 e_display_help (GtkWindow *parent,
301 const gchar *link_id)
303 GString *uri;
304 GtkWidget *dialog;
305 GdkScreen *screen = NULL;
306 GError *error = NULL;
307 guint32 timestamp;
309 if (e_misc_utils_is_help_package_installed ()) {
310 uri = g_string_new ("help:" PACKAGE);
311 } else {
312 uri = g_string_new ("https://help.gnome.org/users/" PACKAGE "/");
313 g_string_append_printf (uri, "%d.%d", EDS_MAJOR_VERSION, EDS_MINOR_VERSION);
316 timestamp = gtk_get_current_event_time ();
318 if (parent != NULL)
319 screen = gtk_widget_get_screen (GTK_WIDGET (parent));
322 if (link_id != NULL) {
323 g_string_append (uri, "/");
324 g_string_append (uri, link_id);
327 if (gtk_show_uri (screen, uri->str, timestamp, &error))
328 goto exit;
330 dialog = gtk_message_dialog_new_with_markup (
331 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
332 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
333 "<big><b>%s</b></big>",
334 _("Could not display help for Evolution."));
336 gtk_message_dialog_format_secondary_text (
337 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
339 gtk_dialog_run (GTK_DIALOG (dialog));
341 gtk_widget_destroy (dialog);
342 g_error_free (error);
344 exit:
345 g_string_free (uri, TRUE);
349 * e_restore_window:
350 * @window: a #GtkWindow
351 * @settings_path: a #GSettings path
352 * @flags: flags indicating which window features to restore
354 * This function can restore one of or both a window's size and position
355 * using #GSettings keys at @settings_path which conform to the relocatable
356 * schema "org.gnome.evolution.window".
358 * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's
359 * previously recorded size and maximize state.
361 * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to
362 * the previously recorded screen coordinates.
364 * The respective #GSettings values will be updated when the window is
365 * resized and/or moved.
367 void
368 e_restore_window (GtkWindow *window,
369 const gchar *settings_path,
370 ERestoreWindowFlags flags)
372 WindowData *data;
373 GSettings *settings;
374 const gchar *schema;
376 g_return_if_fail (GTK_IS_WINDOW (window));
377 g_return_if_fail (settings_path != NULL);
379 schema = "org.gnome.evolution.window";
380 settings = g_settings_new_with_path (schema, settings_path);
382 data = g_slice_new0 (WindowData);
383 data->window = window;
384 data->settings = g_object_ref (settings);
385 data->flags = flags;
387 if (flags & E_RESTORE_WINDOW_SIZE) {
388 gint width, height;
390 width = g_settings_get_int (settings, "width");
391 height = g_settings_get_int (settings, "height");
393 if (width > 0 && height > 0)
394 gtk_window_resize (window, width, height);
396 if (g_settings_get_boolean (settings, "maximized")) {
397 GdkScreen *screen;
398 GdkRectangle monitor_area;
399 gint x, y, monitor;
401 x = g_settings_get_int (settings, "x");
402 y = g_settings_get_int (settings, "y");
404 screen = gtk_window_get_screen (window);
405 gtk_window_get_size (window, &width, &height);
407 data->premax_width = width;
408 data->premax_height = height;
410 monitor = gdk_screen_get_monitor_at_point (screen, x, y);
411 if (monitor < 0)
412 monitor = 0;
414 if (monitor >= gdk_screen_get_n_monitors (screen))
415 monitor = 0;
417 gdk_screen_get_monitor_workarea (
418 screen, monitor, &monitor_area);
420 gtk_window_resize (
421 window,
422 monitor_area.width,
423 monitor_area.height);
425 gtk_window_maximize (window);
429 if (flags & E_RESTORE_WINDOW_POSITION) {
430 gint x, y;
432 x = g_settings_get_int (settings, "x");
433 y = g_settings_get_int (settings, "y");
435 gtk_window_move (window, x, y);
438 g_object_set_data_full (
439 G_OBJECT (window),
440 "e-util-window-data", data,
441 (GDestroyNotify) window_data_free);
443 g_signal_connect (
444 window, "configure-event",
445 G_CALLBACK (window_configure_event_cb), data);
447 g_signal_connect (
448 window, "window-state-event",
449 G_CALLBACK (window_state_event_cb), data);
451 g_signal_connect (
452 window, "unmap",
453 G_CALLBACK (window_unmap_cb), data);
455 g_object_unref (settings);
459 * e_lookup_action:
460 * @ui_manager: a #GtkUIManager
461 * @action_name: the name of an action
463 * Returns the first #GtkAction named @action_name by traversing the
464 * list of action groups in @ui_manager. If no such action exists, the
465 * function emits a critical warning before returning %NULL, since this
466 * probably indicates a programming error and most code is not prepared
467 * to deal with lookup failures.
469 * Returns: the first #GtkAction named @action_name
471 GtkAction *
472 e_lookup_action (GtkUIManager *ui_manager,
473 const gchar *action_name)
475 GtkAction *action = NULL;
476 GList *iter;
478 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
479 g_return_val_if_fail (action_name != NULL, NULL);
481 iter = gtk_ui_manager_get_action_groups (ui_manager);
483 while (iter != NULL) {
484 GtkActionGroup *action_group = iter->data;
486 action = gtk_action_group_get_action (
487 action_group, action_name);
488 if (action != NULL)
489 return action;
491 iter = g_list_next (iter);
494 g_critical ("%s: action '%s' not found", G_STRFUNC, action_name);
496 return NULL;
500 * e_lookup_action_group:
501 * @ui_manager: a #GtkUIManager
502 * @group_name: the name of an action group
504 * Returns the #GtkActionGroup in @ui_manager named @group_name. If no
505 * such action group exists, the function emits a critical warnings before
506 * returning %NULL, since this probably indicates a programming error and
507 * most code is not prepared to deal with lookup failures.
509 * Returns: the #GtkActionGroup named @group_name
511 GtkActionGroup *
512 e_lookup_action_group (GtkUIManager *ui_manager,
513 const gchar *group_name)
515 GList *iter;
517 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
518 g_return_val_if_fail (group_name != NULL, NULL);
520 iter = gtk_ui_manager_get_action_groups (ui_manager);
522 while (iter != NULL) {
523 GtkActionGroup *action_group = iter->data;
524 const gchar *name;
526 name = gtk_action_group_get_name (action_group);
527 if (strcmp (name, group_name) == 0)
528 return action_group;
530 iter = g_list_next (iter);
533 g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name);
535 return NULL;
539 * e_action_compare_by_label:
540 * @action1: a #GtkAction
541 * @action2: a #GtkAction
543 * Compares the labels for @action1 and @action2 using g_utf8_collate().
545 * Returns: &lt; 0 if @action1 compares before @action2, 0 if they
546 * compare equal, &gt; 0 if @action1 compares after @action2
548 gint
549 e_action_compare_by_label (GtkAction *action1,
550 GtkAction *action2)
552 gchar *label1;
553 gchar *label2;
554 gint result;
556 /* XXX This is horribly inefficient but will generally only be
557 * used on short lists of actions during UI construction. */
559 if (action1 == action2)
560 return 0;
562 g_object_get (action1, "label", &label1, NULL);
563 g_object_get (action2, "label", &label2, NULL);
565 result = g_utf8_collate (label1, label2);
567 g_free (label1);
568 g_free (label2);
570 return result;
574 * e_action_group_remove_all_actions:
575 * @action_group: a #GtkActionGroup
577 * Removes all actions from the action group.
579 void
580 e_action_group_remove_all_actions (GtkActionGroup *action_group)
582 GList *list, *iter;
584 /* XXX I've proposed this function for inclusion in GTK+.
585 * GtkActionGroup stores actions in an internal hash
586 * table and can do this more efficiently by calling
587 * g_hash_table_remove_all().
589 * http://bugzilla.gnome.org/show_bug.cgi?id=550485 */
591 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
593 list = gtk_action_group_list_actions (action_group);
594 for (iter = list; iter != NULL; iter = iter->next)
595 gtk_action_group_remove_action (action_group, iter->data);
596 g_list_free (list);
600 * e_radio_action_get_current_action:
601 * @radio_action: a #GtkRadioAction
603 * Returns the currently active member of the group to which @radio_action
604 * belongs.
606 * Returns: the currently active group member
608 GtkRadioAction *
609 e_radio_action_get_current_action (GtkRadioAction *radio_action)
611 GSList *group;
612 gint current_value;
614 g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL);
616 group = gtk_radio_action_get_group (radio_action);
617 current_value = gtk_radio_action_get_current_value (radio_action);
619 while (group != NULL) {
620 gint value;
622 radio_action = GTK_RADIO_ACTION (group->data);
623 g_object_get (radio_action, "value", &value, NULL);
625 if (value == current_value)
626 return radio_action;
628 group = g_slist_next (group);
631 return NULL;
635 * e_action_group_add_actions_localized:
636 * @action_group: a #GtkActionGroup to add @entries to
637 * @translation_domain: a translation domain to use
638 * to translate label and tooltip strings in @entries
639 * @entries: (array length=n_entries): an array of action descriptions
640 * @n_entries: the number of entries
641 * @user_data: data to pass to the action callbacks
643 * Adds #GtkAction-s defined by @entries to @action_group, with action's
644 * label and tooltip localized in the given translation domain, instead
645 * of the domain set on the @action_group.
647 * Since: 3.4
649 void
650 e_action_group_add_actions_localized (GtkActionGroup *action_group,
651 const gchar *translation_domain,
652 const GtkActionEntry *entries,
653 guint n_entries,
654 gpointer user_data)
656 GtkActionGroup *tmp_group;
657 GList *list, *iter;
658 gint ii;
660 g_return_if_fail (action_group != NULL);
661 g_return_if_fail (entries != NULL);
662 g_return_if_fail (n_entries > 0);
663 g_return_if_fail (translation_domain != NULL);
664 g_return_if_fail (*translation_domain);
666 tmp_group = gtk_action_group_new ("temporary-group");
667 gtk_action_group_set_translation_domain (tmp_group, translation_domain);
668 gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data);
670 list = gtk_action_group_list_actions (tmp_group);
671 for (iter = list; iter != NULL; iter = iter->next) {
672 GtkAction *action = GTK_ACTION (iter->data);
673 const gchar *action_name;
675 g_object_ref (action);
677 action_name = gtk_action_get_name (action);
679 for (ii = 0; ii < n_entries; ii++) {
680 if (g_strcmp0 (entries[ii].name, action_name) == 0) {
681 gtk_action_group_remove_action (
682 tmp_group, action);
683 gtk_action_group_add_action_with_accel (
684 action_group, action,
685 entries[ii].accelerator);
686 break;
690 g_object_unref (action);
693 g_list_free (list);
694 g_object_unref (tmp_group);
698 * e_builder_get_widget:
699 * @builder: a #GtkBuilder
700 * @widget_name: name of a widget in @builder
702 * Gets the widget named @widget_name. Note that this function does not
703 * increment the reference count of the returned widget. If @widget_name
704 * could not be found in the @builder<!-- -->'s object tree, a run-time
705 * warning is emitted since this usually indicates a programming error.
707 * This is a convenience function to work around the awkwardness of
708 * #GtkBuilder returning #GObject pointers, when the vast majority of
709 * the time you want a #GtkWidget pointer.
711 * If you need something from @builder other than a #GtkWidget, or you
712 * want to test for the existence of some widget name without incurring
713 * a run-time warning, use gtk_builder_get_object().
715 * Returns: the widget named @widget_name, or %NULL
717 GtkWidget *
718 e_builder_get_widget (GtkBuilder *builder,
719 const gchar *widget_name)
721 GObject *object;
723 g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
724 g_return_val_if_fail (widget_name != NULL, NULL);
726 object = gtk_builder_get_object (builder, widget_name);
727 if (object == NULL) {
728 g_warning ("Could not find widget '%s'", widget_name);
729 return NULL;
732 return GTK_WIDGET (object);
736 * e_load_ui_builder_definition:
737 * @builder: a #GtkBuilder
738 * @basename: basename of the UI definition file
740 * Loads a UI definition into @builder from Evolution's UI directory.
741 * Failure here is fatal, since the application can't function without
742 * its UI definitions.
744 void
745 e_load_ui_builder_definition (GtkBuilder *builder,
746 const gchar *basename)
748 gchar *filename;
749 GError *error = NULL;
751 g_return_if_fail (GTK_IS_BUILDER (builder));
752 g_return_if_fail (basename != NULL);
754 filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
755 gtk_builder_add_from_file (builder, filename, &error);
756 g_free (filename);
758 if (error != NULL) {
759 g_error ("%s: %s", basename, error->message);
760 g_warn_if_reached ();
765 * e_load_ui_manager_definition:
766 * @ui_manager: a #GtkUIManager
767 * @basename: basename of the UI definition file
769 * Loads a UI definition into @ui_manager from Evolution's UI directory.
770 * Failure here is fatal, since the application can't function without
771 * its UI definitions.
773 * Returns: The merge ID for the merged UI. The merge ID can be used to
774 * unmerge the UI with gtk_ui_manager_remove_ui().
776 guint
777 e_load_ui_manager_definition (GtkUIManager *ui_manager,
778 const gchar *basename)
780 gchar *filename;
781 guint merge_id;
782 GError *error = NULL;
784 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), 0);
785 g_return_val_if_fail (basename != NULL, 0);
787 filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
788 merge_id = gtk_ui_manager_add_ui_from_file (
789 ui_manager, filename, &error);
790 g_free (filename);
792 if (error != NULL) {
793 g_error ("%s: %s", basename, error->message);
794 g_warn_if_reached ();
797 return merge_id;
800 /* Helper for e_categories_add_change_hook() */
801 static void
802 categories_changed_cb (GObject *useless_opaque_object,
803 GHookList *hook_list)
805 /* e_categories_register_change_listener() is broken because
806 * it requires callbacks to allow for some opaque GObject as
807 * the first argument (not does it document this). */
808 g_hook_list_invoke (hook_list, FALSE);
811 /* Helper for e_categories_add_change_hook() */
812 static void
813 categories_weak_notify_cb (GHookList *hook_list,
814 gpointer where_the_object_was)
816 GHook *hook;
818 /* This should not happen, but if we fail to find the hook for
819 * some reason, g_hook_destroy_link() will warn about the NULL
820 * pointer, which is all we would do anyway so no need to test
821 * for it ourselves. */
822 hook = g_hook_find_data (hook_list, TRUE, where_the_object_was);
823 g_hook_destroy_link (hook_list, hook);
827 * e_categories_add_change_hook:
828 * @func: a hook function
829 * @object: a #GObject to be passed to @func, or %NULL
831 * A saner alternative to e_categories_register_change_listener().
833 * Adds a hook function to be called when a category is added, removed or
834 * modified. If @object is not %NULL, the hook function is automatically
835 * removed when @object is finalized.
837 void
838 e_categories_add_change_hook (GHookFunc func,
839 gpointer object)
841 static gboolean initialized = FALSE;
842 static GHookList hook_list;
843 GHook *hook;
845 g_return_if_fail (func != NULL);
847 if (object != NULL)
848 g_return_if_fail (G_IS_OBJECT (object));
850 if (!initialized) {
851 g_hook_list_init (&hook_list, sizeof (GHook));
852 e_categories_register_change_listener (
853 G_CALLBACK (categories_changed_cb), &hook_list);
854 initialized = TRUE;
857 hook = g_hook_alloc (&hook_list);
859 hook->func = func;
860 hook->data = object;
862 if (object != NULL)
863 g_object_weak_ref (
864 G_OBJECT (object), (GWeakNotify)
865 categories_weak_notify_cb, &hook_list);
867 g_hook_append (&hook_list, hook);
871 * e_flexible_strtod:
872 * @nptr: the string to convert to a numeric value.
873 * @endptr: if non-NULL, it returns the character after
874 * the last character used in the conversion.
876 * Converts a string to a gdouble value. This function detects
877 * strings either in the standard C locale or in the current locale.
879 * This function is typically used when reading configuration files or
880 * other non-user input that should not be locale dependent, but may
881 * have been in the past. To handle input from the user you should
882 * normally use the locale-sensitive system strtod function.
884 * To convert from a double to a string in a locale-insensitive way, use
885 * @g_ascii_dtostr.
887 * Returns: the gdouble value
889 gdouble
890 e_flexible_strtod (const gchar *nptr,
891 gchar **endptr)
893 gchar *fail_pos;
894 gdouble val;
895 struct lconv *locale_data;
896 const gchar *decimal_point;
897 gint decimal_point_len;
898 const gchar *p, *decimal_point_pos;
899 const gchar *end = NULL; /* Silence gcc */
900 gchar *copy, *c;
902 g_return_val_if_fail (nptr != NULL, 0);
904 fail_pos = NULL;
906 locale_data = localeconv ();
907 decimal_point = locale_data->decimal_point;
908 decimal_point_len = strlen (decimal_point);
910 g_return_val_if_fail (decimal_point_len != 0, 0);
912 decimal_point_pos = NULL;
913 if (!strcmp (decimal_point, "."))
914 return strtod (nptr, endptr);
916 p = nptr;
918 /* Skip leading space */
919 while (isspace ((guchar) * p))
920 p++;
922 /* Skip leading optional sign */
923 if (*p == '+' || *p == '-')
924 p++;
926 if (p[0] == '0' &&
927 (p[1] == 'x' || p[1] == 'X')) {
928 p += 2;
929 /* HEX - find the (optional) decimal point */
931 while (isxdigit ((guchar) * p))
932 p++;
934 if (*p == '.') {
935 decimal_point_pos = p++;
937 while (isxdigit ((guchar) * p))
938 p++;
940 if (*p == 'p' || *p == 'P')
941 p++;
942 if (*p == '+' || *p == '-')
943 p++;
944 while (isdigit ((guchar) * p))
945 p++;
946 end = p;
947 } else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
948 return strtod (nptr, endptr);
950 } else {
951 while (isdigit ((guchar) * p))
952 p++;
954 if (*p == '.') {
955 decimal_point_pos = p++;
957 while (isdigit ((guchar) * p))
958 p++;
960 if (*p == 'e' || *p == 'E')
961 p++;
962 if (*p == '+' || *p == '-')
963 p++;
964 while (isdigit ((guchar) * p))
965 p++;
966 end = p;
967 } else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
968 return strtod (nptr, endptr);
971 /* For the other cases, we need not convert the decimal point */
973 if (!decimal_point_pos)
974 return strtod (nptr, endptr);
976 /* We need to convert the '.' to the locale specific decimal point */
977 copy = g_malloc (end - nptr + 1 + decimal_point_len);
979 c = copy;
980 memcpy (c, nptr, decimal_point_pos - nptr);
981 c += decimal_point_pos - nptr;
982 memcpy (c, decimal_point, decimal_point_len);
983 c += decimal_point_len;
984 memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
985 c += end - (decimal_point_pos + 1);
986 *c = 0;
988 val = strtod (copy, &fail_pos);
990 if (fail_pos) {
991 if (fail_pos > decimal_point_pos)
992 fail_pos =
993 (gchar *) nptr + (fail_pos - copy) -
994 (decimal_point_len - 1);
995 else
996 fail_pos = (gchar *) nptr + (fail_pos - copy);
999 g_free (copy);
1001 if (endptr)
1002 *endptr = fail_pos;
1004 return val;
1008 * e_ascii_dtostr:
1009 * @buffer: A buffer to place the resulting string in
1010 * @buf_len: The length of the buffer.
1011 * @format: The printf-style format to use for the
1012 * code to use for converting.
1013 * @d: The double to convert
1015 * Converts a double to a string, using the '.' as
1016 * decimal_point. To format the number you pass in
1017 * a printf-style formating string. Allowed conversion
1018 * specifiers are eEfFgG.
1020 * If you want to generates enough precision that converting
1021 * the string back using @g_strtod gives the same machine-number
1022 * (on machines with IEEE compatible 64bit doubles) use the format
1023 * string "%.17g". If you do this it is guaranteed that the size
1024 * of the resulting string will never be larger than
1025 * @G_ASCII_DTOSTR_BUF_SIZE bytes.
1027 * Returns: the pointer to the buffer with the converted string
1029 gchar *
1030 e_ascii_dtostr (gchar *buffer,
1031 gint buf_len,
1032 const gchar *format,
1033 gdouble d)
1035 struct lconv *locale_data;
1036 const gchar *decimal_point;
1037 gint decimal_point_len;
1038 gchar *p;
1039 gint rest_len;
1040 gchar format_char;
1042 g_return_val_if_fail (buffer != NULL, NULL);
1043 g_return_val_if_fail (format[0] == '%', NULL);
1044 g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL);
1046 format_char = format[strlen (format) - 1];
1048 g_return_val_if_fail (format_char == 'e' || format_char == 'E' ||
1049 format_char == 'f' || format_char == 'F' ||
1050 format_char == 'g' || format_char == 'G',
1051 NULL);
1053 if (format[0] != '%')
1054 return NULL;
1056 if (strpbrk (format + 1, "'l%"))
1057 return NULL;
1059 if (!(format_char == 'e' || format_char == 'E' ||
1060 format_char == 'f' || format_char == 'F' ||
1061 format_char == 'g' || format_char == 'G'))
1062 return NULL;
1064 g_snprintf (buffer, buf_len, format, d);
1066 locale_data = localeconv ();
1067 decimal_point = locale_data->decimal_point;
1068 decimal_point_len = strlen (decimal_point);
1070 g_return_val_if_fail (decimal_point_len != 0, NULL);
1072 if (strcmp (decimal_point, ".")) {
1073 p = buffer;
1075 if (*p == '+' || *p == '-')
1076 p++;
1078 while (isdigit ((guchar) * p))
1079 p++;
1081 if (strncmp (p, decimal_point, decimal_point_len) == 0) {
1082 *p = '.';
1083 p++;
1084 if (decimal_point_len > 1) {
1085 rest_len = strlen (p + (decimal_point_len - 1));
1086 memmove (
1087 p, p + (decimal_point_len - 1),
1088 rest_len);
1089 p[rest_len] = 0;
1094 return buffer;
1098 * e_str_without_underscores:
1099 * @string: the string to strip underscores from
1101 * Strips underscores from a string in the same way
1102 * @gtk_label_new_with_mnemonics does. The returned string should be freed
1103 * using g_free().
1105 * Returns: a newly-allocated string without underscores
1107 gchar *
1108 e_str_without_underscores (const gchar *string)
1110 gchar *new_string;
1111 const gchar *sp;
1112 gchar *dp;
1114 new_string = g_malloc (strlen (string) + 1);
1116 dp = new_string;
1117 for (sp = string; *sp != '\0'; sp++) {
1118 if (*sp != '_') {
1119 *dp = *sp;
1120 dp++;
1121 } else if (sp[1] == '_') {
1122 /* Translate "__" in "_". */
1123 *dp = '_';
1124 dp++;
1125 sp++;
1128 *dp = 0;
1130 return new_string;
1134 * e_str_replace_string
1135 * @text: the string to replace
1136 * @before: the string to be replaced
1137 * @after: the string to replaced with
1139 * Replaces every occurrence of the string @before with the string @after in
1140 * the string @text and returns a #GString with result that should be freed
1141 * with g_string_free().
1143 * Returns: a newly-allocated #GString
1145 GString *
1146 e_str_replace_string (const gchar *text,
1147 const gchar *before,
1148 const gchar *after)
1150 const gchar *p, *next;
1151 GString *str;
1152 gint find_len;
1154 g_return_val_if_fail (text != NULL, NULL);
1155 g_return_val_if_fail (before != NULL, NULL);
1156 g_return_val_if_fail (*before, NULL);
1158 find_len = strlen (before);
1159 str = g_string_new ("");
1161 p = text;
1162 while (next = strstr (p, before), next) {
1163 if (p < next)
1164 g_string_append_len (str, p, next - p);
1166 if (after && *after)
1167 g_string_append (str, after);
1169 p = next + find_len;
1172 g_string_append (str, p);
1174 return str;
1177 gint
1178 e_str_compare (gconstpointer x,
1179 gconstpointer y)
1181 if (x == NULL || y == NULL) {
1182 if (x == y)
1183 return 0;
1184 else
1185 return x ? -1 : 1;
1188 return strcmp (x, y);
1191 gint
1192 e_str_case_compare (gconstpointer x,
1193 gconstpointer y)
1195 gchar *cx, *cy;
1196 gint res;
1198 if (x == NULL || y == NULL) {
1199 if (x == y)
1200 return 0;
1201 else
1202 return x ? -1 : 1;
1205 cx = g_utf8_casefold (x, -1);
1206 cy = g_utf8_casefold (y, -1);
1208 res = g_utf8_collate (cx, cy);
1210 g_free (cx);
1211 g_free (cy);
1213 return res;
1216 gint
1217 e_collate_compare (gconstpointer x,
1218 gconstpointer y)
1220 if (x == NULL || y == NULL) {
1221 if (x == y)
1222 return 0;
1223 else
1224 return x ? -1 : 1;
1227 return g_utf8_collate (x, y);
1230 gint
1231 e_int_compare (gconstpointer x,
1232 gconstpointer y)
1234 gint nx = GPOINTER_TO_INT (x);
1235 gint ny = GPOINTER_TO_INT (y);
1237 return (nx == ny) ? 0 : (nx < ny) ? -1 : 1;
1241 * e_color_to_value:
1242 * @color: a #GdkColor
1244 * Converts a #GdkColor to a 24-bit RGB color value.
1246 * Returns: a 24-bit color value
1248 guint32
1249 e_color_to_value (const GdkColor *color)
1251 GdkRGBA rgba;
1253 g_return_val_if_fail (color != NULL, 0);
1255 rgba.red = color->red / 65535.0;
1256 rgba.green = color->green / 65535.0;
1257 rgba.blue = color->blue / 65535.0;
1258 rgba.alpha = 0.0;
1260 return e_rgba_to_value (&rgba);
1264 * e_rgba_to_value:
1265 * @rgba: a #GdkRGBA
1267 * Converts #GdkRGBA to a 24-bit RGB color value
1269 * Returns: a 24-bit color value
1271 guint32
1272 e_rgba_to_value (const GdkRGBA *rgba)
1274 guint16 red;
1275 guint16 green;
1276 guint16 blue;
1278 g_return_val_if_fail (rgba != NULL, 0);
1280 red = 255 * rgba->red;
1281 green = 255 * rgba->green;
1282 blue = 255 * rgba->blue;
1284 return (guint32)
1285 ((((red & 0xFF) << 16) |
1286 ((green & 0xFF) << 8) |
1287 (blue & 0xFF)) & 0xffffff);
1291 * e_rgba_to_color:
1292 * @rgba: a source #GdkRGBA
1293 * @color: a destination #GdkColor
1295 * Converts @rgba into @color, but loses the alpha channel from @rgba.
1297 void
1298 e_rgba_to_color (const GdkRGBA *rgba,
1299 GdkColor *color)
1301 g_return_if_fail (rgba != NULL);
1302 g_return_if_fail (color != NULL);
1304 color->pixel = 0;
1305 color->red = rgba->red * 65535.0;
1306 color->green = rgba->green * 65535.0;
1307 color->blue = rgba->blue * 65535.0;
1311 * e_utils_get_theme_color:
1312 * @widget: a #GtkWidget instance
1313 * @color_names: comma-separated theme color names
1314 * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse()
1315 * @rgba: where to store the read color
1317 * Reads named theme color from a #GtkStyleContext of @widget.
1318 * The @color_names are read one after another from left to right,
1319 * the next are meant as fallbacks, in case the theme doesn't
1320 * define the previous color. If none is found then the @fallback_color_ident
1321 * is set to @rgba.
1323 void
1324 e_utils_get_theme_color (GtkWidget *widget,
1325 const gchar *color_names,
1326 const gchar *fallback_color_ident,
1327 GdkRGBA *rgba)
1329 GtkStyleContext *style_context;
1330 gchar **names;
1331 gint ii;
1333 g_return_if_fail (GTK_IS_WIDGET (widget));
1334 g_return_if_fail (color_names != NULL);
1335 g_return_if_fail (fallback_color_ident != NULL);
1336 g_return_if_fail (rgba != NULL);
1338 style_context = gtk_widget_get_style_context (widget);
1340 names = g_strsplit (color_names, ",", -1);
1341 for (ii = 0; names && names[ii]; ii++) {
1342 if (gtk_style_context_lookup_color (style_context, names[ii], rgba)) {
1343 g_strfreev (names);
1344 return;
1348 g_strfreev (names);
1350 g_warn_if_fail (gdk_rgba_parse (rgba, fallback_color_ident));
1354 * e_utils_get_theme_color_color:
1355 * @widget: a #GtkWidget instance
1356 * @color_names: comma-separated theme color names
1357 * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse()
1358 * @color: where to store the read color
1360 * The same as e_utils_get_theme_color(), only populates #GdkColor,
1361 * instead of #GdkRGBA.
1363 void
1364 e_utils_get_theme_color_color (GtkWidget *widget,
1365 const gchar *color_names,
1366 const gchar *fallback_color_ident,
1367 GdkColor *color)
1369 GdkRGBA rgba;
1371 g_return_if_fail (GTK_IS_WIDGET (widget));
1372 g_return_if_fail (color_names != NULL);
1373 g_return_if_fail (fallback_color_ident != NULL);
1374 g_return_if_fail (color != NULL);
1376 e_utils_get_theme_color (widget, color_names, fallback_color_ident, &rgba);
1378 e_rgba_to_color (&rgba, color);
1381 /* This is copied from gtk+ sources */
1382 static void
1383 rgb_to_hls (gdouble *r,
1384 gdouble *g,
1385 gdouble *b)
1387 gdouble min;
1388 gdouble max;
1389 gdouble red;
1390 gdouble green;
1391 gdouble blue;
1392 gdouble h, l, s;
1393 gdouble delta;
1395 red = *r;
1396 green = *g;
1397 blue = *b;
1399 if (red > green) {
1400 if (red > blue)
1401 max = red;
1402 else
1403 max = blue;
1405 if (green < blue)
1406 min = green;
1407 else
1408 min = blue;
1409 } else {
1410 if (green > blue)
1411 max = green;
1412 else
1413 max = blue;
1415 if (red < blue)
1416 min = red;
1417 else
1418 min = blue;
1421 l = (max + min) / 2;
1422 s = 0;
1423 h = 0;
1425 if (max != min) {
1426 if (l <= 0.5)
1427 s = (max - min) / (max + min);
1428 else
1429 s = (max - min) / (2 - max - min);
1431 delta = max -min;
1432 if (red == max)
1433 h = (green - blue) / delta;
1434 else if (green == max)
1435 h = 2 + (blue - red) / delta;
1436 else if (blue == max)
1437 h = 4 + (red - green) / delta;
1439 h *= 60;
1440 if (h < 0.0)
1441 h += 360;
1444 *r = h;
1445 *g = l;
1446 *b = s;
1449 /* This is copied from gtk+ sources */
1450 static void
1451 hls_to_rgb (gdouble *h,
1452 gdouble *l,
1453 gdouble *s)
1455 gdouble hue;
1456 gdouble lightness;
1457 gdouble saturation;
1458 gdouble m1, m2;
1459 gdouble r, g, b;
1461 lightness = *l;
1462 saturation = *s;
1464 if (lightness <= 0.5)
1465 m2 = lightness * (1 + saturation);
1466 else
1467 m2 = lightness + saturation - lightness * saturation;
1468 m1 = 2 * lightness - m2;
1470 if (saturation == 0) {
1471 *h = lightness;
1472 *l = lightness;
1473 *s = lightness;
1474 } else {
1475 hue = *h + 120;
1476 while (hue > 360)
1477 hue -= 360;
1478 while (hue < 0)
1479 hue += 360;
1481 if (hue < 60)
1482 r = m1 + (m2 - m1) * hue / 60;
1483 else if (hue < 180)
1484 r = m2;
1485 else if (hue < 240)
1486 r = m1 + (m2 - m1) * (240 - hue) / 60;
1487 else
1488 r = m1;
1490 hue = *h;
1491 while (hue > 360)
1492 hue -= 360;
1493 while (hue < 0)
1494 hue += 360;
1496 if (hue < 60)
1497 g = m1 + (m2 - m1) * hue / 60;
1498 else if (hue < 180)
1499 g = m2;
1500 else if (hue < 240)
1501 g = m1 + (m2 - m1) * (240 - hue) / 60;
1502 else
1503 g = m1;
1505 hue = *h - 120;
1506 while (hue > 360)
1507 hue -= 360;
1508 while (hue < 0)
1509 hue += 360;
1511 if (hue < 60)
1512 b = m1 + (m2 - m1) * hue / 60;
1513 else if (hue < 180)
1514 b = m2;
1515 else if (hue < 240)
1516 b = m1 + (m2 - m1) * (240 - hue) / 60;
1517 else
1518 b = m1;
1520 *h = r;
1521 *l = g;
1522 *s = b;
1526 /* This is copied from gtk+ sources */
1527 void
1528 e_utils_shade_color (const GdkRGBA *a,
1529 GdkRGBA *b,
1530 gdouble mult)
1532 gdouble red;
1533 gdouble green;
1534 gdouble blue;
1536 g_return_if_fail (a != NULL);
1537 g_return_if_fail (b != NULL);
1539 red = a->red;
1540 green = a->green;
1541 blue = a->blue;
1543 rgb_to_hls (&red, &green, &blue);
1545 green *= mult;
1546 if (green > 1.0)
1547 green = 1.0;
1548 else if (green < 0.0)
1549 green = 0.0;
1551 blue *= mult;
1552 if (blue > 1.0)
1553 blue = 1.0;
1554 else if (blue < 0.0)
1555 blue = 0.0;
1557 hls_to_rgb (&red, &green, &blue);
1559 b->red = red;
1560 b->green = green;
1561 b->blue = blue;
1562 b->alpha = a->alpha;
1565 static gint
1566 epow10 (gint number)
1568 gint value = 1;
1570 while (number-- > 0)
1571 value *= 10;
1573 return value;
1576 gchar *
1577 e_format_number (gint number)
1579 GList *iterator, *list = NULL;
1580 struct lconv *locality;
1581 gint char_length = 0;
1582 gint group_count = 0;
1583 gchar *grouping;
1584 gint last_count = 3;
1585 gint divider;
1586 gchar *value;
1587 gchar *value_iterator;
1589 locality = localeconv ();
1590 grouping = locality->grouping;
1591 while (number) {
1592 gchar *group;
1593 switch (*grouping) {
1594 default:
1595 last_count = *grouping;
1596 grouping++;
1597 /* coverity[fallthrough] */
1598 case 0:
1599 divider = epow10 (last_count);
1600 if (number >= divider) {
1601 group = g_strdup_printf (
1602 "%0*d", last_count, number % divider);
1603 } else {
1604 group = g_strdup_printf (
1605 "%d", number % divider);
1607 number /= divider;
1608 break;
1609 case CHAR_MAX:
1610 group = g_strdup_printf ("%d", number);
1611 number = 0;
1612 break;
1614 char_length += strlen (group);
1615 list = g_list_prepend (list, group);
1616 group_count++;
1619 if (list) {
1620 value = g_new (
1621 gchar, 1 + char_length + (group_count - 1) *
1622 strlen (locality->thousands_sep));
1624 iterator = list;
1625 value_iterator = value;
1627 strcpy (value_iterator, iterator->data);
1628 value_iterator += strlen (iterator->data);
1629 for (iterator = iterator->next; iterator; iterator = iterator->next) {
1630 strcpy (value_iterator, locality->thousands_sep);
1631 value_iterator += strlen (locality->thousands_sep);
1633 strcpy (value_iterator, iterator->data);
1634 value_iterator += strlen (iterator->data);
1636 g_list_foreach (list, (GFunc) g_free, NULL);
1637 g_list_free (list);
1638 return value;
1639 } else {
1640 return g_strdup ("0");
1644 /* Perform a binary search for key in base which has nmemb elements
1645 * of size bytes each. The comparisons are done by (*compare)(). */
1646 void
1647 e_bsearch (gconstpointer key,
1648 gconstpointer base,
1649 gsize nmemb,
1650 gsize size,
1651 ESortCompareFunc compare,
1652 gpointer closure,
1653 gsize *start,
1654 gsize *end)
1656 gsize l, u, idx;
1657 gconstpointer p;
1658 gint comparison;
1659 if (!(start || end))
1660 return;
1662 l = 0;
1663 u = nmemb;
1664 while (l < u) {
1665 idx = (l + u) / 2;
1666 p = (((const gchar *) base) + (idx * size));
1667 comparison = (*compare) (key, p, closure);
1668 if (comparison < 0)
1669 u = idx;
1670 else if (comparison > 0)
1671 l = idx + 1;
1672 else {
1673 gsize lsave, usave;
1674 lsave = l;
1675 usave = u;
1676 if (start) {
1677 while (l < u) {
1678 idx = (l + u) / 2;
1679 p = (((const gchar *) base) + (idx * size));
1680 comparison = (*compare) (key, p, closure);
1681 if (comparison <= 0)
1682 u = idx;
1683 else
1684 l = idx + 1;
1686 *start = l;
1688 l = lsave;
1689 u = usave;
1691 if (end) {
1692 while (l < u) {
1693 idx = (l + u) / 2;
1694 p = (((const gchar *) base) + (idx * size));
1695 comparison = (*compare) (key, p, closure);
1696 if (comparison < 0)
1697 u = idx;
1698 else
1699 l = idx + 1;
1701 *end = l;
1703 return;
1707 if (start)
1708 *start = l;
1709 if (end)
1710 *end = l;
1713 /* Function to do a last minute fixup of the AM/PM stuff if the locale
1714 * and gettext haven't done it right. Most English speaking countries
1715 * except the USA use the 24 hour clock (UK, Australia etc). However
1716 * since they are English nobody bothers to write a language
1717 * translation (gettext) file. So the locale turns off the AM/PM, but
1718 * gettext does not turn on the 24 hour clock. Leaving a mess.
1720 * This routine checks if AM/PM are defined in the locale, if not it
1721 * forces the use of the 24 hour clock.
1723 * The function itself is a front end on strftime and takes exactly
1724 * the same arguments.
1726 * TODO: Actually remove the '%p' from the fixed up string so that
1727 * there isn't a stray space.
1730 gsize
1731 e_strftime_fix_am_pm (gchar *str,
1732 gsize max,
1733 const gchar *fmt,
1734 const struct tm *tm)
1736 gchar buf[10];
1737 gchar *sp;
1738 gchar *ffmt;
1739 gsize ret;
1741 if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) {
1742 /* No AM/PM involved - can use the fmt string directly */
1743 ret = e_strftime (str, max, fmt, tm);
1744 } else {
1745 /* Get the AM/PM symbol from the locale */
1746 e_strftime (buf, 10, "%p", tm);
1748 if (buf[0]) {
1749 /* AM/PM have been defined in the locale
1750 * so we can use the fmt string directly. */
1751 ret = e_strftime (str, max, fmt, tm);
1752 } else {
1753 /* No AM/PM defined by locale
1754 * must change to 24 hour clock. */
1755 ffmt = g_strdup (fmt);
1756 for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) {
1757 /* Maybe this should be 'k', but I have never
1758 * seen a 24 clock actually use that format. */
1759 sp[1]='H';
1761 for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) {
1762 sp[1]='H';
1764 ret = e_strftime (str, max, ffmt, tm);
1765 g_free (ffmt);
1769 return (ret);
1772 gsize
1773 e_utf8_strftime_fix_am_pm (gchar *str,
1774 gsize max,
1775 const gchar *fmt,
1776 const struct tm *tm)
1778 gsize sz, ret;
1779 gchar *locale_fmt, *buf;
1781 locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL);
1782 if (!locale_fmt)
1783 return 0;
1785 ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm);
1786 if (!ret) {
1787 g_free (locale_fmt);
1788 return 0;
1791 buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL);
1792 if (!buf) {
1793 g_free (locale_fmt);
1794 return 0;
1797 if (sz >= max) {
1798 gchar *tmp = buf + max - 1;
1799 tmp = g_utf8_find_prev_char (buf, tmp);
1800 if (tmp)
1801 sz = tmp - buf;
1802 else
1803 sz = 0;
1805 memcpy (str, buf, sz);
1806 str[sz] = '\0';
1807 g_free (locale_fmt);
1808 g_free (buf);
1809 return sz;
1813 * e_get_month_name:
1814 * @month: month index
1815 * @abbreviated: if %TRUE, abbreviate the month name
1817 * Returns the localized name for @month. If @abbreviated is %TRUE,
1818 * returns the locale's abbreviated month name.
1820 * Returns: localized month name
1822 const gchar *
1823 e_get_month_name (GDateMonth month,
1824 gboolean abbreviated)
1826 /* Make the indices correspond to the enum values. */
1827 static const gchar *abbr_names[G_DATE_DECEMBER + 1];
1828 static const gchar *full_names[G_DATE_DECEMBER + 1];
1829 static gboolean first_time = TRUE;
1831 g_return_val_if_fail (month >= G_DATE_JANUARY, NULL);
1832 g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL);
1834 if (G_UNLIKELY (first_time)) {
1835 gchar buffer[256];
1836 GDateMonth ii;
1837 GDate date;
1839 memset (abbr_names, 0, sizeof (abbr_names));
1840 memset (full_names, 0, sizeof (full_names));
1842 /* First Julian day was in January. */
1843 g_date_set_julian (&date, 1);
1845 for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) {
1846 g_date_strftime (buffer, sizeof (buffer), "%b", &date);
1847 abbr_names[ii] = g_intern_string (buffer);
1848 g_date_strftime (buffer, sizeof (buffer), "%B", &date);
1849 full_names[ii] = g_intern_string (buffer);
1850 g_date_add_months (&date, 1);
1853 first_time = FALSE;
1856 return abbreviated ? abbr_names[month] : full_names[month];
1860 * e_get_weekday_name:
1861 * @weekday: weekday index
1862 * @abbreviated: if %TRUE, abbreviate the weekday name
1864 * Returns the localized name for @weekday. If @abbreviated is %TRUE,
1865 * returns the locale's abbreviated weekday name.
1867 * Returns: localized weekday name
1869 const gchar *
1870 e_get_weekday_name (GDateWeekday weekday,
1871 gboolean abbreviated)
1873 /* Make the indices correspond to the enum values. */
1874 static const gchar *abbr_names[G_DATE_SUNDAY + 1];
1875 static const gchar *full_names[G_DATE_SUNDAY + 1];
1876 static gboolean first_time = TRUE;
1878 g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL);
1879 g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL);
1881 if (G_UNLIKELY (first_time)) {
1882 gchar buffer[256];
1883 GDateWeekday ii;
1884 GDate date;
1886 memset (abbr_names, 0, sizeof (abbr_names));
1887 memset (full_names, 0, sizeof (full_names));
1889 /* First Julian day was a Monday. */
1890 g_date_set_julian (&date, 1);
1892 for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) {
1893 g_date_strftime (buffer, sizeof (buffer), "%a", &date);
1894 abbr_names[ii] = g_intern_string (buffer);
1895 g_date_strftime (buffer, sizeof (buffer), "%A", &date);
1896 full_names[ii] = g_intern_string (buffer);
1897 g_date_add_days (&date, 1);
1900 first_time = FALSE;
1903 return abbreviated ? abbr_names[weekday] : full_names[weekday];
1907 * e_weekday_get_next:
1908 * @weekday: a #GDateWeekday
1910 * Returns the #GDateWeekday after @weekday.
1912 * Returns: the day after @weekday
1914 GDateWeekday
1915 e_weekday_get_next (GDateWeekday weekday)
1917 GDateWeekday next;
1919 /* Verbose for readability. */
1920 switch (weekday) {
1921 case G_DATE_MONDAY:
1922 next = G_DATE_TUESDAY;
1923 break;
1924 case G_DATE_TUESDAY:
1925 next = G_DATE_WEDNESDAY;
1926 break;
1927 case G_DATE_WEDNESDAY:
1928 next = G_DATE_THURSDAY;
1929 break;
1930 case G_DATE_THURSDAY:
1931 next = G_DATE_FRIDAY;
1932 break;
1933 case G_DATE_FRIDAY:
1934 next = G_DATE_SATURDAY;
1935 break;
1936 case G_DATE_SATURDAY:
1937 next = G_DATE_SUNDAY;
1938 break;
1939 case G_DATE_SUNDAY:
1940 next = G_DATE_MONDAY;
1941 break;
1942 default:
1943 next = G_DATE_BAD_WEEKDAY;
1944 break;
1947 return next;
1951 * e_weekday_get_prev:
1952 * @weekday: a #GDateWeekday
1954 * Returns the #GDateWeekday before @weekday.
1956 * Returns: the day before @weekday
1958 GDateWeekday
1959 e_weekday_get_prev (GDateWeekday weekday)
1961 GDateWeekday prev;
1963 /* Verbose for readability. */
1964 switch (weekday) {
1965 case G_DATE_MONDAY:
1966 prev = G_DATE_SUNDAY;
1967 break;
1968 case G_DATE_TUESDAY:
1969 prev = G_DATE_MONDAY;
1970 break;
1971 case G_DATE_WEDNESDAY:
1972 prev = G_DATE_TUESDAY;
1973 break;
1974 case G_DATE_THURSDAY:
1975 prev = G_DATE_WEDNESDAY;
1976 break;
1977 case G_DATE_FRIDAY:
1978 prev = G_DATE_THURSDAY;
1979 break;
1980 case G_DATE_SATURDAY:
1981 prev = G_DATE_FRIDAY;
1982 break;
1983 case G_DATE_SUNDAY:
1984 prev = G_DATE_SATURDAY;
1985 break;
1986 default:
1987 prev = G_DATE_BAD_WEEKDAY;
1988 break;
1991 return prev;
1995 * e_weekday_add_days:
1996 * @weekday: a #GDateWeekday
1997 * @n_days: number of days to add
1999 * Increments @weekday by @n_days.
2001 * Returns: a #GDateWeekday
2003 GDateWeekday
2004 e_weekday_add_days (GDateWeekday weekday,
2005 guint n_days)
2007 g_return_val_if_fail (
2008 g_date_valid_weekday (weekday),
2009 G_DATE_BAD_WEEKDAY);
2011 n_days %= 7; /* Weekdays repeat every 7 days. */
2013 while (n_days-- > 0)
2014 weekday = e_weekday_get_next (weekday);
2016 return weekday;
2020 * e_weekday_subtract_days:
2021 * @weekday: a #GDateWeekday
2022 * @n_days: number of days to subtract
2024 * Decrements @weekday by @n_days.
2026 * Returns: a #GDateWeekday
2028 GDateWeekday
2029 e_weekday_subtract_days (GDateWeekday weekday,
2030 guint n_days)
2032 g_return_val_if_fail (
2033 g_date_valid_weekday (weekday),
2034 G_DATE_BAD_WEEKDAY);
2036 n_days %= 7; /* Weekdays repeat every 7 days. */
2038 while (n_days-- > 0)
2039 weekday = e_weekday_get_prev (weekday);
2041 return weekday;
2045 * e_weekday_get_days_between:
2046 * @weekday1: a #GDateWeekday
2047 * @weekday2: a #GDateWeekday
2049 * Counts the number of days starting at @weekday1 and ending at @weekday2.
2051 * Returns: the number of days
2053 guint
2054 e_weekday_get_days_between (GDateWeekday weekday1,
2055 GDateWeekday weekday2)
2057 guint n_days = 0;
2059 g_return_val_if_fail (g_date_valid_weekday (weekday1), 0);
2060 g_return_val_if_fail (g_date_valid_weekday (weekday2), 0);
2062 while (weekday1 != weekday2) {
2063 n_days++;
2064 weekday1 = e_weekday_get_next (weekday1);
2067 return n_days;
2071 * e_weekday_to_tm_wday:
2072 * @weekday: a #GDateWeekday
2074 * Converts a #GDateWeekday to the numbering used in
2075 * <structname>struct tm</structname>.
2077 * Returns: number of days since Sunday (0 - 6)
2079 gint
2080 e_weekday_to_tm_wday (GDateWeekday weekday)
2082 gint tm_wday;
2084 switch (weekday) {
2085 case G_DATE_MONDAY:
2086 tm_wday = 1;
2087 break;
2088 case G_DATE_TUESDAY:
2089 tm_wday = 2;
2090 break;
2091 case G_DATE_WEDNESDAY:
2092 tm_wday = 3;
2093 break;
2094 case G_DATE_THURSDAY:
2095 tm_wday = 4;
2096 break;
2097 case G_DATE_FRIDAY:
2098 tm_wday = 5;
2099 break;
2100 case G_DATE_SATURDAY:
2101 tm_wday = 6;
2102 break;
2103 case G_DATE_SUNDAY:
2104 tm_wday = 0;
2105 break;
2106 default:
2107 g_return_val_if_reached (-1);
2110 return tm_wday;
2114 * e_weekday_from_tm_wday:
2115 * @tm_wday: number of days since Sunday (0 - 6)
2117 * Converts a weekday in the numbering used in
2118 * <structname>struct tm</structname> to a #GDateWeekday.
2120 * Returns: a #GDateWeekday
2122 GDateWeekday
2123 e_weekday_from_tm_wday (gint tm_wday)
2125 GDateWeekday weekday;
2127 switch (tm_wday) {
2128 case 0:
2129 weekday = G_DATE_SUNDAY;
2130 break;
2131 case 1:
2132 weekday = G_DATE_MONDAY;
2133 break;
2134 case 2:
2135 weekday = G_DATE_TUESDAY;
2136 break;
2137 case 3:
2138 weekday = G_DATE_WEDNESDAY;
2139 break;
2140 case 4:
2141 weekday = G_DATE_THURSDAY;
2142 break;
2143 case 5:
2144 weekday = G_DATE_FRIDAY;
2145 break;
2146 case 6:
2147 weekday = G_DATE_SATURDAY;
2148 break;
2149 default:
2150 g_return_val_if_reached (G_DATE_BAD_WEEKDAY);
2153 return weekday;
2156 /* Evolution Locks for crash recovery */
2157 static const gchar *
2158 get_lock_filename (void)
2160 static gchar *filename = NULL;
2162 if (G_UNLIKELY (filename == NULL))
2163 filename = g_build_filename (
2164 e_get_user_config_dir (), ".running", NULL);
2166 return filename;
2169 gboolean
2170 e_file_lock_create (void)
2172 const gchar *filename = get_lock_filename ();
2173 gboolean status = FALSE;
2174 FILE *file;
2176 file = g_fopen (filename, "w");
2177 if (file != NULL) {
2178 /* The lock file also serves as a PID file. */
2179 g_fprintf (
2180 file, "%" G_GINT64_FORMAT "\n",
2181 (gint64) getpid ());
2182 fclose (file);
2183 status = TRUE;
2184 } else {
2185 const gchar *errmsg = g_strerror (errno);
2186 g_warning ("Lock file creation failed: %s", errmsg);
2189 return status;
2192 void
2193 e_file_lock_destroy (void)
2195 const gchar *filename = get_lock_filename ();
2197 if (g_unlink (filename) == -1) {
2198 const gchar *errmsg = g_strerror (errno);
2199 g_warning ("Lock file deletion failed: %s", errmsg);
2203 gboolean
2204 e_file_lock_exists (void)
2206 const gchar *filename = get_lock_filename ();
2208 return g_file_test (filename, G_FILE_TEST_EXISTS);
2211 /* Returns a PID stored in the lock file; 0 if no such file exists. */
2212 GPid
2213 e_file_lock_get_pid (void)
2215 const gchar *filename = get_lock_filename ();
2216 gchar *contents = NULL;
2217 GPid pid = (GPid) 0;
2218 gint64 n_int64;
2220 if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
2221 return pid;
2224 /* Try to extract an integer value from the string. */
2225 n_int64 = g_ascii_strtoll (contents, NULL, 10);
2226 if (n_int64 > 0 && n_int64 < G_MAXINT64) {
2227 /* XXX Probably not portable. */
2228 pid = (GPid) n_int64;
2231 g_free (contents);
2233 return pid;
2237 * e_util_guess_mime_type:
2238 * @filename: a local file name, or URI
2239 * @localfile: %TRUE to check the file content, FALSE to check only the name
2241 * Tries to determine the MIME type for @filename. Free the returned
2242 * string with g_free().
2244 * Returns: the MIME type of @filename, or %NULL if the the MIME type could
2245 * not be determined
2247 gchar *
2248 e_util_guess_mime_type (const gchar *filename,
2249 gboolean localfile)
2251 gchar *mime_type = NULL;
2253 g_return_val_if_fail (filename != NULL, NULL);
2255 if (localfile) {
2256 GFile *file;
2257 GFileInfo *fi;
2259 if (strstr (filename, "://"))
2260 file = g_file_new_for_uri (filename);
2261 else
2262 file = g_file_new_for_path (filename);
2264 fi = g_file_query_info (
2265 file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
2266 G_FILE_QUERY_INFO_NONE, NULL, NULL);
2267 if (fi) {
2268 mime_type = g_content_type_get_mime_type (
2269 g_file_info_get_content_type (fi));
2270 g_object_unref (fi);
2273 g_object_unref (file);
2276 if (!mime_type) {
2277 /* file doesn't exists locally, thus guess based on the filename */
2278 gboolean uncertain = FALSE;
2279 gchar *content_type;
2281 content_type = g_content_type_guess (filename, NULL, 0, &uncertain);
2282 if (content_type) {
2283 mime_type = g_content_type_get_mime_type (content_type);
2284 g_free (content_type);
2288 return mime_type;
2291 GSList *
2292 e_util_get_category_filter_options (void)
2294 GSList *res = NULL;
2295 GList *clist, *l;
2297 clist = e_categories_dup_list ();
2298 for (l = clist; l; l = l->next) {
2299 const gchar *cname = l->data;
2300 struct _filter_option *fo;
2302 if (!e_categories_is_searchable (cname))
2303 continue;
2305 fo = g_new0 (struct _filter_option, 1);
2307 fo->title = g_strdup (cname);
2308 fo->value = g_strdup (cname);
2309 res = g_slist_prepend (res, fo);
2312 g_list_free_full (clist, g_free);
2314 return g_slist_reverse (res);
2318 * e_util_dup_searchable_categories:
2320 * Returns a list of the searchable categories, with each item being a UTF-8
2321 * category name. The list should be freed with g_list_free() when done with it,
2322 * and the items should be freed with g_free(). Everything can be freed at once
2323 * using g_list_free_full().
2325 * Returns: (transfer full) (element-type utf8): a list of searchable category
2326 * names; free with g_list_free_full()
2328 GList *
2329 e_util_dup_searchable_categories (void)
2331 GList *res = NULL, *all_categories, *l;
2333 all_categories = e_categories_dup_list ();
2334 for (l = all_categories; l; l = l->next) {
2335 gchar *cname = l->data;
2337 /* Steal the string from e_categories_dup_list(). */
2338 if (e_categories_is_searchable (cname))
2339 res = g_list_prepend (res, (gpointer) cname);
2340 else
2341 g_free (cname);
2344 /* NOTE: Do *not* free the items. They have been freed or stolen
2345 * above. */
2346 g_list_free (all_categories);
2348 return g_list_reverse (res);
2351 * e_util_get_open_source_job_info:
2352 * @extension_name: an extension name of the source
2353 * @source_display_name: an ESource's display name
2354 * @description: (out) (transfer-full): a description to use
2355 * @alert_ident: (out) (transfer-full): an alert ident to use on failure
2356 * @alert_arg_0: (out) (transfer-full): an alert argument 0 to use on failure
2358 * Populates @desription, @alert_ident and @alert_arg_0 to be used
2359 * to open an #ESource with extension @extension_name. The values
2360 * can be used for functions like e_alert_sink_submit_thread_job().
2362 * If #TRUE is returned, then the caller is responsible to free
2363 * all @desription, @alert_ident and @alert_arg_0 with g_free(),
2364 * when no longer needed.
2366 * Returns: #TRUE, if the values for @desription, @alert_ident and @alert_arg_0
2367 * were set for the given @extension_name; when #FALSE is returned, then
2368 * none of these out variables are changed.
2370 * Since: 3.16
2372 gboolean
2373 e_util_get_open_source_job_info (const gchar *extension_name,
2374 const gchar *source_display_name,
2375 gchar **description,
2376 gchar **alert_ident,
2377 gchar **alert_arg_0)
2379 g_return_val_if_fail (extension_name != NULL, FALSE);
2380 g_return_val_if_fail (source_display_name != NULL, FALSE);
2381 g_return_val_if_fail (description != NULL, FALSE);
2382 g_return_val_if_fail (alert_ident != NULL, FALSE);
2383 g_return_val_if_fail (alert_arg_0 != NULL, FALSE);
2385 if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) {
2386 *alert_ident = g_strdup ("calendar:failed-open-calendar");
2387 *description = g_strdup_printf (_("Opening calendar '%s'"), source_display_name);
2388 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) {
2389 *alert_ident = g_strdup ("calendar:failed-open-memos");
2390 *description = g_strdup_printf (_("Opening memo list '%s'"), source_display_name);
2391 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) {
2392 *alert_ident = g_strdup ("calendar:failed-open-tasks");
2393 *description = g_strdup_printf (_("Opening task list '%s'"), source_display_name);
2394 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) {
2395 *alert_ident = g_strdup ("addressbook:load-error");
2396 *description = g_strdup_printf (_("Opening address book '%s'"), source_display_name);
2397 } else {
2398 return FALSE;
2401 *alert_arg_0 = g_strdup (source_display_name);
2403 return TRUE;
2407 * e_util_propagate_open_source_job_error:
2408 * @job_data: an #EAlertSinkThreadJobData instance
2409 * @extension_name: what extension name had beeing opened
2410 * @local_error: (allow none): a #GError as obtained in a thread job; can be NULL for success
2411 * @error: (allow none): an output #GError, to which propagate the @local_error
2413 * Propagates (and cosumes) the @local_error into the @error, eventually
2414 * changes alert_ident for the @job_data for well-known error codes,
2415 * where is available better error description.
2417 * Since: 3.16
2419 void
2420 e_util_propagate_open_source_job_error (EAlertSinkThreadJobData *job_data,
2421 const gchar *extension_name,
2422 GError *local_error,
2423 GError **error)
2425 const gchar *alert_ident = NULL;
2427 g_return_if_fail (job_data != NULL);
2428 g_return_if_fail (extension_name != NULL);
2430 if (!local_error)
2431 return;
2433 if (!error) {
2434 g_error_free (local_error);
2435 return;
2438 if (g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) {
2439 if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) {
2440 alert_ident = "calendar:prompt-no-contents-offline-calendar";
2441 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) {
2442 alert_ident = "calendar:prompt-no-contents-offline-memos";
2443 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) {
2444 alert_ident = "calendar:prompt-no-contents-offline-tasks";
2445 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) {
2449 if (alert_ident)
2450 e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident);
2452 g_propagate_error (error, local_error);
2455 EClient *
2456 e_util_open_client_sync (EAlertSinkThreadJobData *job_data,
2457 EClientCache *client_cache,
2458 const gchar *extension_name,
2459 ESource *source,
2460 guint32 wait_for_connected_seconds,
2461 GCancellable *cancellable,
2462 GError **error)
2464 gchar *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL;
2465 EClient *client = NULL;
2466 ESourceRegistry *registry;
2467 gchar *display_name;
2468 GError *local_error = NULL;
2470 registry = e_client_cache_ref_registry (client_cache);
2471 display_name = e_util_get_source_full_name (registry, source);
2472 g_clear_object (&registry);
2474 g_warn_if_fail (e_util_get_open_source_job_info (extension_name,
2475 display_name, &description, &alert_ident, &alert_arg_0));
2477 g_free (display_name);
2479 camel_operation_push_message (cancellable, "%s", description);
2481 client = e_client_cache_get_client_sync (client_cache, source, extension_name, wait_for_connected_seconds, cancellable, &local_error);
2483 camel_operation_pop_message (cancellable);
2485 if (!client) {
2486 e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident);
2487 e_alert_sink_thread_job_set_alert_arg_0 (job_data, alert_arg_0);
2489 e_util_propagate_open_source_job_error (job_data, extension_name, local_error, error);
2492 g_free (description);
2493 g_free (alert_ident);
2494 g_free (alert_arg_0);
2496 return client;
2500 * e_binding_transform_color_to_string:
2501 * @binding: a #GBinding
2502 * @source_value: a #GValue of type #GDK_TYPE_COLOR
2503 * @target_value: a #GValue of type #G_TYPE_STRING
2504 * @not_used: not used
2506 * Transforms a #GdkColor value to a color string specification.
2508 * Returns: %TRUE always
2510 gboolean
2511 e_binding_transform_color_to_string (GBinding *binding,
2512 const GValue *source_value,
2513 GValue *target_value,
2514 gpointer not_used)
2516 const GdkColor *color;
2517 gchar *string;
2519 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2521 color = g_value_get_boxed (source_value);
2522 if (!color) {
2523 g_value_set_string (target_value, "");
2524 } else {
2525 /* encode color manually, because css styles expect colors in #rrggbb,
2526 * not in #rrrrggggbbbb, which is a result of gdk_color_to_string()
2528 string = g_strdup_printf (
2529 "#%02x%02x%02x",
2530 (gint) color->red * 256 / 65536,
2531 (gint) color->green * 256 / 65536,
2532 (gint) color->blue * 256 / 65536);
2533 g_value_set_string (target_value, string);
2534 g_free (string);
2537 return TRUE;
2541 * e_binding_transform_string_to_color:
2542 * @binding: a #GBinding
2543 * @source_value: a #GValue of type #G_TYPE_STRING
2544 * @target_value: a #GValue of type #GDK_TYPE_COLOR
2545 * @not_used: not used
2547 * Transforms a color string specification to a #GdkColor.
2549 * Returns: %TRUE if color string specification was valid
2551 gboolean
2552 e_binding_transform_string_to_color (GBinding *binding,
2553 const GValue *source_value,
2554 GValue *target_value,
2555 gpointer not_used)
2557 GdkColor color;
2558 const gchar *string;
2559 gboolean success = FALSE;
2561 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2563 string = g_value_get_string (source_value);
2564 if (gdk_color_parse (string, &color)) {
2565 g_value_set_boxed (target_value, &color);
2566 success = TRUE;
2569 return success;
2573 * e_binding_transform_source_to_uid:
2574 * @binding: a #GBinding
2575 * @source_value: a #GValue of type #E_TYPE_SOURCE
2576 * @target_value: a #GValue of type #G_TYPE_STRING
2577 * @registry: an #ESourceRegistry
2579 * Transforms an #ESource object to its UID string.
2581 * Returns: %TRUE if @source_value was an #ESource object
2583 gboolean
2584 e_binding_transform_source_to_uid (GBinding *binding,
2585 const GValue *source_value,
2586 GValue *target_value,
2587 ESourceRegistry *registry)
2589 ESource *source;
2590 const gchar *string;
2591 gboolean success = FALSE;
2593 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2594 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
2596 source = g_value_get_object (source_value);
2597 if (E_IS_SOURCE (source)) {
2598 string = e_source_get_uid (source);
2599 g_value_set_string (target_value, string);
2600 success = TRUE;
2603 return success;
2607 * e_binding_transform_uid_to_source:
2608 * @binding: a #GBinding
2609 * @source_value: a #GValue of type #G_TYPE_STRING
2610 * @target_value: a #GValue of type #E_TYPE_SOURCe
2611 * @registry: an #ESourceRegistry
2613 * Transforms an #ESource UID string to the corresponding #ESource object
2614 * in @registry.
2616 * Returns: %TRUE if @registry had an #ESource object with a matching
2617 * UID string
2619 gboolean
2620 e_binding_transform_uid_to_source (GBinding *binding,
2621 const GValue *source_value,
2622 GValue *target_value,
2623 ESourceRegistry *registry)
2625 ESource *source;
2626 const gchar *string;
2627 gboolean success = FALSE;
2629 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2630 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
2632 string = g_value_get_string (source_value);
2633 if (string == NULL || *string == '\0')
2634 return FALSE;
2636 source = e_source_registry_ref_source (registry, string);
2637 if (source != NULL) {
2638 g_value_take_object (target_value, source);
2639 success = TRUE;
2642 return success;
2646 * e_binding_transform_text_non_null:
2647 * @binding: a #GBinding
2648 * @source_value: a #GValue of type #G_TYPE_STRING
2649 * @target_value: a #GValue of type #G_TYPE_STRING
2650 * @user_data: custom user data, unused
2652 * Transforms a text value to a text value which is never NULL;
2653 * an empty string is used instead of NULL.
2655 * Returns: %TRUE on success
2657 gboolean
2658 e_binding_transform_text_non_null (GBinding *binding,
2659 const GValue *source_value,
2660 GValue *target_value,
2661 gpointer user_data)
2663 const gchar *str;
2665 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2666 g_return_val_if_fail (source_value != NULL, FALSE);
2667 g_return_val_if_fail (target_value != NULL, FALSE);
2669 str = g_value_get_string (source_value);
2670 if (!str)
2671 str = "";
2673 g_value_set_string (target_value, str);
2675 return TRUE;
2679 * e_binding_bind_object_text_property:
2680 * @source: the source #GObject
2681 * @source_property: the text property on the source to bind
2682 * @target: the target #GObject
2683 * @target_property: the text property on the target to bind
2684 * @flags: flags to pass to e_binding_bind_property_full()
2686 * Installs a new text property object binding, using e_binding_bind_property_full(),
2687 * with transform functions to make sure that a NULL pointer is not
2688 * passed in either way. Instead of NULL an empty string is used.
2690 * Returns: the #GBinding instance representing the binding between the two #GObject instances;
2691 * there applies the same rules to it as for the result of e_binding_bind_property_full().
2693 GBinding *
2694 e_binding_bind_object_text_property (gpointer source,
2695 const gchar *source_property,
2696 gpointer target,
2697 const gchar *target_property,
2698 GBindingFlags flags)
2700 GObjectClass *klass;
2701 GParamSpec *property;
2703 g_return_val_if_fail (G_IS_OBJECT (source), NULL);
2704 g_return_val_if_fail (source_property != NULL, NULL);
2705 g_return_val_if_fail (G_IS_OBJECT (target), NULL);
2706 g_return_val_if_fail (target_property != NULL, NULL);
2708 klass = G_OBJECT_GET_CLASS (source);
2709 property = g_object_class_find_property (klass, source_property);
2710 g_return_val_if_fail (property != NULL, NULL);
2711 g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL);
2713 klass = G_OBJECT_GET_CLASS (target);
2714 property = g_object_class_find_property (klass, target_property);
2715 g_return_val_if_fail (property != NULL, NULL);
2716 g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL);
2718 return e_binding_bind_property_full (source, source_property,
2719 target, target_property,
2720 flags,
2721 e_binding_transform_text_non_null,
2722 e_binding_transform_text_non_null,
2723 NULL, NULL);
2726 typedef struct _EConnectNotifyData {
2727 GConnectFlags flags;
2728 GValue *old_value;
2730 GCallback c_handler;
2731 gpointer user_data;
2732 } EConnectNotifyData;
2734 static EConnectNotifyData *
2735 e_connect_notify_data_new (GCallback c_handler,
2736 gpointer user_data,
2737 guint32 connect_flags)
2739 EConnectNotifyData *connect_data;
2741 connect_data = g_new0 (EConnectNotifyData, 1);
2742 connect_data->flags = connect_flags;
2743 connect_data->c_handler = c_handler;
2744 connect_data->user_data = user_data;
2746 return connect_data;
2749 static void
2750 e_connect_notify_data_free (EConnectNotifyData *data)
2752 if (!data)
2753 return;
2755 if (data->old_value) {
2756 g_value_unset (data->old_value);
2757 g_free (data->old_value);
2759 g_free (data);
2762 static gboolean
2763 e_value_equal (GValue *value1,
2764 GValue *value2)
2766 if (value1 == value2)
2767 return TRUE;
2769 if (!value1 || !value2)
2770 return FALSE;
2772 #define testType(_uc,_lc) G_STMT_START { \
2773 if (G_VALUE_HOLDS_ ## _uc (value1)) \
2774 return g_value_get_ ## _lc (value1) == g_value_get_ ## _lc (value2); \
2775 } G_STMT_END
2777 testType (BOOLEAN, boolean);
2778 testType (BOXED, boxed);
2779 testType (CHAR, schar);
2780 testType (DOUBLE, double);
2781 testType (ENUM, enum);
2782 testType (FLAGS, flags);
2783 testType (FLOAT, float);
2784 testType (GTYPE, gtype);
2785 testType (INT, int);
2786 testType (INT64, int64);
2787 testType (LONG, long);
2788 testType (OBJECT, object);
2789 testType (POINTER, pointer);
2790 testType (UCHAR, uchar);
2791 testType (UINT, uint);
2792 testType (UINT64, uint64);
2793 testType (ULONG, ulong);
2795 #undef testType
2797 if (G_VALUE_HOLDS_PARAM (value1)) {
2798 GParamSpec *param1, *param2;
2800 param1 = g_value_get_param (value1);
2801 param2 = g_value_get_param (value2);
2803 return param1 && param2 &&
2804 g_strcmp0 (param1->name, param2->name) == 0 &&
2805 param1->flags == param2->flags &&
2806 param1->value_type == param2->value_type &&
2807 param1->owner_type == param2->owner_type;
2808 } else if (G_VALUE_HOLDS_STRING (value1)) {
2809 const gchar *string1, *string2;
2811 string1 = g_value_get_string (value1);
2812 string2 = g_value_get_string (value2);
2814 return g_strcmp0 (string1, string2) == 0;
2815 } else if (G_VALUE_HOLDS_VARIANT (value1)) {
2816 GVariant *variant1, *variant2;
2818 variant1 = g_value_get_variant (value1);
2819 variant2 = g_value_get_variant (value2);
2821 return variant1 == variant2 ||
2822 (variant1 && variant2 && g_variant_equal (variant1, variant2));
2825 return FALSE;
2828 static void
2829 e_signal_connect_notify_cb (gpointer instance,
2830 GParamSpec *param,
2831 gpointer user_data)
2833 EConnectNotifyData *connect_data = user_data;
2834 GValue *value;
2836 g_return_if_fail (connect_data != NULL);
2838 value = g_new0 (GValue, 1);
2839 g_value_init (value, param->value_type);
2840 g_object_get_property (instance, param->name, value);
2842 if (!e_value_equal (connect_data->old_value, value)) {
2843 typedef void (* NotifyCBType) (gpointer instance, GParamSpec *param, gpointer user_data);
2844 NotifyCBType c_handler = (NotifyCBType) connect_data->c_handler;
2846 if (connect_data->old_value) {
2847 g_value_unset (connect_data->old_value);
2848 g_free (connect_data->old_value);
2850 connect_data->old_value = value;
2852 if (connect_data->flags == G_CONNECT_SWAPPED) {
2853 c_handler (connect_data->user_data, param, instance);
2854 } else {
2855 c_handler (instance, param, connect_data->user_data);
2857 } else {
2858 g_value_unset (value);
2859 g_free (value);
2864 * e_signal_connect_notify:
2866 * This installs a special handler in front of @c_handler, which will
2867 * call the @c_handler only if the property value changed since the last
2868 * time it was checked. Due to this, these handlers cannot be disconnected
2869 * by by any of the g_signal_handlers_* functions, but only with the returned
2870 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
2871 * to make it easier.
2873 gulong
2874 e_signal_connect_notify (gpointer instance,
2875 const gchar *notify_name,
2876 GCallback c_handler,
2877 gpointer user_data)
2879 EConnectNotifyData *connect_data;
2881 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
2883 connect_data = e_connect_notify_data_new (c_handler, user_data, 0);
2885 return g_signal_connect_data (instance,
2886 notify_name,
2887 G_CALLBACK (e_signal_connect_notify_cb),
2888 connect_data,
2889 (GClosureNotify) e_connect_notify_data_free,
2894 * e_signal_connect_notify_after:
2896 * This installs a special handler in front of @c_handler, which will
2897 * call the @c_handler only if the property value changed since the last
2898 * time it was checked. Due to this, these handlers cannot be disconnected
2899 * by by any of the g_signal_handlers_* functions, but only with the returned
2900 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
2901 * to make it easier.
2903 gulong
2904 e_signal_connect_notify_after (gpointer instance,
2905 const gchar *notify_name,
2906 GCallback c_handler,
2907 gpointer user_data)
2909 EConnectNotifyData *connect_data;
2911 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
2913 connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_AFTER);
2915 return g_signal_connect_data (instance,
2916 notify_name,
2917 G_CALLBACK (e_signal_connect_notify_cb),
2918 connect_data,
2919 (GClosureNotify) e_connect_notify_data_free,
2920 G_CONNECT_AFTER);
2924 * e_signal_connect_notify_swapped:
2926 * This installs a special handler in front of @c_handler, which will
2927 * call the @c_handler only if the property value changed since the last
2928 * time it was checked. Due to this, these handlers cannot be disconnected
2929 * by by any of the g_signal_handlers_* functions, but only with the returned
2930 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
2931 * to make it easier.
2933 gulong
2934 e_signal_connect_notify_swapped (gpointer instance,
2935 const gchar *notify_name,
2936 GCallback c_handler,
2937 gpointer user_data)
2939 EConnectNotifyData *connect_data;
2941 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
2943 connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_SWAPPED);
2945 return g_signal_connect_data (instance,
2946 notify_name,
2947 G_CALLBACK (e_signal_connect_notify_cb),
2948 connect_data,
2949 (GClosureNotify) e_connect_notify_data_free,
2954 * e_signal_connect_notify_object:
2956 * This installs a special handler in front of @c_handler, which will
2957 * call the @c_handler only if the property value changed since the last
2958 * time it was checked. Due to this, these handlers cannot be disconnected
2959 * by by any of the g_signal_handlers_* functions, but only with the returned
2960 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
2961 * to make it easier.
2963 gulong
2964 e_signal_connect_notify_object (gpointer instance,
2965 const gchar *notify_name,
2966 GCallback c_handler,
2967 gpointer gobject,
2968 GConnectFlags connect_flags)
2970 EConnectNotifyData *connect_data;
2971 GClosure *closure;
2973 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
2975 if (!gobject) {
2976 if ((connect_flags & G_CONNECT_SWAPPED) != 0)
2977 return e_signal_connect_notify_swapped (instance, notify_name, c_handler, gobject);
2978 else if ((connect_flags & G_CONNECT_AFTER) != 0)
2979 e_signal_connect_notify_after (instance, notify_name, c_handler, gobject);
2980 else
2981 g_warn_if_fail (connect_flags == 0);
2983 return e_signal_connect_notify (instance, notify_name, c_handler, gobject);
2986 g_return_val_if_fail (G_IS_OBJECT (gobject), 0);
2988 connect_data = e_connect_notify_data_new (c_handler, gobject, connect_flags & G_CONNECT_SWAPPED);
2989 closure = g_cclosure_new (
2990 G_CALLBACK (e_signal_connect_notify_cb),
2991 connect_data,
2992 (GClosureNotify) e_connect_notify_data_free);
2994 g_object_watch_closure (G_OBJECT (gobject), closure);
2996 return g_signal_connect_closure (instance,
2997 notify_name,
2998 closure,
2999 connect_flags & G_CONNECT_AFTER);
3003 * e_signal_disconnect_notify_handler:
3005 * Convenient handler disconnect function to be used with
3006 * returned handler IDs from:
3007 * e_signal_connect_notify()
3008 * e_signal_connect_notify_after()
3009 * e_signal_connect_notify_swapped()
3010 * e_signal_connect_notify_object()
3011 * but not necessarily only with these functions.
3013 void
3014 e_signal_disconnect_notify_handler (gpointer instance,
3015 gulong *handler_id)
3017 g_return_if_fail (instance != NULL);
3018 g_return_if_fail (handler_id != NULL);
3020 if (!*handler_id)
3021 return;
3023 g_signal_handler_disconnect (instance, *handler_id);
3024 *handler_id = 0;
3027 static GMutex settings_hash_lock;
3028 static GHashTable *settings_hash = NULL;
3031 * e_util_ref_settings:
3032 * @schema_id: the id of the schema to reference settings for
3034 * Either returns an existing referenced #GSettings object for the given @schema_id,
3035 * or creates a new one and remembers it for later use, to avoid having too many
3036 * #GSettings objects created for the same @schema_id.
3038 * Returns: A #GSettings for the given @schema_id. The returned #GSettings object
3039 * is referenced, thus free it with g_object_unref() when done with it.
3041 * Since: 3.16
3043 GSettings *
3044 e_util_ref_settings (const gchar *schema_id)
3046 GSettings *settings;
3048 g_return_val_if_fail (schema_id != NULL, NULL);
3049 g_return_val_if_fail (*schema_id, NULL);
3051 g_mutex_lock (&settings_hash_lock);
3053 if (!settings_hash) {
3054 settings_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3057 settings = g_hash_table_lookup (settings_hash, schema_id);
3058 if (!settings) {
3059 settings = g_settings_new (schema_id);
3060 g_hash_table_insert (settings_hash, g_strdup (schema_id), settings);
3063 if (settings)
3064 g_object_ref (settings);
3066 g_mutex_unlock (&settings_hash_lock);
3068 return settings;
3072 * e_util_cleanup_settings:
3074 * Frees all the memory taken by e_util_ref_settings().
3076 * Since: 3.16
3078 void
3079 e_util_cleanup_settings (void)
3081 g_mutex_lock (&settings_hash_lock);
3083 if (settings_hash) {
3084 g_hash_table_destroy (settings_hash);
3085 settings_hash = NULL;
3088 g_mutex_unlock (&settings_hash_lock);
3092 * e_util_prompt_user:
3093 * @parent: parent window
3094 * @settings_schema: name of the settings schema where @promptkey belongs.
3095 * @promptkey: settings key to check if we should prompt the user or not.
3096 * @tag: e_alert tag.
3098 * Convenience function to query the user with a Yes/No dialog and a
3099 * "Do not show this dialog again" checkbox. If the user checks that
3100 * checkbox, then @promptkey is set to %FALSE, otherwise it is set to
3101 * %TRUE.
3103 * Returns %TRUE if the user clicks Yes or %FALSE otherwise.
3105 gboolean
3106 e_util_prompt_user (GtkWindow *parent,
3107 const gchar *settings_schema,
3108 const gchar *promptkey,
3109 const gchar *tag,
3110 ...)
3112 GtkWidget *dialog;
3113 GtkWidget *check = NULL;
3114 GtkWidget *container;
3115 va_list ap;
3116 gint button;
3117 GSettings *settings;
3118 EAlert *alert = NULL;
3120 settings = e_util_ref_settings (settings_schema);
3122 if (promptkey && !g_settings_get_boolean (settings, promptkey)) {
3123 g_object_unref (settings);
3124 return TRUE;
3127 va_start (ap, tag);
3128 alert = e_alert_new_valist (tag, ap);
3129 va_end (ap);
3131 dialog = e_alert_dialog_new (parent, alert);
3132 g_object_unref (alert);
3134 container = e_alert_dialog_get_content_area (E_ALERT_DIALOG (dialog));
3136 if (promptkey) {
3137 check = gtk_check_button_new_with_mnemonic (
3138 _("_Do not show this message again"));
3139 gtk_box_pack_start (
3140 GTK_BOX (container), check, FALSE, FALSE, 0);
3141 gtk_widget_show (check);
3144 button = gtk_dialog_run (GTK_DIALOG (dialog));
3145 if (promptkey)
3146 g_settings_set_boolean (
3147 settings, promptkey,
3148 !gtk_toggle_button_get_active (
3149 GTK_TOGGLE_BUTTON (check)));
3151 gtk_widget_destroy (dialog);
3153 g_object_unref (settings);
3155 return button == GTK_RESPONSE_YES;
3158 typedef struct _EUtilSimpleAsyncResultThreadData {
3159 GSimpleAsyncResult *simple;
3160 GSimpleAsyncThreadFunc func;
3161 GCancellable *cancellable;
3162 } EUtilSimpleAsyncResultThreadData;
3164 static void
3165 e_util_simple_async_result_thread (gpointer data,
3166 gpointer user_data)
3168 EUtilSimpleAsyncResultThreadData *thread_data = data;
3169 GError *error = NULL;
3171 g_return_if_fail (thread_data != NULL);
3172 g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (thread_data->simple));
3173 g_return_if_fail (thread_data->func != NULL);
3175 if (g_cancellable_set_error_if_cancelled (thread_data->cancellable, &error)) {
3176 g_simple_async_result_take_error (thread_data->simple, error);
3177 } else {
3178 thread_data->func (thread_data->simple,
3179 g_async_result_get_source_object (G_ASYNC_RESULT (thread_data->simple)),
3180 thread_data->cancellable);
3183 g_simple_async_result_complete_in_idle (thread_data->simple);
3185 g_clear_object (&thread_data->simple);
3186 g_clear_object (&thread_data->cancellable);
3187 g_free (thread_data);
3191 * e_util_run_simple_async_result_in_thread:
3192 * @simple: a #GSimpleAsyncResult
3193 * @func: a #GSimpleAsyncThreadFunc to execute in the thread
3194 * @cancellable: an optional #GCancellable, or %NULL
3196 * Similar to g_simple_async_result_run_in_thread(), except
3197 * it doesn't use GTask internally, thus doesn't block the GTask
3198 * thread pool with possibly long job.
3200 * It doesn't behave exactly the same as the g_simple_async_result_run_in_thread(),
3201 * the @cancellable checking is not done before the finish.
3203 * Since: 3.18
3205 void
3206 e_util_run_simple_async_result_in_thread (GSimpleAsyncResult *simple,
3207 GSimpleAsyncThreadFunc func,
3208 GCancellable *cancellable)
3210 static GThreadPool *thread_pool = NULL;
3211 static GMutex thread_pool_mutex;
3212 EUtilSimpleAsyncResultThreadData *thread_data;
3214 g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple));
3215 g_return_if_fail (func != NULL);
3217 g_mutex_lock (&thread_pool_mutex);
3219 if (!thread_pool)
3220 thread_pool = g_thread_pool_new (e_util_simple_async_result_thread, NULL, 20, FALSE, NULL);
3222 thread_data = g_new0 (EUtilSimpleAsyncResultThreadData, 1);
3223 thread_data->simple = g_object_ref (simple);
3224 thread_data->func = func;
3225 thread_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
3227 g_thread_pool_push (thread_pool, thread_data, NULL);
3229 g_mutex_unlock (&thread_pool_mutex);
3233 * e_util_is_running_gnome:
3235 * Returns: Whether the current running desktop environment is GNOME.
3237 * Since: 3.18
3239 gboolean
3240 e_util_is_running_gnome (void)
3242 #ifdef G_OS_WIN32
3243 return FALSE;
3244 #else
3245 static gint runs_gnome = -1;
3247 if (runs_gnome == -1) {
3248 runs_gnome = g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "GNOME") == 0 ? 1 : 0;
3249 if (runs_gnome) {
3250 GDesktopAppInfo *app_info;
3252 app_info = g_desktop_app_info_new ("gnome-notifications-panel.desktop");
3253 if (!app_info) {
3254 runs_gnome = 0;
3257 g_clear_object (&app_info);
3261 return runs_gnome != 0;
3262 #endif
3266 * e_util_set_entry_issue_hint:
3267 * @entry: a #GtkEntry
3268 * @hint: (allow none): a hint to set, or %NULL to unset
3270 * Sets a @hint on the secondary @entry icon about an entered value issue,
3271 * or unsets it, when the @hint is %NULL.
3273 * Since: 3.20
3275 void
3276 e_util_set_entry_issue_hint (GtkWidget *entry,
3277 const gchar *hint)
3279 GtkEntry *eentry;
3281 g_return_if_fail (GTK_IS_ENTRY (entry));
3283 eentry = GTK_ENTRY (entry);
3285 if (hint) {
3286 gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
3287 gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, hint);
3288 } else {
3289 gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, NULL);
3290 gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, NULL);