/* * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * * * Authors: * Chris Lahey * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #include "evolution-config.h" #include "e-misc-utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef G_OS_WIN32 #include #else #include #endif #include #include #include #ifdef HAVE_LDAP #include #ifndef SUNLDAP #include #endif #endif /* HAVE_LDAP */ #include "e-alert-dialog.h" #include "e-alert-sink.h" #include "e-client-cache.h" #include "e-filter-option.h" #include "e-mktemp.h" #include "e-util-private.h" #include "e-xml-utils.h" typedef struct _WindowData WindowData; struct _WindowData { GtkWindow *window; GSettings *settings; ERestoreWindowFlags flags; gint premax_width; gint premax_height; guint timeout_id; }; static void window_data_free (WindowData *data) { if (data->settings != NULL) g_object_unref (data->settings); if (data->timeout_id > 0) g_source_remove (data->timeout_id); g_slice_free (WindowData, data); } static gboolean window_update_settings (gpointer user_data) { WindowData *data = user_data; GSettings *settings = data->settings; if (data->flags & E_RESTORE_WINDOW_SIZE) { GdkWindowState state; GdkWindow *window; gboolean maximized; window = gtk_widget_get_window (GTK_WIDGET (data->window)); state = gdk_window_get_state (window); maximized = ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0); g_settings_set_boolean (settings, "maximized", maximized); if (!maximized) { gint width, height; gtk_window_get_size (data->window, &width, &height); g_settings_set_int (settings, "width", width); g_settings_set_int (settings, "height", height); } } if (data->flags & E_RESTORE_WINDOW_POSITION) { gint x, y; gtk_window_get_position (data->window, &x, &y); g_settings_set_int (settings, "x", x); g_settings_set_int (settings, "y", y); } data->timeout_id = 0; return FALSE; } static void window_delayed_update_settings (WindowData *data) { if (data->timeout_id > 0) g_source_remove (data->timeout_id); data->timeout_id = e_named_timeout_add_seconds ( 1, window_update_settings, data); } static gboolean window_configure_event_cb (GtkWindow *window, GdkEventConfigure *event, WindowData *data) { window_delayed_update_settings (data); return FALSE; } static gboolean window_state_event_cb (GtkWindow *window, GdkEventWindowState *event, WindowData *data) { gboolean window_was_unmaximized; if (data->timeout_id > 0) { g_source_remove (data->timeout_id); data->timeout_id = 0; } window_was_unmaximized = ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) && ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0); if (window_was_unmaximized) { gint width, height; width = data->premax_width; data->premax_width = 0; height = data->premax_height; data->premax_height = 0; /* This only applies when the window is initially restored * as maximized and is then unmaximized. GTK+ handles the * unmaximized window size thereafter. */ if (width > 0 && height > 0) gtk_window_resize (window, width, height); } window_delayed_update_settings (data); return FALSE; } static gboolean window_unmap_cb (GtkWindow *window, WindowData *data) { if (data->timeout_id > 0) { g_source_remove (data->timeout_id); data->timeout_id = 0; } /* Reset the flags so the window position and size are not * accidentally reverted to their default value at the next run. */ data->flags = 0; return FALSE; } /** * e_get_accels_filename: * * Returns the name of the user data file containing custom keyboard * accelerator specifications. * * Returns: filename for accelerator specifications **/ const gchar * e_get_accels_filename (void) { static gchar *filename = NULL; if (G_UNLIKELY (filename == NULL)) { const gchar *config_dir = e_get_user_config_dir (); filename = g_build_filename (config_dir, "accels", NULL); } return filename; } /** * e_show_uri: * @parent: a parent #GtkWindow or %NULL * @uri: the URI to show * * Launches the default application to show the given URI. The URI must * be of a form understood by GIO. If the URI cannot be shown, it presents * a dialog describing the error. The dialog is set as transient to @parent * if @parent is non-%NULL. **/ void e_show_uri (GtkWindow *parent, const gchar *uri) { GtkWidget *dialog; GdkScreen *screen = NULL; GError *error = NULL; guint32 timestamp; g_return_if_fail (uri != NULL); timestamp = gtk_get_current_event_time (); if (parent != NULL) screen = gtk_widget_get_screen (GTK_WIDGET (parent)); if (gtk_show_uri (screen, uri, timestamp, &error)) return; dialog = gtk_message_dialog_new_with_markup ( parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("Could not open the link.")); gtk_message_dialog_format_secondary_text ( GTK_MESSAGE_DIALOG (dialog), "%s", error->message); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_error_free (error); } static gboolean e_misc_utils_is_help_package_installed (void) { gboolean is_installed; gchar *path; /* Viewing user documentation requires the evolution help * files. Look for one of the files it installs. */ path = g_build_filename (EVOLUTION_DATADIR, "help", "C", PACKAGE, "index.page", NULL); is_installed = g_file_test (path, G_FILE_TEST_IS_REGULAR); g_free (path); if (is_installed) { GAppInfo *help_handler; help_handler = g_app_info_get_default_for_uri_scheme ("help"); is_installed = help_handler && g_app_info_get_commandline (help_handler); g_clear_object (&help_handler); } return is_installed; } /** * e_display_help: * @parent: a parent #GtkWindow or %NULL * @link_id: help section to present or %NULL * * Opens the user documentation to the section given by @link_id, or to the * table of contents if @link_id is %NULL. If the user documentation cannot * be opened, it presents a dialog describing the error. The dialog is set * as transient to @parent if @parent is non-%NULL. **/ void e_display_help (GtkWindow *parent, const gchar *link_id) { GString *uri; GtkWidget *dialog; GdkScreen *screen = NULL; GError *error = NULL; guint32 timestamp; if (e_misc_utils_is_help_package_installed ()) { uri = g_string_new ("help:" PACKAGE); } else { uri = g_string_new ("https://help.gnome.org/users/" PACKAGE "/"); g_string_append_printf (uri, "%d.%d", EDS_MAJOR_VERSION, EDS_MINOR_VERSION); } timestamp = gtk_get_current_event_time (); if (parent != NULL) screen = gtk_widget_get_screen (GTK_WIDGET (parent)); if (link_id != NULL) { g_string_append (uri, "/"); g_string_append (uri, link_id); } if (gtk_show_uri (screen, uri->str, timestamp, &error)) goto exit; dialog = gtk_message_dialog_new_with_markup ( parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("Could not display help for Evolution.")); gtk_message_dialog_format_secondary_text ( GTK_MESSAGE_DIALOG (dialog), "%s", error->message); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); g_error_free (error); exit: g_string_free (uri, TRUE); } /** * e_restore_window: * @window: a #GtkWindow * @settings_path: a #GSettings path * @flags: flags indicating which window features to restore * * This function can restore one of or both a window's size and position * using #GSettings keys at @settings_path which conform to the relocatable * schema "org.gnome.evolution.window". * * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's * previously recorded size and maximize state. * * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to * the previously recorded screen coordinates. * * The respective #GSettings values will be updated when the window is * resized and/or moved. **/ void e_restore_window (GtkWindow *window, const gchar *settings_path, ERestoreWindowFlags flags) { WindowData *data; GSettings *settings; const gchar *schema; g_return_if_fail (GTK_IS_WINDOW (window)); g_return_if_fail (settings_path != NULL); schema = "org.gnome.evolution.window"; settings = g_settings_new_with_path (schema, settings_path); data = g_slice_new0 (WindowData); data->window = window; data->settings = g_object_ref (settings); data->flags = flags; if (flags & E_RESTORE_WINDOW_SIZE) { gint width, height; width = g_settings_get_int (settings, "width"); height = g_settings_get_int (settings, "height"); if (width > 0 && height > 0) gtk_window_resize (window, width, height); if (g_settings_get_boolean (settings, "maximized")) { GdkScreen *screen; GdkRectangle monitor_area; gint x, y, monitor; x = g_settings_get_int (settings, "x"); y = g_settings_get_int (settings, "y"); screen = gtk_window_get_screen (window); gtk_window_get_size (window, &width, &height); data->premax_width = width; data->premax_height = height; monitor = gdk_screen_get_monitor_at_point (screen, x, y); if (monitor < 0) monitor = 0; if (monitor >= gdk_screen_get_n_monitors (screen)) monitor = 0; gdk_screen_get_monitor_workarea ( screen, monitor, &monitor_area); gtk_window_resize ( window, monitor_area.width, monitor_area.height); gtk_window_maximize (window); } } if (flags & E_RESTORE_WINDOW_POSITION) { gint x, y; x = g_settings_get_int (settings, "x"); y = g_settings_get_int (settings, "y"); gtk_window_move (window, x, y); } g_object_set_data_full ( G_OBJECT (window), "e-util-window-data", data, (GDestroyNotify) window_data_free); g_signal_connect ( window, "configure-event", G_CALLBACK (window_configure_event_cb), data); g_signal_connect ( window, "window-state-event", G_CALLBACK (window_state_event_cb), data); g_signal_connect ( window, "unmap", G_CALLBACK (window_unmap_cb), data); g_object_unref (settings); } /** * e_lookup_action: * @ui_manager: a #GtkUIManager * @action_name: the name of an action * * Returns the first #GtkAction named @action_name by traversing the * list of action groups in @ui_manager. If no such action exists, the * function emits a critical warning before returning %NULL, since this * probably indicates a programming error and most code is not prepared * to deal with lookup failures. * * Returns: the first #GtkAction named @action_name **/ GtkAction * e_lookup_action (GtkUIManager *ui_manager, const gchar *action_name) { GtkAction *action = NULL; GList *iter; g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL); g_return_val_if_fail (action_name != NULL, NULL); iter = gtk_ui_manager_get_action_groups (ui_manager); while (iter != NULL) { GtkActionGroup *action_group = iter->data; action = gtk_action_group_get_action ( action_group, action_name); if (action != NULL) return action; iter = g_list_next (iter); } g_critical ("%s: action '%s' not found", G_STRFUNC, action_name); return NULL; } /** * e_lookup_action_group: * @ui_manager: a #GtkUIManager * @group_name: the name of an action group * * Returns the #GtkActionGroup in @ui_manager named @group_name. If no * such action group exists, the function emits a critical warnings before * returning %NULL, since this probably indicates a programming error and * most code is not prepared to deal with lookup failures. * * Returns: the #GtkActionGroup named @group_name **/ GtkActionGroup * e_lookup_action_group (GtkUIManager *ui_manager, const gchar *group_name) { GList *iter; g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL); g_return_val_if_fail (group_name != NULL, NULL); iter = gtk_ui_manager_get_action_groups (ui_manager); while (iter != NULL) { GtkActionGroup *action_group = iter->data; const gchar *name; name = gtk_action_group_get_name (action_group); if (strcmp (name, group_name) == 0) return action_group; iter = g_list_next (iter); } g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name); return NULL; } /** * e_action_compare_by_label: * @action1: a #GtkAction * @action2: a #GtkAction * * Compares the labels for @action1 and @action2 using g_utf8_collate(). * * Returns: < 0 if @action1 compares before @action2, 0 if they * compare equal, > 0 if @action1 compares after @action2 **/ gint e_action_compare_by_label (GtkAction *action1, GtkAction *action2) { gchar *label1; gchar *label2; gint result; /* XXX This is horribly inefficient but will generally only be * used on short lists of actions during UI construction. */ if (action1 == action2) return 0; g_object_get (action1, "label", &label1, NULL); g_object_get (action2, "label", &label2, NULL); result = g_utf8_collate (label1, label2); g_free (label1); g_free (label2); return result; } /** * e_action_group_remove_all_actions: * @action_group: a #GtkActionGroup * * Removes all actions from the action group. **/ void e_action_group_remove_all_actions (GtkActionGroup *action_group) { GList *list, *iter; /* XXX I've proposed this function for inclusion in GTK+. * GtkActionGroup stores actions in an internal hash * table and can do this more efficiently by calling * g_hash_table_remove_all(). * * http://bugzilla.gnome.org/show_bug.cgi?id=550485 */ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); list = gtk_action_group_list_actions (action_group); for (iter = list; iter != NULL; iter = iter->next) gtk_action_group_remove_action (action_group, iter->data); g_list_free (list); } /** * e_radio_action_get_current_action: * @radio_action: a #GtkRadioAction * * Returns the currently active member of the group to which @radio_action * belongs. * * Returns: the currently active group member **/ GtkRadioAction * e_radio_action_get_current_action (GtkRadioAction *radio_action) { GSList *group; gint current_value; g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL); group = gtk_radio_action_get_group (radio_action); current_value = gtk_radio_action_get_current_value (radio_action); while (group != NULL) { gint value; radio_action = GTK_RADIO_ACTION (group->data); g_object_get (radio_action, "value", &value, NULL); if (value == current_value) return radio_action; group = g_slist_next (group); } return NULL; } /** * e_action_group_add_actions_localized: * @action_group: a #GtkActionGroup to add @entries to * @translation_domain: a translation domain to use * to translate label and tooltip strings in @entries * @entries: (array length=n_entries): an array of action descriptions * @n_entries: the number of entries * @user_data: data to pass to the action callbacks * * Adds #GtkAction-s defined by @entries to @action_group, with action's * label and tooltip localized in the given translation domain, instead * of the domain set on the @action_group. * * Since: 3.4 **/ void e_action_group_add_actions_localized (GtkActionGroup *action_group, const gchar *translation_domain, const GtkActionEntry *entries, guint n_entries, gpointer user_data) { GtkActionGroup *tmp_group; GList *list, *iter; gint ii; g_return_if_fail (action_group != NULL); g_return_if_fail (entries != NULL); g_return_if_fail (n_entries > 0); g_return_if_fail (translation_domain != NULL); g_return_if_fail (*translation_domain); tmp_group = gtk_action_group_new ("temporary-group"); gtk_action_group_set_translation_domain (tmp_group, translation_domain); gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data); list = gtk_action_group_list_actions (tmp_group); for (iter = list; iter != NULL; iter = iter->next) { GtkAction *action = GTK_ACTION (iter->data); const gchar *action_name; g_object_ref (action); action_name = gtk_action_get_name (action); for (ii = 0; ii < n_entries; ii++) { if (g_strcmp0 (entries[ii].name, action_name) == 0) { gtk_action_group_remove_action ( tmp_group, action); gtk_action_group_add_action_with_accel ( action_group, action, entries[ii].accelerator); break; } } g_object_unref (action); } g_list_free (list); g_object_unref (tmp_group); } /** * e_builder_get_widget: * @builder: a #GtkBuilder * @widget_name: name of a widget in @builder * * Gets the widget named @widget_name. Note that this function does not * increment the reference count of the returned widget. If @widget_name * could not be found in the @builder's object tree, a run-time * warning is emitted since this usually indicates a programming error. * * This is a convenience function to work around the awkwardness of * #GtkBuilder returning #GObject pointers, when the vast majority of * the time you want a #GtkWidget pointer. * * If you need something from @builder other than a #GtkWidget, or you * want to test for the existence of some widget name without incurring * a run-time warning, use gtk_builder_get_object(). * * Returns: the widget named @widget_name, or %NULL **/ GtkWidget * e_builder_get_widget (GtkBuilder *builder, const gchar *widget_name) { GObject *object; g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL); g_return_val_if_fail (widget_name != NULL, NULL); object = gtk_builder_get_object (builder, widget_name); if (object == NULL) { g_warning ("Could not find widget '%s'", widget_name); return NULL; } return GTK_WIDGET (object); } /** * e_load_ui_builder_definition: * @builder: a #GtkBuilder * @basename: basename of the UI definition file * * Loads a UI definition into @builder from Evolution's UI directory. * Failure here is fatal, since the application can't function without * its UI definitions. **/ void e_load_ui_builder_definition (GtkBuilder *builder, const gchar *basename) { gchar *filename; GError *error = NULL; g_return_if_fail (GTK_IS_BUILDER (builder)); g_return_if_fail (basename != NULL); filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL); gtk_builder_add_from_file (builder, filename, &error); g_free (filename); if (error != NULL) { g_error ("%s: %s", basename, error->message); g_warn_if_reached (); } } static gdouble e_get_ui_manager_definition_file_version (const gchar *filename) { xmlDocPtr doc; xmlNode *root; gdouble version = -1.0; g_return_val_if_fail (filename != NULL, version); doc = e_xml_parse_file (filename); if (!doc) return version; root = xmlDocGetRootElement (doc); if (root && g_strcmp0 ((const gchar *) root->name, "ui") == 0) { version = e_xml_get_double_prop_by_name_with_default (root, (const xmlChar *) "evolution-ui-version", -1.0); } xmlFreeDoc (doc); return version; } static gchar * e_pick_ui_manager_definition_file (const gchar *basename) { gchar *system_filename, *user_filename; gdouble system_version, user_version; g_return_val_if_fail (basename != NULL, NULL); system_filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL); user_filename = g_build_filename (e_get_user_config_dir (), "ui", basename, NULL); if (!g_file_test (user_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { g_free (user_filename); return system_filename; } user_version = e_get_ui_manager_definition_file_version (user_filename); system_version = e_get_ui_manager_definition_file_version (system_filename); /* Versions are equal and the system version is a positive number */ if (user_version - system_version >= -1e-9 && user_version - system_version <= 1e-9 && system_version > 1e-9) { g_free (system_filename); return user_filename; } g_warning ("User's UI file '%s' version (%.1f) doesn't match expected version (%.1f), skipping it. Either correct the version or remove the file.", user_filename, user_version, system_version); g_free (user_filename); return system_filename; } /** * e_load_ui_manager_definition: * @ui_manager: a #GtkUIManager * @basename: basename of the UI definition file * * Loads a UI definition into @ui_manager from Evolution's UI directory. * Failure here is fatal, since the application can't function without * its UI definitions. * * Returns: The merge ID for the merged UI. The merge ID can be used to * unmerge the UI with gtk_ui_manager_remove_ui(). **/ guint e_load_ui_manager_definition (GtkUIManager *ui_manager, const gchar *basename) { gchar *filename; guint merge_id; GError *error = NULL; g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), 0); g_return_val_if_fail (basename != NULL, 0); filename = e_pick_ui_manager_definition_file (basename); merge_id = gtk_ui_manager_add_ui_from_file ( ui_manager, filename, &error); g_free (filename); if (error != NULL) { g_error ("%s: %s", basename, error->message); g_warn_if_reached (); } return merge_id; } /* Helper for e_categories_add_change_hook() */ static void categories_changed_cb (GObject *useless_opaque_object, GHookList *hook_list) { /* e_categories_register_change_listener() is broken because * it requires callbacks to allow for some opaque GObject as * the first argument (not does it document this). */ g_hook_list_invoke (hook_list, FALSE); } /* Helper for e_categories_add_change_hook() */ static void categories_weak_notify_cb (GHookList *hook_list, gpointer where_the_object_was) { GHook *hook; /* This should not happen, but if we fail to find the hook for * some reason, g_hook_destroy_link() will warn about the NULL * pointer, which is all we would do anyway so no need to test * for it ourselves. */ hook = g_hook_find_data (hook_list, TRUE, where_the_object_was); g_hook_destroy_link (hook_list, hook); } /** * e_categories_add_change_hook: * @func: a hook function * @object: a #GObject to be passed to @func, or %NULL * * A saner alternative to e_categories_register_change_listener(). * * Adds a hook function to be called when a category is added, removed or * modified. If @object is not %NULL, the hook function is automatically * removed when @object is finalized. **/ void e_categories_add_change_hook (GHookFunc func, gpointer object) { static gboolean initialized = FALSE; static GHookList hook_list; GHook *hook; g_return_if_fail (func != NULL); if (object != NULL) g_return_if_fail (G_IS_OBJECT (object)); if (!initialized) { g_hook_list_init (&hook_list, sizeof (GHook)); e_categories_register_change_listener ( G_CALLBACK (categories_changed_cb), &hook_list); initialized = TRUE; } hook = g_hook_alloc (&hook_list); hook->func = func; hook->data = object; if (object != NULL) g_object_weak_ref ( G_OBJECT (object), (GWeakNotify) categories_weak_notify_cb, &hook_list); g_hook_append (&hook_list, hook); } /** * e_flexible_strtod: * @nptr: the string to convert to a numeric value. * @endptr: if non-NULL, it returns the character after * the last character used in the conversion. * * Converts a string to a gdouble value. This function detects * strings either in the standard C locale or in the current locale. * * This function is typically used when reading configuration files or * other non-user input that should not be locale dependent, but may * have been in the past. To handle input from the user you should * normally use the locale-sensitive system strtod function. * * To convert from a double to a string in a locale-insensitive way, use * @g_ascii_dtostr. * * Returns: the gdouble value **/ gdouble e_flexible_strtod (const gchar *nptr, gchar **endptr) { gchar *fail_pos; gdouble val; struct lconv *locale_data; const gchar *decimal_point; gint decimal_point_len; const gchar *p, *decimal_point_pos; const gchar *end = NULL; /* Silence gcc */ gchar *copy, *c; g_return_val_if_fail (nptr != NULL, 0); fail_pos = NULL; locale_data = localeconv (); decimal_point = locale_data->decimal_point; decimal_point_len = strlen (decimal_point); g_return_val_if_fail (decimal_point_len != 0, 0); decimal_point_pos = NULL; if (!strcmp (decimal_point, ".")) return strtod (nptr, endptr); p = nptr; /* Skip leading space */ while (isspace ((guchar) * p)) p++; /* Skip leading optional sign */ if (*p == '+' || *p == '-') p++; if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { p += 2; /* HEX - find the (optional) decimal point */ while (isxdigit ((guchar) * p)) p++; if (*p == '.') { decimal_point_pos = p++; while (isxdigit ((guchar) * p)) p++; if (*p == 'p' || *p == 'P') p++; if (*p == '+' || *p == '-') p++; while (isdigit ((guchar) * p)) p++; end = p; } else if (strncmp (p, decimal_point, decimal_point_len) == 0) { return strtod (nptr, endptr); } } else { while (isdigit ((guchar) * p)) p++; if (*p == '.') { decimal_point_pos = p++; while (isdigit ((guchar) * p)) p++; if (*p == 'e' || *p == 'E') p++; if (*p == '+' || *p == '-') p++; while (isdigit ((guchar) * p)) p++; end = p; } else if (strncmp (p, decimal_point, decimal_point_len) == 0) { return strtod (nptr, endptr); } } /* For the other cases, we need not convert the decimal point */ if (!decimal_point_pos) return strtod (nptr, endptr); /* We need to convert the '.' to the locale specific decimal point */ copy = g_malloc (end - nptr + 1 + decimal_point_len); c = copy; memcpy (c, nptr, decimal_point_pos - nptr); c += decimal_point_pos - nptr; memcpy (c, decimal_point, decimal_point_len); c += decimal_point_len; memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1)); c += end - (decimal_point_pos + 1); *c = 0; val = strtod (copy, &fail_pos); if (fail_pos) { if (fail_pos > decimal_point_pos) fail_pos = (gchar *) nptr + (fail_pos - copy) - (decimal_point_len - 1); else fail_pos = (gchar *) nptr + (fail_pos - copy); } g_free (copy); if (endptr) *endptr = fail_pos; return val; } /** * e_ascii_dtostr: * @buffer: A buffer to place the resulting string in * @buf_len: The length of the buffer. * @format: The printf-style format to use for the * code to use for converting. * @d: The double to convert * * Converts a double to a string, using the '.' as * decimal_point. To format the number you pass in * a printf-style formating string. Allowed conversion * specifiers are eEfFgG. * * If you want to generates enough precision that converting * the string back using @g_strtod gives the same machine-number * (on machines with IEEE compatible 64bit doubles) use the format * string "%.17g". If you do this it is guaranteed that the size * of the resulting string will never be larger than * @G_ASCII_DTOSTR_BUF_SIZE bytes. * * Returns: the pointer to the buffer with the converted string **/ gchar * e_ascii_dtostr (gchar *buffer, gint buf_len, const gchar *format, gdouble d) { struct lconv *locale_data; const gchar *decimal_point; gint decimal_point_len; gchar *p; gint rest_len; gchar format_char; g_return_val_if_fail (buffer != NULL, NULL); g_return_val_if_fail (format[0] == '%', NULL); g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL); format_char = format[strlen (format) - 1]; g_return_val_if_fail (format_char == 'e' || format_char == 'E' || format_char == 'f' || format_char == 'F' || format_char == 'g' || format_char == 'G', NULL); if (format[0] != '%') return NULL; if (strpbrk (format + 1, "'l%")) return NULL; if (!(format_char == 'e' || format_char == 'E' || format_char == 'f' || format_char == 'F' || format_char == 'g' || format_char == 'G')) return NULL; g_snprintf (buffer, buf_len, format, d); locale_data = localeconv (); decimal_point = locale_data->decimal_point; decimal_point_len = strlen (decimal_point); g_return_val_if_fail (decimal_point_len != 0, NULL); if (strcmp (decimal_point, ".")) { p = buffer; if (*p == '+' || *p == '-') p++; while (isdigit ((guchar) * p)) p++; if (strncmp (p, decimal_point, decimal_point_len) == 0) { *p = '.'; p++; if (decimal_point_len > 1) { rest_len = strlen (p + (decimal_point_len - 1)); memmove ( p, p + (decimal_point_len - 1), rest_len); p[rest_len] = 0; } } } return buffer; } /** * e_str_without_underscores: * @string: the string to strip underscores from * * Strips underscores from a string in the same way * @gtk_label_new_with_mnemonics does. The returned string should be freed * using g_free(). * * Returns: a newly-allocated string without underscores */ gchar * e_str_without_underscores (const gchar *string) { gchar *new_string; const gchar *sp; gchar *dp; new_string = g_malloc (strlen (string) + 1); dp = new_string; for (sp = string; *sp != '\0'; sp++) { if (*sp != '_') { *dp = *sp; dp++; } else if (sp[1] == '_') { /* Translate "__" in "_". */ *dp = '_'; dp++; sp++; } } *dp = 0; return new_string; } /** * e_str_replace_string * @text: the string to replace * @before: the string to be replaced * @after: the string to replaced with * * Replaces every occurrence of the string @before with the string @after in * the string @text and returns a #GString with result that should be freed * with g_string_free(). * * Returns: a newly-allocated #GString */ GString * e_str_replace_string (const gchar *text, const gchar *before, const gchar *after) { const gchar *p, *next; GString *str; gint find_len; g_return_val_if_fail (text != NULL, NULL); g_return_val_if_fail (before != NULL, NULL); g_return_val_if_fail (*before, NULL); find_len = strlen (before); str = g_string_new (""); p = text; while (next = strstr (p, before), next) { if (p < next) g_string_append_len (str, p, next - p); if (after && *after) g_string_append (str, after); p = next + find_len; } g_string_append (str, p); return str; } gint e_str_compare (gconstpointer x, gconstpointer y) { if (x == NULL || y == NULL) { if (x == y) return 0; else return x ? -1 : 1; } return strcmp (x, y); } gint e_str_case_compare (gconstpointer x, gconstpointer y) { gchar *cx, *cy; gint res; if (x == NULL || y == NULL) { if (x == y) return 0; else return x ? -1 : 1; } cx = g_utf8_casefold (x, -1); cy = g_utf8_casefold (y, -1); res = g_utf8_collate (cx, cy); g_free (cx); g_free (cy); return res; } gint e_collate_compare (gconstpointer x, gconstpointer y) { if (x == NULL || y == NULL) { if (x == y) return 0; else return x ? -1 : 1; } return g_utf8_collate (x, y); } gint e_int_compare (gconstpointer x, gconstpointer y) { gint nx = GPOINTER_TO_INT (x); gint ny = GPOINTER_TO_INT (y); return (nx == ny) ? 0 : (nx < ny) ? -1 : 1; } /** * e_color_to_value: * @color: a #GdkColor * * Converts a #GdkColor to a 24-bit RGB color value. * * Returns: a 24-bit color value **/ guint32 e_color_to_value (const GdkColor *color) { GdkRGBA rgba; g_return_val_if_fail (color != NULL, 0); rgba.red = color->red / 65535.0; rgba.green = color->green / 65535.0; rgba.blue = color->blue / 65535.0; rgba.alpha = 0.0; return e_rgba_to_value (&rgba); } /** * e_rgba_to_value: * @rgba: a #GdkRGBA * * Converts #GdkRGBA to a 24-bit RGB color value * * Returns: a 24-bit color value **/ guint32 e_rgba_to_value (const GdkRGBA *rgba) { guint16 red; guint16 green; guint16 blue; g_return_val_if_fail (rgba != NULL, 0); red = 255 * rgba->red; green = 255 * rgba->green; blue = 255 * rgba->blue; return (guint32) ((((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF)) & 0xffffff); } /** * e_rgba_to_color: * @rgba: a source #GdkRGBA * @color: a destination #GdkColor * * Converts @rgba into @color, but loses the alpha channel from @rgba. **/ void e_rgba_to_color (const GdkRGBA *rgba, GdkColor *color) { g_return_if_fail (rgba != NULL); g_return_if_fail (color != NULL); color->pixel = 0; color->red = rgba->red * 65535.0; color->green = rgba->green * 65535.0; color->blue = rgba->blue * 65535.0; } /** * e_utils_get_theme_color: * @widget: a #GtkWidget instance * @color_names: comma-separated theme color names * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse() * @rgba: where to store the read color * * Reads named theme color from a #GtkStyleContext of @widget. * The @color_names are read one after another from left to right, * the next are meant as fallbacks, in case the theme doesn't * define the previous color. If none is found then the @fallback_color_ident * is set to @rgba. **/ void e_utils_get_theme_color (GtkWidget *widget, const gchar *color_names, const gchar *fallback_color_ident, GdkRGBA *rgba) { GtkStyleContext *style_context; gchar **names; gint ii; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (color_names != NULL); g_return_if_fail (fallback_color_ident != NULL); g_return_if_fail (rgba != NULL); style_context = gtk_widget_get_style_context (widget); names = g_strsplit (color_names, ",", -1); for (ii = 0; names && names[ii]; ii++) { if (gtk_style_context_lookup_color (style_context, names[ii], rgba)) { g_strfreev (names); return; } } g_strfreev (names); g_warn_if_fail (gdk_rgba_parse (rgba, fallback_color_ident)); } /** * e_utils_get_theme_color_color: * @widget: a #GtkWidget instance * @color_names: comma-separated theme color names * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse() * @color: where to store the read color * * The same as e_utils_get_theme_color(), only populates #GdkColor, * instead of #GdkRGBA. **/ void e_utils_get_theme_color_color (GtkWidget *widget, const gchar *color_names, const gchar *fallback_color_ident, GdkColor *color) { GdkRGBA rgba; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (color_names != NULL); g_return_if_fail (fallback_color_ident != NULL); g_return_if_fail (color != NULL); e_utils_get_theme_color (widget, color_names, fallback_color_ident, &rgba); e_rgba_to_color (&rgba, color); } /* This is copied from gtk+ sources */ static void rgb_to_hls (gdouble *r, gdouble *g, gdouble *b) { gdouble min; gdouble max; gdouble red; gdouble green; gdouble blue; gdouble h, l, s; gdouble delta; red = *r; green = *g; blue = *b; if (red > green) { if (red > blue) max = red; else max = blue; if (green < blue) min = green; else min = blue; } else { if (green > blue) max = green; else max = blue; if (red < blue) min = red; else min = blue; } l = (max + min) / 2; s = 0; h = 0; if (max != min) { if (l <= 0.5) s = (max - min) / (max + min); else s = (max - min) / (2 - max - min); delta = max -min; if (red == max) h = (green - blue) / delta; else if (green == max) h = 2 + (blue - red) / delta; else if (blue == max) h = 4 + (red - green) / delta; h *= 60; if (h < 0.0) h += 360; } *r = h; *g = l; *b = s; } /* This is copied from gtk+ sources */ static void hls_to_rgb (gdouble *h, gdouble *l, gdouble *s) { gdouble hue; gdouble lightness; gdouble saturation; gdouble m1, m2; gdouble r, g, b; lightness = *l; saturation = *s; if (lightness <= 0.5) m2 = lightness * (1 + saturation); else m2 = lightness + saturation - lightness * saturation; m1 = 2 * lightness - m2; if (saturation == 0) { *h = lightness; *l = lightness; *s = lightness; } else { hue = *h + 120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) r = m1 + (m2 - m1) * hue / 60; else if (hue < 180) r = m2; else if (hue < 240) r = m1 + (m2 - m1) * (240 - hue) / 60; else r = m1; hue = *h; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) g = m1 + (m2 - m1) * hue / 60; else if (hue < 180) g = m2; else if (hue < 240) g = m1 + (m2 - m1) * (240 - hue) / 60; else g = m1; hue = *h - 120; while (hue > 360) hue -= 360; while (hue < 0) hue += 360; if (hue < 60) b = m1 + (m2 - m1) * hue / 60; else if (hue < 180) b = m2; else if (hue < 240) b = m1 + (m2 - m1) * (240 - hue) / 60; else b = m1; *h = r; *l = g; *s = b; } } /* This is copied from gtk+ sources */ void e_utils_shade_color (const GdkRGBA *a, GdkRGBA *b, gdouble mult) { gdouble red; gdouble green; gdouble blue; g_return_if_fail (a != NULL); g_return_if_fail (b != NULL); red = a->red; green = a->green; blue = a->blue; rgb_to_hls (&red, &green, &blue); green *= mult; if (green > 1.0) green = 1.0; else if (green < 0.0) green = 0.0; blue *= mult; if (blue > 1.0) blue = 1.0; else if (blue < 0.0) blue = 0.0; hls_to_rgb (&red, &green, &blue); b->red = red; b->green = green; b->blue = blue; b->alpha = a->alpha; } static gint epow10 (gint number) { gint value = 1; while (number-- > 0) value *= 10; return value; } gchar * e_format_number (gint number) { GList *iterator, *list = NULL; struct lconv *locality; gint char_length = 0; gint group_count = 0; gchar *grouping; gint last_count = 3; gint divider; gchar *value; gchar *value_iterator; locality = localeconv (); grouping = locality->grouping; while (number) { gchar *group; switch (*grouping) { default: last_count = *grouping; grouping++; /* coverity[fallthrough] */ case 0: divider = epow10 (last_count); if (number >= divider) { group = g_strdup_printf ( "%0*d", last_count, number % divider); } else { group = g_strdup_printf ( "%d", number % divider); } number /= divider; break; case CHAR_MAX: group = g_strdup_printf ("%d", number); number = 0; break; } char_length += strlen (group); list = g_list_prepend (list, group); group_count++; } if (list) { value = g_new ( gchar, 1 + char_length + (group_count - 1) * strlen (locality->thousands_sep)); iterator = list; value_iterator = value; strcpy (value_iterator, iterator->data); value_iterator += strlen (iterator->data); for (iterator = iterator->next; iterator; iterator = iterator->next) { strcpy (value_iterator, locality->thousands_sep); value_iterator += strlen (locality->thousands_sep); strcpy (value_iterator, iterator->data); value_iterator += strlen (iterator->data); } g_list_foreach (list, (GFunc) g_free, NULL); g_list_free (list); return value; } else { return g_strdup ("0"); } } /* Perform a binary search for key in base which has nmemb elements * of size bytes each. The comparisons are done by (*compare)(). */ void e_bsearch (gconstpointer key, gconstpointer base, gsize nmemb, gsize size, ESortCompareFunc compare, gpointer closure, gsize *start, gsize *end) { gsize l, u, idx; gconstpointer p; gint comparison; if (!(start || end)) return; l = 0; u = nmemb; while (l < u) { idx = (l + u) / 2; p = (((const gchar *) base) + (idx * size)); comparison = (*compare) (key, p, closure); if (comparison < 0) u = idx; else if (comparison > 0) l = idx + 1; else { gsize lsave, usave; lsave = l; usave = u; if (start) { while (l < u) { idx = (l + u) / 2; p = (((const gchar *) base) + (idx * size)); comparison = (*compare) (key, p, closure); if (comparison <= 0) u = idx; else l = idx + 1; } *start = l; l = lsave; u = usave; } if (end) { while (l < u) { idx = (l + u) / 2; p = (((const gchar *) base) + (idx * size)); comparison = (*compare) (key, p, closure); if (comparison < 0) u = idx; else l = idx + 1; } *end = l; } return; } } if (start) *start = l; if (end) *end = l; } /* Function to do a last minute fixup of the AM/PM stuff if the locale * and gettext haven't done it right. Most English speaking countries * except the USA use the 24 hour clock (UK, Australia etc). However * since they are English nobody bothers to write a language * translation (gettext) file. So the locale turns off the AM/PM, but * gettext does not turn on the 24 hour clock. Leaving a mess. * * This routine checks if AM/PM are defined in the locale, if not it * forces the use of the 24 hour clock. * * The function itself is a front end on strftime and takes exactly * the same arguments. * * TODO: Actually remove the '%p' from the fixed up string so that * there isn't a stray space. */ gsize e_strftime_fix_am_pm (gchar *str, gsize max, const gchar *fmt, const struct tm *tm) { gchar buf[10]; gchar *sp; gchar *ffmt; gsize ret; if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) { /* No AM/PM involved - can use the fmt string directly */ ret = e_strftime (str, max, fmt, tm); } else { /* Get the AM/PM symbol from the locale */ e_strftime (buf, 10, "%p", tm); if (buf[0]) { /* AM/PM have been defined in the locale * so we can use the fmt string directly. */ ret = e_strftime (str, max, fmt, tm); } else { /* No AM/PM defined by locale * must change to 24 hour clock. */ ffmt = g_strdup (fmt); for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) { /* Maybe this should be 'k', but I have never * seen a 24 clock actually use that format. */ sp[1]='H'; } for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) { sp[1]='H'; } ret = e_strftime (str, max, ffmt, tm); g_free (ffmt); } } return (ret); } gsize e_utf8_strftime_fix_am_pm (gchar *str, gsize max, const gchar *fmt, const struct tm *tm) { gsize sz, ret; gchar *locale_fmt, *buf; locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL); if (!locale_fmt) return 0; ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm); if (!ret) { g_free (locale_fmt); return 0; } buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL); if (!buf) { g_free (locale_fmt); return 0; } if (sz >= max) { gchar *tmp = buf + max - 1; tmp = g_utf8_find_prev_char (buf, tmp); if (tmp) sz = tmp - buf; else sz = 0; } memcpy (str, buf, sz); str[sz] = '\0'; g_free (locale_fmt); g_free (buf); return sz; } /** * e_utf8_strftime_match_lc_messages: * @string: The string to store the result in. * @max: The size of the @string. * @fmt: The formatting to use on @tm. * @tm: The time value to format. * * The UTF-8 equivalent of e_strftime (), which also * makes sure that the locale used for time and date * formatting matches the locale used by the * application so that, for example, the quoted * message header produced by the mail composer in a * reply uses only one locale (i.e. LC_MESSAGES, where * available, overrides LC_TIME for consistency). * * Returns: The number of characters placed in @string. * * Since: 3.22 **/ gsize e_utf8_strftime_match_lc_messages (gchar *string, gsize max, const gchar *fmt, const struct tm *tm) { gsize ret; #if defined(LC_MESSAGES) && defined(LC_TIME) gchar *ctime, *cmessages, *saved_locale; /* Use LC_MESSAGES instead of LC_TIME for time * formatting (for consistency). */ ctime = setlocale (LC_TIME, NULL); saved_locale = g_strdup (ctime); g_return_val_if_fail (saved_locale != NULL, 0); cmessages = setlocale (LC_MESSAGES, NULL); setlocale (LC_TIME, cmessages); #endif ret = e_utf8_strftime(string, max, fmt, tm); #if defined(LC_MESSAGES) && defined(LC_TIME) /* Restore LC_TIME, if it has been changed to match * LC_MESSAGES. */ setlocale (LC_TIME, saved_locale); g_free (saved_locale); #endif return ret; } /** * e_get_month_name: * @month: month index * @abbreviated: if %TRUE, abbreviate the month name * * Returns the localized name for @month. If @abbreviated is %TRUE, * returns the locale's abbreviated month name. * * Returns: localized month name **/ const gchar * e_get_month_name (GDateMonth month, gboolean abbreviated) { /* Make the indices correspond to the enum values. */ static const gchar *abbr_names[G_DATE_DECEMBER + 1]; static const gchar *full_names[G_DATE_DECEMBER + 1]; static gboolean first_time = TRUE; g_return_val_if_fail (month >= G_DATE_JANUARY, NULL); g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL); if (G_UNLIKELY (first_time)) { gchar buffer[256]; GDateMonth ii; GDate date; memset (abbr_names, 0, sizeof (abbr_names)); memset (full_names, 0, sizeof (full_names)); /* First Julian day was in January. */ g_date_set_julian (&date, 1); for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) { g_date_strftime (buffer, sizeof (buffer), "%b", &date); abbr_names[ii] = g_intern_string (buffer); g_date_strftime (buffer, sizeof (buffer), "%B", &date); full_names[ii] = g_intern_string (buffer); g_date_add_months (&date, 1); } first_time = FALSE; } return abbreviated ? abbr_names[month] : full_names[month]; } /** * e_get_weekday_name: * @weekday: weekday index * @abbreviated: if %TRUE, abbreviate the weekday name * * Returns the localized name for @weekday. If @abbreviated is %TRUE, * returns the locale's abbreviated weekday name. * * Returns: localized weekday name **/ const gchar * e_get_weekday_name (GDateWeekday weekday, gboolean abbreviated) { /* Make the indices correspond to the enum values. */ static const gchar *abbr_names[G_DATE_SUNDAY + 1]; static const gchar *full_names[G_DATE_SUNDAY + 1]; static gboolean first_time = TRUE; g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL); g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL); if (G_UNLIKELY (first_time)) { gchar buffer[256]; GDateWeekday ii; GDate date; memset (abbr_names, 0, sizeof (abbr_names)); memset (full_names, 0, sizeof (full_names)); /* First Julian day was a Monday. */ g_date_set_julian (&date, 1); for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) { g_date_strftime (buffer, sizeof (buffer), "%a", &date); abbr_names[ii] = g_intern_string (buffer); g_date_strftime (buffer, sizeof (buffer), "%A", &date); full_names[ii] = g_intern_string (buffer); g_date_add_days (&date, 1); } first_time = FALSE; } return abbreviated ? abbr_names[weekday] : full_names[weekday]; } /** * e_weekday_get_next: * @weekday: a #GDateWeekday * * Returns the #GDateWeekday after @weekday. * * Returns: the day after @weekday **/ GDateWeekday e_weekday_get_next (GDateWeekday weekday) { GDateWeekday next; /* Verbose for readability. */ switch (weekday) { case G_DATE_MONDAY: next = G_DATE_TUESDAY; break; case G_DATE_TUESDAY: next = G_DATE_WEDNESDAY; break; case G_DATE_WEDNESDAY: next = G_DATE_THURSDAY; break; case G_DATE_THURSDAY: next = G_DATE_FRIDAY; break; case G_DATE_FRIDAY: next = G_DATE_SATURDAY; break; case G_DATE_SATURDAY: next = G_DATE_SUNDAY; break; case G_DATE_SUNDAY: next = G_DATE_MONDAY; break; default: next = G_DATE_BAD_WEEKDAY; break; } return next; } /** * e_weekday_get_prev: * @weekday: a #GDateWeekday * * Returns the #GDateWeekday before @weekday. * * Returns: the day before @weekday **/ GDateWeekday e_weekday_get_prev (GDateWeekday weekday) { GDateWeekday prev; /* Verbose for readability. */ switch (weekday) { case G_DATE_MONDAY: prev = G_DATE_SUNDAY; break; case G_DATE_TUESDAY: prev = G_DATE_MONDAY; break; case G_DATE_WEDNESDAY: prev = G_DATE_TUESDAY; break; case G_DATE_THURSDAY: prev = G_DATE_WEDNESDAY; break; case G_DATE_FRIDAY: prev = G_DATE_THURSDAY; break; case G_DATE_SATURDAY: prev = G_DATE_FRIDAY; break; case G_DATE_SUNDAY: prev = G_DATE_SATURDAY; break; default: prev = G_DATE_BAD_WEEKDAY; break; } return prev; } /** * e_weekday_add_days: * @weekday: a #GDateWeekday * @n_days: number of days to add * * Increments @weekday by @n_days. * * Returns: a #GDateWeekday **/ GDateWeekday e_weekday_add_days (GDateWeekday weekday, guint n_days) { g_return_val_if_fail ( g_date_valid_weekday (weekday), G_DATE_BAD_WEEKDAY); n_days %= 7; /* Weekdays repeat every 7 days. */ while (n_days-- > 0) weekday = e_weekday_get_next (weekday); return weekday; } /** * e_weekday_subtract_days: * @weekday: a #GDateWeekday * @n_days: number of days to subtract * * Decrements @weekday by @n_days. * * Returns: a #GDateWeekday **/ GDateWeekday e_weekday_subtract_days (GDateWeekday weekday, guint n_days) { g_return_val_if_fail ( g_date_valid_weekday (weekday), G_DATE_BAD_WEEKDAY); n_days %= 7; /* Weekdays repeat every 7 days. */ while (n_days-- > 0) weekday = e_weekday_get_prev (weekday); return weekday; } /** * e_weekday_get_days_between: * @weekday1: a #GDateWeekday * @weekday2: a #GDateWeekday * * Counts the number of days starting at @weekday1 and ending at @weekday2. * * Returns: the number of days **/ guint e_weekday_get_days_between (GDateWeekday weekday1, GDateWeekday weekday2) { guint n_days = 0; g_return_val_if_fail (g_date_valid_weekday (weekday1), 0); g_return_val_if_fail (g_date_valid_weekday (weekday2), 0); while (weekday1 != weekday2) { n_days++; weekday1 = e_weekday_get_next (weekday1); } return n_days; } /** * e_weekday_to_tm_wday: * @weekday: a #GDateWeekday * * Converts a #GDateWeekday to the numbering used in * struct tm. * * Returns: number of days since Sunday (0 - 6) **/ gint e_weekday_to_tm_wday (GDateWeekday weekday) { gint tm_wday; switch (weekday) { case G_DATE_MONDAY: tm_wday = 1; break; case G_DATE_TUESDAY: tm_wday = 2; break; case G_DATE_WEDNESDAY: tm_wday = 3; break; case G_DATE_THURSDAY: tm_wday = 4; break; case G_DATE_FRIDAY: tm_wday = 5; break; case G_DATE_SATURDAY: tm_wday = 6; break; case G_DATE_SUNDAY: tm_wday = 0; break; default: g_return_val_if_reached (-1); } return tm_wday; } /** * e_weekday_from_tm_wday: * @tm_wday: number of days since Sunday (0 - 6) * * Converts a weekday in the numbering used in * struct tm to a #GDateWeekday. * * Returns: a #GDateWeekday **/ GDateWeekday e_weekday_from_tm_wday (gint tm_wday) { GDateWeekday weekday; switch (tm_wday) { case 0: weekday = G_DATE_SUNDAY; break; case 1: weekday = G_DATE_MONDAY; break; case 2: weekday = G_DATE_TUESDAY; break; case 3: weekday = G_DATE_WEDNESDAY; break; case 4: weekday = G_DATE_THURSDAY; break; case 5: weekday = G_DATE_FRIDAY; break; case 6: weekday = G_DATE_SATURDAY; break; default: g_return_val_if_reached (G_DATE_BAD_WEEKDAY); } return weekday; } /* Evolution Locks for crash recovery */ static const gchar * get_lock_filename (void) { static gchar *filename = NULL; if (G_UNLIKELY (filename == NULL)) filename = g_build_filename ( e_get_user_config_dir (), ".running", NULL); return filename; } gboolean e_file_lock_create (void) { const gchar *filename = get_lock_filename (); gboolean status = FALSE; FILE *file; file = g_fopen (filename, "w"); if (file != NULL) { /* The lock file also serves as a PID file. */ g_fprintf ( file, "%" G_GINT64_FORMAT "\n", (gint64) getpid ()); fclose (file); status = TRUE; } else { const gchar *errmsg = g_strerror (errno); g_warning ("Lock file creation failed: %s", errmsg); } return status; } void e_file_lock_destroy (void) { const gchar *filename = get_lock_filename (); if (g_unlink (filename) == -1) { const gchar *errmsg = g_strerror (errno); g_warning ("Lock file deletion failed: %s", errmsg); } } gboolean e_file_lock_exists (void) { const gchar *filename = get_lock_filename (); return g_file_test (filename, G_FILE_TEST_EXISTS); } /* Returns a PID stored in the lock file; 0 if no such file exists. */ GPid e_file_lock_get_pid (void) { const gchar *filename = get_lock_filename (); gchar *contents = NULL; GPid pid = (GPid) 0; gint64 n_int64; if (!g_file_get_contents (filename, &contents, NULL, NULL)) { return pid; } /* Try to extract an integer value from the string. */ n_int64 = g_ascii_strtoll (contents, NULL, 10); if (n_int64 > 0 && n_int64 < G_MAXINT64) { /* XXX Probably not portable. */ pid = (GPid) n_int64; } g_free (contents); return pid; } /** * e_util_guess_mime_type: * @filename: a local file name, or URI * @localfile: %TRUE to check the file content, FALSE to check only the name * * Tries to determine the MIME type for @filename. Free the returned * string with g_free(). * * Returns: the MIME type of @filename, or %NULL if the the MIME type could * not be determined **/ gchar * e_util_guess_mime_type (const gchar *filename, gboolean localfile) { gchar *mime_type = NULL; g_return_val_if_fail (filename != NULL, NULL); if (localfile) { GFile *file; GFileInfo *fi; if (strstr (filename, "://")) file = g_file_new_for_uri (filename); else file = g_file_new_for_path (filename); fi = g_file_query_info ( file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (fi) { mime_type = g_content_type_get_mime_type ( g_file_info_get_content_type (fi)); g_object_unref (fi); } g_object_unref (file); } if (!mime_type) { /* file doesn't exists locally, thus guess based on the filename */ gboolean uncertain = FALSE; gchar *content_type; content_type = g_content_type_guess (filename, NULL, 0, &uncertain); if (content_type) { mime_type = g_content_type_get_mime_type (content_type); g_free (content_type); } } return mime_type; } GSList * e_util_get_category_filter_options (void) { GSList *res = NULL; GList *clist, *l; clist = e_categories_dup_list (); for (l = clist; l; l = l->next) { const gchar *cname = l->data; struct _filter_option *fo; if (!e_categories_is_searchable (cname)) continue; fo = g_new0 (struct _filter_option, 1); fo->title = g_strdup (cname); fo->value = g_strdup (cname); res = g_slist_prepend (res, fo); } g_list_free_full (clist, g_free); return g_slist_reverse (res); } /** * e_util_dup_searchable_categories: * * Returns a list of the searchable categories, with each item being a UTF-8 * category name. The list should be freed with g_list_free() when done with it, * and the items should be freed with g_free(). Everything can be freed at once * using g_list_free_full(). * * Returns: (transfer full) (element-type utf8): a list of searchable category * names; free with g_list_free_full() */ GList * e_util_dup_searchable_categories (void) { GList *res = NULL, *all_categories, *l; all_categories = e_categories_dup_list (); for (l = all_categories; l; l = l->next) { gchar *cname = l->data; /* Steal the string from e_categories_dup_list(). */ if (e_categories_is_searchable (cname)) res = g_list_prepend (res, (gpointer) cname); else g_free (cname); } /* NOTE: Do *not* free the items. They have been freed or stolen * above. */ g_list_free (all_categories); return g_list_reverse (res); } /** * e_util_get_open_source_job_info: * @extension_name: an extension name of the source * @source_display_name: an ESource's display name * @description: (out) (transfer-full): a description to use * @alert_ident: (out) (transfer-full): an alert ident to use on failure * @alert_arg_0: (out) (transfer-full): an alert argument 0 to use on failure * * Populates @desription, @alert_ident and @alert_arg_0 to be used * to open an #ESource with extension @extension_name. The values * can be used for functions like e_alert_sink_submit_thread_job(). * * If #TRUE is returned, then the caller is responsible to free * all @desription, @alert_ident and @alert_arg_0 with g_free(), * when no longer needed. * * Returns: #TRUE, if the values for @desription, @alert_ident and @alert_arg_0 * were set for the given @extension_name; when #FALSE is returned, then * none of these out variables are changed. * * Since: 3.16 **/ gboolean e_util_get_open_source_job_info (const gchar *extension_name, const gchar *source_display_name, gchar **description, gchar **alert_ident, gchar **alert_arg_0) { g_return_val_if_fail (extension_name != NULL, FALSE); g_return_val_if_fail (source_display_name != NULL, FALSE); g_return_val_if_fail (description != NULL, FALSE); g_return_val_if_fail (alert_ident != NULL, FALSE); g_return_val_if_fail (alert_arg_0 != NULL, FALSE); if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) { *alert_ident = g_strdup ("calendar:failed-open-calendar"); *description = g_strdup_printf (_("Opening calendar “%s”"), source_display_name); } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) { *alert_ident = g_strdup ("calendar:failed-open-memos"); *description = g_strdup_printf (_("Opening memo list “%s”"), source_display_name); } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) { *alert_ident = g_strdup ("calendar:failed-open-tasks"); *description = g_strdup_printf (_("Opening task list “%s”"), source_display_name); } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) { *alert_ident = g_strdup ("addressbook:load-error"); *description = g_strdup_printf (_("Opening address book “%s”"), source_display_name); } else { return FALSE; } *alert_arg_0 = g_strdup (source_display_name); return TRUE; } /** * e_util_propagate_open_source_job_error: * @job_data: an #EAlertSinkThreadJobData instance * @extension_name: what extension name had beeing opened * @local_error: (allow none): a #GError as obtained in a thread job; can be NULL for success * @error: (allow none): an output #GError, to which propagate the @local_error * * Propagates (and cosumes) the @local_error into the @error, eventually * changes alert_ident for the @job_data for well-known error codes, * where is available better error description. * * Since: 3.16 **/ void e_util_propagate_open_source_job_error (EAlertSinkThreadJobData *job_data, const gchar *extension_name, GError *local_error, GError **error) { const gchar *alert_ident = NULL; g_return_if_fail (job_data != NULL); g_return_if_fail (extension_name != NULL); if (!local_error) return; if (!error) { g_error_free (local_error); return; } if (g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) { if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) { alert_ident = "calendar:prompt-no-contents-offline-calendar"; } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) { alert_ident = "calendar:prompt-no-contents-offline-memos"; } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) { alert_ident = "calendar:prompt-no-contents-offline-tasks"; } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) { } } if (alert_ident) e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident); g_propagate_error (error, local_error); } EClient * e_util_open_client_sync (EAlertSinkThreadJobData *job_data, EClientCache *client_cache, const gchar *extension_name, ESource *source, guint32 wait_for_connected_seconds, GCancellable *cancellable, GError **error) { gchar *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL; EClient *client = NULL; ESourceRegistry *registry; gchar *display_name; GError *local_error = NULL; registry = e_client_cache_ref_registry (client_cache); display_name = e_util_get_source_full_name (registry, source); g_clear_object (®istry); g_warn_if_fail (e_util_get_open_source_job_info (extension_name, display_name, &description, &alert_ident, &alert_arg_0)); g_free (display_name); camel_operation_push_message (cancellable, "%s", description); client = e_client_cache_get_client_sync (client_cache, source, extension_name, wait_for_connected_seconds, cancellable, &local_error); camel_operation_pop_message (cancellable); if (!client) { e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident); e_alert_sink_thread_job_set_alert_arg_0 (job_data, alert_arg_0); e_util_propagate_open_source_job_error (job_data, extension_name, local_error, error); } g_free (description); g_free (alert_ident); g_free (alert_arg_0); return client; } /** * e_binding_transform_color_to_string: * @binding: a #GBinding * @source_value: a #GValue of type #GDK_TYPE_COLOR * @target_value: a #GValue of type #G_TYPE_STRING * @not_used: not used * * Transforms a #GdkColor value to a color string specification. * * Returns: %TRUE always **/ gboolean e_binding_transform_color_to_string (GBinding *binding, const GValue *source_value, GValue *target_value, gpointer not_used) { const GdkColor *color; gchar *string; g_return_val_if_fail (G_IS_BINDING (binding), FALSE); color = g_value_get_boxed (source_value); if (!color) { g_value_set_string (target_value, ""); } else { /* encode color manually, because css styles expect colors in #rrggbb, * not in #rrrrggggbbbb, which is a result of gdk_color_to_string() */ string = g_strdup_printf ( "#%02x%02x%02x", (gint) color->red * 256 / 65536, (gint) color->green * 256 / 65536, (gint) color->blue * 256 / 65536); g_value_set_string (target_value, string); g_free (string); } return TRUE; } /** * e_binding_transform_string_to_color: * @binding: a #GBinding * @source_value: a #GValue of type #G_TYPE_STRING * @target_value: a #GValue of type #GDK_TYPE_COLOR * @not_used: not used * * Transforms a color string specification to a #GdkColor. * * Returns: %TRUE if color string specification was valid **/ gboolean e_binding_transform_string_to_color (GBinding *binding, const GValue *source_value, GValue *target_value, gpointer not_used) { GdkColor color; const gchar *string; gboolean success = FALSE; g_return_val_if_fail (G_IS_BINDING (binding), FALSE); string = g_value_get_string (source_value); if (gdk_color_parse (string, &color)) { g_value_set_boxed (target_value, &color); success = TRUE; } return success; } /** * e_binding_transform_source_to_uid: * @binding: a #GBinding * @source_value: a #GValue of type #E_TYPE_SOURCE * @target_value: a #GValue of type #G_TYPE_STRING * @registry: an #ESourceRegistry * * Transforms an #ESource object to its UID string. * * Returns: %TRUE if @source_value was an #ESource object **/ gboolean e_binding_transform_source_to_uid (GBinding *binding, const GValue *source_value, GValue *target_value, ESourceRegistry *registry) { ESource *source; const gchar *string; gboolean success = FALSE; g_return_val_if_fail (G_IS_BINDING (binding), FALSE); g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); source = g_value_get_object (source_value); if (E_IS_SOURCE (source)) { string = e_source_get_uid (source); g_value_set_string (target_value, string); success = TRUE; } return success; } /** * e_binding_transform_uid_to_source: * @binding: a #GBinding * @source_value: a #GValue of type #G_TYPE_STRING * @target_value: a #GValue of type #E_TYPE_SOURCe * @registry: an #ESourceRegistry * * Transforms an #ESource UID string to the corresponding #ESource object * in @registry. * * Returns: %TRUE if @registry had an #ESource object with a matching * UID string **/ gboolean e_binding_transform_uid_to_source (GBinding *binding, const GValue *source_value, GValue *target_value, ESourceRegistry *registry) { ESource *source; const gchar *string; gboolean success = FALSE; g_return_val_if_fail (G_IS_BINDING (binding), FALSE); g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); string = g_value_get_string (source_value); if (string == NULL || *string == '\0') return FALSE; source = e_source_registry_ref_source (registry, string); if (source != NULL) { g_value_take_object (target_value, source); success = TRUE; } return success; } /** * e_binding_transform_text_non_null: * @binding: a #GBinding * @source_value: a #GValue of type #G_TYPE_STRING * @target_value: a #GValue of type #G_TYPE_STRING * @user_data: custom user data, unused * * Transforms a text value to a text value which is never NULL; * an empty string is used instead of NULL. * * Returns: %TRUE on success **/ gboolean e_binding_transform_text_non_null (GBinding *binding, const GValue *source_value, GValue *target_value, gpointer user_data) { const gchar *str; g_return_val_if_fail (G_IS_BINDING (binding), FALSE); g_return_val_if_fail (source_value != NULL, FALSE); g_return_val_if_fail (target_value != NULL, FALSE); str = g_value_get_string (source_value); if (!str) str = ""; g_value_set_string (target_value, str); return TRUE; } /** * e_binding_bind_object_text_property: * @source: the source #GObject * @source_property: the text property on the source to bind * @target: the target #GObject * @target_property: the text property on the target to bind * @flags: flags to pass to e_binding_bind_property_full() * * Installs a new text property object binding, using e_binding_bind_property_full(), * with transform functions to make sure that a NULL pointer is not * passed in either way. Instead of NULL an empty string is used. * * Returns: the #GBinding instance representing the binding between the two #GObject instances; * there applies the same rules to it as for the result of e_binding_bind_property_full(). **/ GBinding * e_binding_bind_object_text_property (gpointer source, const gchar *source_property, gpointer target, const gchar *target_property, GBindingFlags flags) { GObjectClass *klass; GParamSpec *property; g_return_val_if_fail (G_IS_OBJECT (source), NULL); g_return_val_if_fail (source_property != NULL, NULL); g_return_val_if_fail (G_IS_OBJECT (target), NULL); g_return_val_if_fail (target_property != NULL, NULL); klass = G_OBJECT_GET_CLASS (source); property = g_object_class_find_property (klass, source_property); g_return_val_if_fail (property != NULL, NULL); g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL); klass = G_OBJECT_GET_CLASS (target); property = g_object_class_find_property (klass, target_property); g_return_val_if_fail (property != NULL, NULL); g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL); return e_binding_bind_property_full (source, source_property, target, target_property, flags, e_binding_transform_text_non_null, e_binding_transform_text_non_null, NULL, NULL); } typedef struct _EConnectNotifyData { GConnectFlags flags; GValue *old_value; GCallback c_handler; gpointer user_data; } EConnectNotifyData; static EConnectNotifyData * e_connect_notify_data_new (GCallback c_handler, gpointer user_data, guint32 connect_flags) { EConnectNotifyData *connect_data; connect_data = g_new0 (EConnectNotifyData, 1); connect_data->flags = connect_flags; connect_data->c_handler = c_handler; connect_data->user_data = user_data; return connect_data; } static void e_connect_notify_data_free (EConnectNotifyData *data) { if (!data) return; if (data->old_value) { g_value_unset (data->old_value); g_free (data->old_value); } g_free (data); } static gboolean e_value_equal (GValue *value1, GValue *value2) { if (value1 == value2) return TRUE; if (!value1 || !value2) return FALSE; #define testType(_uc,_lc) G_STMT_START { \ if (G_VALUE_HOLDS_ ## _uc (value1)) \ return g_value_get_ ## _lc (value1) == g_value_get_ ## _lc (value2); \ } G_STMT_END testType (BOOLEAN, boolean); testType (BOXED, boxed); testType (CHAR, schar); testType (DOUBLE, double); testType (ENUM, enum); testType (FLAGS, flags); testType (FLOAT, float); testType (GTYPE, gtype); testType (INT, int); testType (INT64, int64); testType (LONG, long); testType (OBJECT, object); testType (POINTER, pointer); testType (UCHAR, uchar); testType (UINT, uint); testType (UINT64, uint64); testType (ULONG, ulong); #undef testType if (G_VALUE_HOLDS_PARAM (value1)) { GParamSpec *param1, *param2; param1 = g_value_get_param (value1); param2 = g_value_get_param (value2); return param1 && param2 && g_strcmp0 (param1->name, param2->name) == 0 && param1->flags == param2->flags && param1->value_type == param2->value_type && param1->owner_type == param2->owner_type; } else if (G_VALUE_HOLDS_STRING (value1)) { const gchar *string1, *string2; string1 = g_value_get_string (value1); string2 = g_value_get_string (value2); return g_strcmp0 (string1, string2) == 0; } else if (G_VALUE_HOLDS_VARIANT (value1)) { GVariant *variant1, *variant2; variant1 = g_value_get_variant (value1); variant2 = g_value_get_variant (value2); return variant1 == variant2 || (variant1 && variant2 && g_variant_equal (variant1, variant2)); } return FALSE; } static void e_signal_connect_notify_cb (gpointer instance, GParamSpec *param, gpointer user_data) { EConnectNotifyData *connect_data = user_data; GValue *value; g_return_if_fail (connect_data != NULL); value = g_new0 (GValue, 1); g_value_init (value, param->value_type); g_object_get_property (instance, param->name, value); if (!e_value_equal (connect_data->old_value, value)) { typedef void (* NotifyCBType) (gpointer instance, GParamSpec *param, gpointer user_data); NotifyCBType c_handler = (NotifyCBType) connect_data->c_handler; if (connect_data->old_value) { g_value_unset (connect_data->old_value); g_free (connect_data->old_value); } connect_data->old_value = value; if (connect_data->flags == G_CONNECT_SWAPPED) { c_handler (connect_data->user_data, param, instance); } else { c_handler (instance, param, connect_data->user_data); } } else { g_value_unset (value); g_free (value); } } /** * e_signal_connect_notify: * * This installs a special handler in front of @c_handler, which will * call the @c_handler only if the property value changed since the last * time it was checked. Due to this, these handlers cannot be disconnected * by by any of the g_signal_handlers_* functions, but only with the returned * handler ID. A convenient e_signal_disconnect_notify_handler() was added * to make it easier. **/ gulong e_signal_connect_notify (gpointer instance, const gchar *notify_name, GCallback c_handler, gpointer user_data) { EConnectNotifyData *connect_data; g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0); connect_data = e_connect_notify_data_new (c_handler, user_data, 0); return g_signal_connect_data (instance, notify_name, G_CALLBACK (e_signal_connect_notify_cb), connect_data, (GClosureNotify) e_connect_notify_data_free, 0); } /** * e_signal_connect_notify_after: * * This installs a special handler in front of @c_handler, which will * call the @c_handler only if the property value changed since the last * time it was checked. Due to this, these handlers cannot be disconnected * by by any of the g_signal_handlers_* functions, but only with the returned * handler ID. A convenient e_signal_disconnect_notify_handler() was added * to make it easier. **/ gulong e_signal_connect_notify_after (gpointer instance, const gchar *notify_name, GCallback c_handler, gpointer user_data) { EConnectNotifyData *connect_data; g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0); connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_AFTER); return g_signal_connect_data (instance, notify_name, G_CALLBACK (e_signal_connect_notify_cb), connect_data, (GClosureNotify) e_connect_notify_data_free, G_CONNECT_AFTER); } /** * e_signal_connect_notify_swapped: * * This installs a special handler in front of @c_handler, which will * call the @c_handler only if the property value changed since the last * time it was checked. Due to this, these handlers cannot be disconnected * by by any of the g_signal_handlers_* functions, but only with the returned * handler ID. A convenient e_signal_disconnect_notify_handler() was added * to make it easier. **/ gulong e_signal_connect_notify_swapped (gpointer instance, const gchar *notify_name, GCallback c_handler, gpointer user_data) { EConnectNotifyData *connect_data; g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0); connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_SWAPPED); return g_signal_connect_data (instance, notify_name, G_CALLBACK (e_signal_connect_notify_cb), connect_data, (GClosureNotify) e_connect_notify_data_free, 0); } /** * e_signal_connect_notify_object: * * This installs a special handler in front of @c_handler, which will * call the @c_handler only if the property value changed since the last * time it was checked. Due to this, these handlers cannot be disconnected * by by any of the g_signal_handlers_* functions, but only with the returned * handler ID. A convenient e_signal_disconnect_notify_handler() was added * to make it easier. **/ gulong e_signal_connect_notify_object (gpointer instance, const gchar *notify_name, GCallback c_handler, gpointer gobject, GConnectFlags connect_flags) { EConnectNotifyData *connect_data; GClosure *closure; g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0); if (!gobject) { if ((connect_flags & G_CONNECT_SWAPPED) != 0) return e_signal_connect_notify_swapped (instance, notify_name, c_handler, gobject); else if ((connect_flags & G_CONNECT_AFTER) != 0) e_signal_connect_notify_after (instance, notify_name, c_handler, gobject); else g_warn_if_fail (connect_flags == 0); return e_signal_connect_notify (instance, notify_name, c_handler, gobject); } g_return_val_if_fail (G_IS_OBJECT (gobject), 0); connect_data = e_connect_notify_data_new (c_handler, gobject, connect_flags & G_CONNECT_SWAPPED); closure = g_cclosure_new ( G_CALLBACK (e_signal_connect_notify_cb), connect_data, (GClosureNotify) e_connect_notify_data_free); g_object_watch_closure (G_OBJECT (gobject), closure); return g_signal_connect_closure (instance, notify_name, closure, connect_flags & G_CONNECT_AFTER); } /** * e_signal_disconnect_notify_handler: * * Convenient handler disconnect function to be used with * returned handler IDs from: * e_signal_connect_notify() * e_signal_connect_notify_after() * e_signal_connect_notify_swapped() * e_signal_connect_notify_object() * but not necessarily only with these functions. **/ void e_signal_disconnect_notify_handler (gpointer instance, gulong *handler_id) { g_return_if_fail (instance != NULL); g_return_if_fail (handler_id != NULL); if (!*handler_id) return; g_signal_handler_disconnect (instance, *handler_id); *handler_id = 0; } static GMutex settings_hash_lock; static GHashTable *settings_hash = NULL; /** * e_util_ref_settings: * @schema_id: the id of the schema to reference settings for * * Either returns an existing referenced #GSettings object for the given @schema_id, * or creates a new one and remembers it for later use, to avoid having too many * #GSettings objects created for the same @schema_id. * * Returns: A #GSettings for the given @schema_id. The returned #GSettings object * is referenced, thus free it with g_object_unref() when done with it. * * Since: 3.16 **/ GSettings * e_util_ref_settings (const gchar *schema_id) { GSettings *settings; g_return_val_if_fail (schema_id != NULL, NULL); g_return_val_if_fail (*schema_id, NULL); g_mutex_lock (&settings_hash_lock); if (!settings_hash) { settings_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); } settings = g_hash_table_lookup (settings_hash, schema_id); if (!settings) { settings = g_settings_new (schema_id); g_hash_table_insert (settings_hash, g_strdup (schema_id), settings); } if (settings) g_object_ref (settings); g_mutex_unlock (&settings_hash_lock); return settings; } /** * e_util_cleanup_settings: * * Frees all the memory taken by e_util_ref_settings(). * * Since: 3.16 **/ void e_util_cleanup_settings (void) { g_mutex_lock (&settings_hash_lock); if (settings_hash) { g_hash_table_destroy (settings_hash); settings_hash = NULL; } g_mutex_unlock (&settings_hash_lock); } static gdouble get_screen_dpi (GdkScreen *screen) { gdouble dpi; gdouble dp, di; dpi = gdk_screen_get_resolution (screen); if (dpi != -1) return dpi; dp = hypot (gdk_screen_get_width (screen), gdk_screen_get_height (screen)); di = hypot (gdk_screen_get_width_mm (screen), gdk_screen_get_height_mm (screen)) / 25.4; return dp / di; } guint e_util_normalize_font_size (GtkWidget *widget, gdouble font_size) { /* WebKit2 uses font sizes in pixels. */ GdkScreen *screen; gdouble dpi; if (widget) { screen = gtk_widget_has_screen (widget) ? gtk_widget_get_screen (widget) : gdk_screen_get_default (); } else { screen = gdk_screen_get_default (); } dpi = screen ? get_screen_dpi (screen) : 96; return font_size / 72.0 * dpi; } /** * e_util_prompt_user: * @parent: parent window * @settings_schema: name of the settings schema where @promptkey belongs. * @promptkey: settings key to check if we should prompt the user or not. * @tag: e_alert tag. * * Convenience function to query the user with a Yes/No dialog and a * "Do not show this dialog again" checkbox. If the user checks that * checkbox, then @promptkey is set to %FALSE, otherwise it is set to * %TRUE. * * Returns %TRUE if the user clicks Yes or %FALSE otherwise. **/ gboolean e_util_prompt_user (GtkWindow *parent, const gchar *settings_schema, const gchar *promptkey, const gchar *tag, ...) { GtkWidget *dialog; GtkWidget *check = NULL; GtkWidget *container; va_list ap; gint button; GSettings *settings = NULL; EAlert *alert = NULL; if (promptkey) { settings = e_util_ref_settings (settings_schema); if (!g_settings_get_boolean (settings, promptkey)) { g_object_unref (settings); return TRUE; } } va_start (ap, tag); alert = e_alert_new_valist (tag, ap); va_end (ap); dialog = e_alert_dialog_new (parent, alert); g_object_unref (alert); container = e_alert_dialog_get_content_area (E_ALERT_DIALOG (dialog)); if (promptkey) { check = gtk_check_button_new_with_mnemonic ( _("_Do not show this message again")); gtk_box_pack_start ( GTK_BOX (container), check, FALSE, FALSE, 0); gtk_widget_show (check); } button = gtk_dialog_run (GTK_DIALOG (dialog)); if (promptkey) g_settings_set_boolean ( settings, promptkey, !gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON (check))); gtk_widget_destroy (dialog); g_clear_object (&settings); return button == GTK_RESPONSE_YES; } typedef struct _EUtilSimpleAsyncResultThreadData { GSimpleAsyncResult *simple; GSimpleAsyncThreadFunc func; GCancellable *cancellable; } EUtilSimpleAsyncResultThreadData; static void e_util_simple_async_result_thread (gpointer data, gpointer user_data) { EUtilSimpleAsyncResultThreadData *thread_data = data; GError *error = NULL; g_return_if_fail (thread_data != NULL); g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (thread_data->simple)); g_return_if_fail (thread_data->func != NULL); if (g_cancellable_set_error_if_cancelled (thread_data->cancellable, &error)) { g_simple_async_result_take_error (thread_data->simple, error); } else { thread_data->func (thread_data->simple, g_async_result_get_source_object (G_ASYNC_RESULT (thread_data->simple)), thread_data->cancellable); } g_simple_async_result_complete_in_idle (thread_data->simple); g_clear_object (&thread_data->simple); g_clear_object (&thread_data->cancellable); g_free (thread_data); } /** * e_util_run_simple_async_result_in_thread: * @simple: a #GSimpleAsyncResult * @func: a #GSimpleAsyncThreadFunc to execute in the thread * @cancellable: an optional #GCancellable, or %NULL * * Similar to g_simple_async_result_run_in_thread(), except * it doesn't use GTask internally, thus doesn't block the GTask * thread pool with possibly long job. * * It doesn't behave exactly the same as the g_simple_async_result_run_in_thread(), * the @cancellable checking is not done before the finish. * * Since: 3.18 **/ void e_util_run_simple_async_result_in_thread (GSimpleAsyncResult *simple, GSimpleAsyncThreadFunc func, GCancellable *cancellable) { static GThreadPool *thread_pool = NULL; static GMutex thread_pool_mutex; EUtilSimpleAsyncResultThreadData *thread_data; g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); g_return_if_fail (func != NULL); g_mutex_lock (&thread_pool_mutex); if (!thread_pool) thread_pool = g_thread_pool_new (e_util_simple_async_result_thread, NULL, 20, FALSE, NULL); thread_data = g_new0 (EUtilSimpleAsyncResultThreadData, 1); thread_data->simple = g_object_ref (simple); thread_data->func = func; thread_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL; g_thread_pool_push (thread_pool, thread_data, NULL); g_mutex_unlock (&thread_pool_mutex); } /** * e_util_is_running_gnome: * * Returns: Whether the current running desktop environment is GNOME. * * Since: 3.18 **/ gboolean e_util_is_running_gnome (void) { #ifdef G_OS_WIN32 return FALSE; #else static gint runs_gnome = -1; if (runs_gnome == -1) { runs_gnome = g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "GNOME") == 0 ? 1 : 0; if (runs_gnome) { GDesktopAppInfo *app_info; app_info = g_desktop_app_info_new ("gnome-notifications-panel.desktop"); if (!app_info) { runs_gnome = 0; } g_clear_object (&app_info); } } return runs_gnome != 0; #endif } /** * e_util_set_entry_issue_hint: * @entry: a #GtkEntry * @hint: (allow none): a hint to set, or %NULL to unset * * Sets a @hint on the secondary @entry icon about an entered value issue, * or unsets it, when the @hint is %NULL. * * Since: 3.20 **/ void e_util_set_entry_issue_hint (GtkWidget *entry, const gchar *hint) { GtkEntry *eentry; g_return_if_fail (GTK_IS_ENTRY (entry)); eentry = GTK_ENTRY (entry); if (hint) { gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning"); gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, hint); } else { gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, NULL); gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, NULL); } } static GThread *main_thread = NULL; void e_util_init_main_thread (GThread *thread) { g_return_if_fail (main_thread == NULL); main_thread = thread ? thread : g_thread_self (); } gboolean e_util_is_main_thread (GThread *thread) { return thread ? thread == main_thread : g_thread_self () == main_thread; } /** * e_util_save_image_from_clipboard: * @clipboard: a #GtkClipboard * @hint: (allow none): a hint to set, or %NULL to unset * * Saves the image from @clipboard to a temporary file and returns its URI. * * Since: 3.22 **/ gchar * e_util_save_image_from_clipboard (GtkClipboard *clipboard) { GdkPixbuf *pixbuf = NULL; gchar *tmpl; gchar *filename = NULL; gchar *uri = NULL; GError *error = NULL; g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), NULL); /* Extract the image data from the clipboard. */ pixbuf = gtk_clipboard_wait_for_image (clipboard); g_return_val_if_fail (pixbuf != NULL, FALSE); tmpl = g_strconcat (_("Image"), "-XXXXXX.png", NULL); /* Reserve a temporary file. */ filename = e_mktemp (tmpl); g_free (tmpl); if (filename == NULL) { g_set_error ( &error, G_FILE_ERROR, g_file_error_from_errno (errno), "Could not create temporary file: %s", g_strerror (errno)); goto exit; } /* Save the pixbuf as a temporary file in image/png format. */ if (!gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL)) goto exit; /* Convert the filename to a URI. */ uri = g_filename_to_uri (filename, NULL, &error); exit: if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } g_object_unref (pixbuf); g_free (filename); return uri; } static void e_util_stop_signal_emission_cb (gpointer instance, const gchar *signal_name) { g_signal_stop_emission_by_name (instance, signal_name); } /** * e_util_check_gtk_bindings_in_key_press_event_cb: * @widget: a #GtkWidget, most often a #GtkWindow * @event: a #GdkEventKey * * A callback function for GtkWidget::key-press-event signal, * which checks whether currently focused widget inside @widget, * if it's a #GtkWindow, or a toplevel window containing the @widget, * will consume the @event due to gtk+ bindings and if so, then * it'll stop processing the event further. When it's connected * on a #GtkWindow, then it can prevent the event to be used * for shortcuts of actions. * * Returns: %TRUE to stop other handlers from being invoked for * the event, %FALSE to propagate the event further. * * Since: 3.22 **/ gboolean e_util_check_gtk_bindings_in_key_press_event_cb (GtkWidget *widget, GdkEvent *event) { GdkEventKey *key_event = (GdkEventKey *) event; GtkWindow *window = NULL; GtkWidget *focused; g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); g_return_val_if_fail (event->type == GDK_KEY_PRESS, FALSE); if (GTK_IS_WINDOW (widget)) { window = GTK_WINDOW (widget); } else { GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (widget); if (GTK_IS_WINDOW (toplevel)) window = GTK_WINDOW (toplevel); } if (!window) return FALSE; focused = gtk_window_get_focus (window); if (!focused) return FALSE; if (gtk_bindings_activate_event (G_OBJECT (focused), key_event)) return TRUE; if (WEBKIT_IS_WEB_VIEW (focused) && (key_event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0) { GtkWidget *text_view; gboolean may_use; /* WebKit uses GtkTextView to process key bindings. Do the same. */ text_view = gtk_text_view_new (); /* Stop emissing for clipboard signals, to not populate the text_view */ g_signal_connect (text_view, "copy-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "copy-clipboard"); g_signal_connect (text_view, "cut-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "cut-clipboard"); g_signal_connect (text_view, "paste-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "paste-clipboard"); may_use = gtk_bindings_activate_event (G_OBJECT (text_view), key_event); gtk_widget_destroy (text_view); if (may_use) { gboolean result = FALSE; g_signal_emit_by_name (focused, "key-press-event", event, &result); return result; } } return FALSE; } /** * e_util_claim_dbus_proxy_call_error: * @dbus_proxy: a #GDBusProxy instance * @method_name: a method name of the @dbus_proxy * @in_error: (allow-none): a #GError with the failure, or %NULL * * Claims the @in_error on the console as a failure to call * method @method_name of the @dbus_proxy. It's safe to call this * with a %NULL @in_error, then the function does nothing. * Some errors can be ignored, like the G_IO_ERROR_CANCELLED is. * * Since: 3.22 **/ void e_util_claim_dbus_proxy_call_error (GDBusProxy *dbus_proxy, const gchar *method_name, const GError *in_error) { g_return_if_fail (G_IS_DBUS_PROXY (dbus_proxy)); g_return_if_fail (method_name != NULL); if (in_error && !g_error_matches (in_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Failed to call a DBus Proxy method %s::%s: %s", g_dbus_proxy_get_name (dbus_proxy), method_name, in_error->message); } static void e_util_finish_dbus_proxy_call_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { gchar *method_name = user_data; GDBusProxy *dbus_proxy; GVariant *ret; GError *error = NULL; g_return_if_fail (G_IS_DBUS_PROXY (source_object)); dbus_proxy = G_DBUS_PROXY (source_object); ret = g_dbus_proxy_call_finish (dbus_proxy, result, &error); if (ret) g_variant_unref (ret); if (error) g_dbus_error_strip_remote_error (error); e_util_claim_dbus_proxy_call_error (dbus_proxy, method_name, error); g_clear_error (&error); g_free (method_name); } /** * e_util_invoke_g_dbus_proxy_call_with_error_check: * @dbus_proxy: a #GDBusProxy instance * @method_name: a method name to invoke * @parameters: (allow-none): parameters of the method, or %NULL * @cancellable: (allow-none): a #GCancellable, or %NULL * * Calls g_dbus_proxy_call() on the @dbus_proxy for the @method_name with @parameters * and adds its own callback for the result. Then, if an error is returned, passes this * error into e_util_claim_dbus_proxy_call_error(). * * See: e_util_invoke_g_dbus_proxy_call_with_error_check_full() * * Since: 3.22 **/ void e_util_invoke_g_dbus_proxy_call_with_error_check (GDBusProxy *dbus_proxy, const gchar *method_name, GVariant *parameters, GCancellable *cancellable) { g_return_if_fail (G_IS_DBUS_PROXY (dbus_proxy)); g_return_if_fail (method_name != NULL); e_util_invoke_g_dbus_proxy_call_with_error_check_full ( dbus_proxy, method_name, parameters, G_DBUS_CALL_FLAGS_NONE, -1, cancellable); } /** * e_util_invoke_g_dbus_proxy_call_with_error_check_full: * @dbus_proxy: a #GDBusProxy instance * @method_name: a method name to invoke * @parameters: (allow-none): parameters of the method, or %NULL * @flags: a bit-or of #GDBusCallFlags, with the same meaning as in the g_dbus_proxy_call() * @timeout_msec: timeout in milliseconds, with the same meaning as in the g_dbus_proxy_call(). * @cancellable: (allow-none): a #GCancellable, or %NULL * * Calls g_dbus_proxy_call() on the @dbus_proxy for the @method_name with @parameters * and adds its own callback for the result. Then, if an error is returned, passes this * error into e_util_claim_dbus_proxy_call_error(). * * See: e_util_invoke_g_dbus_proxy_call_with_error_check() * * Since: 3.22 **/ void e_util_invoke_g_dbus_proxy_call_with_error_check_full (GDBusProxy *dbus_proxy, const gchar *method_name, GVariant *parameters, GDBusCallFlags flags, gint timeout_msec, GCancellable *cancellable) { g_return_if_fail (G_IS_DBUS_PROXY (dbus_proxy)); g_return_if_fail (method_name != NULL); g_dbus_proxy_call (dbus_proxy, method_name, parameters, flags, timeout_msec, cancellable, e_util_finish_dbus_proxy_call_cb, g_strdup (method_name)); } /** * e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check: * @dbus_proxy: a #GDBusProxy instance * @method_name: a method name to invoke * @parameters: (allow-none): parameters of the method, or %NULL * @cancellable: (allow-none): a #GCancellable, or %NULL * * Calls e_util_invoke_g_dbus_proxy_call_sync_wrapper_full() with some default * values for flags and timeout_msec, while also providing its own GError and * after the call is finished it calls e_util_claim_dbus_proxy_call_error() * with the returned error, if any. * * Returns: The result of the method call, or %NULL on error. Free with g_variant_unref(). * * Since: 3.22 **/ GVariant * e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check (GDBusProxy *dbus_proxy, const gchar *method_name, GVariant *parameters, GCancellable *cancellable) { GVariant *result; GError *error = NULL; g_return_val_if_fail (G_IS_DBUS_PROXY (dbus_proxy), NULL); g_return_val_if_fail (method_name != NULL, NULL); result = e_util_invoke_g_dbus_proxy_call_sync_wrapper_full (dbus_proxy, method_name, parameters, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, &error); if (error) g_dbus_error_strip_remote_error (error); e_util_claim_dbus_proxy_call_error (dbus_proxy, method_name, error); g_clear_error (&error); return result; } static void sync_wrapper_result_callback (GObject *source_object, GAsyncResult *result, gpointer user_data) { GAsyncResult **out_async_result = user_data; g_return_if_fail (out_async_result != NULL); g_return_if_fail (*out_async_result == NULL); *out_async_result = g_object_ref (result); } /** * e_util_invoke_g_dbus_proxy_call_sync_wrapper_full: * @dbus_proxy: a #GDBusProxy instance * @method_name: a method name to invoke * @parameters: (allow-none): parameters of the method, or %NULL * @flags: a bit-or of #GDBusCallFlags, with the same meaning as in the g_dbus_proxy_call() * @timeout_msec: timeout in milliseconds, with the same meaning as in the g_dbus_proxy_call(). * @cancellable: (allow-none): a #GCancellable, or %NULL * @error: (allow-none): Return location for error, or %NULL * * Wraps GDBusProxy synchronous call into an asynchronous without blocking * the main context. This can be useful when doing calls on a WebExtension, * because it can avoid freeze when this is called in the UI process and * the WebProcess also does its own IPC call. * * This function should be called only from the main thread. * * Returns: The result of the method call, or %NULL on error. Free with g_variant_unref(). * * Since: 3.22 **/ GVariant * e_util_invoke_g_dbus_proxy_call_sync_wrapper_full (GDBusProxy *dbus_proxy, const gchar *method_name, GVariant *parameters, GDBusCallFlags flags, gint timeout_msec, GCancellable *cancellable, GError **error) { GAsyncResult *async_result = NULL; GVariant *var_result; GMainContext *main_context; g_return_val_if_fail (G_IS_DBUS_PROXY (dbus_proxy), NULL); g_return_val_if_fail (method_name != NULL, NULL); g_warn_if_fail (e_util_is_main_thread (g_thread_self ())); g_dbus_proxy_call ( dbus_proxy, method_name, parameters, flags, timeout_msec, cancellable, sync_wrapper_result_callback, &async_result); main_context = g_main_context_get_thread_default (); while (!async_result) { g_main_context_iteration (main_context, TRUE); } var_result = g_dbus_proxy_call_finish (dbus_proxy, async_result, error); g_clear_object (&async_result); return var_result; } /** * e_util_save_file_chooser_folder: * @file_chooser: a #GtkFileChooser * * Saves the current folder of the @file_chooser, thus it could be used * by e_util_load_file_chooser_folder() to open it in the last chosen folder. * * Since: 3.24 **/ void e_util_save_file_chooser_folder (GtkFileChooser *file_chooser) { GSettings *settings; gchar *uri; g_return_if_fail (GTK_IS_FILE_CHOOSER (file_chooser)); uri = gtk_file_chooser_get_current_folder_uri (file_chooser); if (uri && g_str_has_prefix (uri, "file://")) { settings = e_util_ref_settings ("org.gnome.evolution.shell"); g_settings_set_string (settings, "file-chooser-folder", uri); g_object_unref (settings); } g_free (uri); } /** * e_util_load_file_chooser_folder: * @file_chooser: a #GtkFileChooser * * Sets the current folder to the @file_chooser to the one previously saved * by e_util_save_file_chooser_folder(). The function does nothing if none * or invalid is saved. * * Since: 3.24 **/ void e_util_load_file_chooser_folder (GtkFileChooser *file_chooser) { GSettings *settings; gchar *uri; g_return_if_fail (GTK_IS_FILE_CHOOSER (file_chooser)); settings = e_util_ref_settings ("org.gnome.evolution.shell"); uri = g_settings_get_string (settings, "file-chooser-folder"); g_object_unref (settings); if (uri && g_str_has_prefix (uri, "file://")) { gchar *filename; filename = g_filename_from_uri (uri, NULL, NULL); if (filename && g_file_test (filename, G_FILE_TEST_IS_DIR)) gtk_file_chooser_set_current_folder_uri (file_chooser, uri); g_free (filename); } g_free (uri); } /** * e_util_get_webkit_developer_mode_enabled: * * Returns: Whether WebKit developer mode is enabled. This is read only * once, thus any changes in the GSettings property require restart * of the Evolution. * * Since: 3.24 **/ gboolean e_util_get_webkit_developer_mode_enabled (void) { static gchar enabled = -1; if (enabled == -1) { GSettings *settings; settings = e_util_ref_settings ("org.gnome.evolution.shell"); enabled = g_settings_get_boolean (settings, "webkit-developer-mode") ? 1 : 0; g_clear_object (&settings); } return enabled != 0; } /** * e_util_next_uri_from_uri_list: * @uri_list: array of URIs separated by new lines * @len: (out): a length of the found URI * @list_len: (out): a length of the array * * Returns: A newly allocated string with found URI. * * Since: 3.26 **/ gchar * e_util_next_uri_from_uri_list (guchar **uri_list, gint *len, gint *list_len) { guchar *uri, *begin; begin = *uri_list; *len = 0; while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) { (*uri_list) ++; (*len) ++; (*list_len) --; } uri = (guchar *) g_strndup ((gchar *) begin, *len); while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) { (*uri_list) ++; (*list_len) --; } return (gchar *) uri; } /** * e_util_resize_window_for_screen: * @window: a #GtkWindow * @window_width: the @window width without @children, or -1 to compute * @window_height: the @window height without @children, or -1 to compute * @children: (element-type GtkWidget): (nullable): a #GSList with children to calculate with * * Calculates the size of the @window considering preferred sizes of @children, * and shrinks the @window in case it won't be completely visible on the screen * it is assigned to. * * Since: 3.26 **/ void e_util_resize_window_for_screen (GtkWindow *window, gint window_width, gint window_height, const GSList *children) { gint width = -1, height = -1, content_width = -1, content_height = -1, current_width = -1, current_height = -1; GtkRequisition requisition; const GSList *link; g_return_if_fail (GTK_IS_WINDOW (window)); gtk_window_get_default_size (window, &width, &height); if (width < 0 || height < 0) { gtk_widget_get_preferred_size (GTK_WIDGET (window), &requisition, NULL); width = requisition.width; height = requisition.height; } for (link = children; link; link = g_slist_next (link)) { GtkWidget *widget = link->data; if (GTK_IS_SCROLLED_WINDOW (widget)) widget = gtk_bin_get_child (GTK_BIN (widget)); if (GTK_IS_VIEWPORT (widget)) widget = gtk_bin_get_child (GTK_BIN (widget)); if (!GTK_IS_WIDGET (widget)) continue; gtk_widget_get_preferred_size (widget, &requisition, NULL); if (requisition.width > content_width) content_width = requisition.width; if (requisition.height > content_height) content_height = requisition.height; widget = gtk_widget_get_parent (widget); if (GTK_IS_VIEWPORT (widget)) widget = gtk_widget_get_parent (widget); if (GTK_IS_WIDGET (widget)) { gtk_widget_get_preferred_size (widget, &requisition, NULL); if (current_width == -1 || current_width < requisition.width) current_width = requisition.width; if (current_height == -1 || current_height < requisition.height) current_height = requisition.height; } } if (content_width > 0 && content_height > 0 && width > 0 && height > 0) { GdkScreen *screen; GdkRectangle monitor_area; gint x = 0, y = 0, monitor; screen = gtk_window_get_screen (GTK_WINDOW (window)); gtk_window_get_position (GTK_WINDOW (window), &x, &y); monitor = gdk_screen_get_monitor_at_point (screen, x, y); if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen)) monitor = 0; gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area); /* When the children are packed inside the window then influence the window size too, thus subtract it, if possible. */ if (window_width < 0) { if (current_width > 0 && current_width < width) width -= current_width; } else { width = window_width; } if (window_height < 0) { if (current_height > 0 && current_height < height) height -= current_height; } else { height = window_height; } if (content_width > monitor_area.width - width) content_width = monitor_area.width - width; if (content_height > monitor_area.height - height) content_height = monitor_area.height - height; if (content_width > 0 && content_height > 0) gtk_window_set_default_size (GTK_WINDOW (window), width + content_width, height + content_height); } } /** * e_util_query_ldap_root_dse_sync: * @host: an LDAP server host name * @port: an LDAP server port * @out_root_dse: (out) (transfer full): NULL-terminated array of the server root DSE-s, or %NULL on error * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Queries an LDAP server identified by @host and @port for supported * search bases and returns them as an NULL-terminated array of strings * at @out_root_dse. It sets @out_root_dse to NULL on error. * Free the returned @out_root_dse with g_strfreev() when no longer needed. * * The function fails and sets @error to G_IO_ERROR_NOT_SUPPORTED when * Evolution had been compiled without LDAP support. * * Returns: Whether succeeded. * * Since: 3.28 **/ gboolean e_util_query_ldap_root_dse_sync (const gchar *host, guint16 port, gchar ***out_root_dse, GCancellable *cancellable, GError **error) { #ifdef HAVE_LDAP G_LOCK_DEFINE_STATIC (ldap); LDAP *ldap = NULL; LDAPMessage *result = NULL; struct timeval timeout; gchar **values = NULL, **root_dse; gint ldap_error; gint option; gint version; gint ii; const gchar *attrs[] = { "namingContexts", NULL }; g_return_val_if_fail (host && *host, FALSE); g_return_val_if_fail (port > 0, FALSE); g_return_val_if_fail (out_root_dse != NULL, FALSE); *out_root_dse = NULL; timeout.tv_sec = 60; timeout.tv_usec = 0; G_LOCK (ldap); if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; ldap = ldap_init (host, port); if (!ldap) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("This address book server might be unreachable or the server name may be misspelled or your network connection could be down.")); goto exit; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; version = LDAP_VERSION3; option = LDAP_OPT_PROTOCOL_VERSION; ldap_error = ldap_set_option (ldap, option, &version); if (ldap_error != LDAP_OPT_SUCCESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, _("Failed to set protocol version to LDAPv3 (%d): %s"), ldap_error, ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error")); goto exit; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; /* FIXME Use the user's actual authentication settings. */ ldap_error = ldap_simple_bind_s (ldap, NULL, NULL); if (ldap_error != LDAP_SUCCESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Failed to authenticate with LDAP server (%d): %s"), ldap_error, ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error")); goto exit; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; ldap_error = ldap_search_ext_s ( ldap, LDAP_ROOT_DSE, LDAP_SCOPE_BASE, "(objectclass=*)", (gchar **) attrs, 0, NULL, NULL, &timeout, LDAP_NO_LIMIT, &result); if (ldap_error != LDAP_SUCCESS) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("This LDAP server may use an older version of LDAP, which does not support this functionality or it may be misconfigured. Ask your administrator for supported search bases.\n\nDetailed error (%d): %s"), ldap_error, ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error")); goto exit; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; values = ldap_get_values (ldap, result, "namingContexts"); if (values == NULL || values[0] == NULL || *values[0] == '\0') { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("This LDAP server may use an older version of LDAP, which does not support this functionality or it may be misconfigured. Ask your administrator for supported search bases.")); goto exit; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto exit; root_dse = g_new0 (gchar *, g_strv_length (values) + 1); for (ii = 0; values[ii]; ii++) { root_dse[ii] = g_strdup (values[ii]); } root_dse[ii] = NULL; *out_root_dse = root_dse; exit: if (values) ldap_value_free (values); if (result) ldap_msgfree (result); if (ldap) ldap_unbind_s (ldap); G_UNLOCK (ldap); return *out_root_dse != NULL; #else /* HAVE_LDAP */ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Evolution had not been compiled with LDAP support")); return FALSE; #endif }