1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 *
15 * Authors:
16 * Chris Lahey <clahey@ximian.com>
17 *
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 *
20 */
21
22 #include "evolution-config.h"
23
24 #include "e-misc-utils.h"
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <ctype.h>
31 #include <math.h>
32 #include <string.h>
33 #include <locale.h>
34 #include <time.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37
38 #include <gio/gio.h>
39 #include <gtk/gtk.h>
40 #include <glib/gi18n.h>
41 #include <glib/gstdio.h>
42
43 #ifdef G_OS_WIN32
44 #include <windows.h>
45 #else
46 #include <gio/gdesktopappinfo.h>
47 #endif
48
49 #include <camel/camel.h>
50 #include <libedataserver/libedataserver.h>
51
52 #include <webkit2/webkit2.h>
53
54 #ifdef HAVE_LDAP
55 #include <ldap.h>
56 #ifndef SUNLDAP
57 #include <ldap_schema.h>
58 #endif
59 #endif /* HAVE_LDAP */
60
61 #include "e-alert-dialog.h"
62 #include "e-alert-sink.h"
63 #include "e-client-cache.h"
64 #include "e-filter-option.h"
65 #include "e-mktemp.h"
66 #include "e-simple-async-result.h"
67 #include "e-spell-checker.h"
68 #include "e-util-private.h"
69 #include "e-xml-utils.h"
70 #include "e-supported-locales-private.h"
71
72 typedef struct _WindowData WindowData;
73
74 struct _WindowData {
75 GtkWindow *window;
76 GSettings *settings;
77 ERestoreWindowFlags flags;
78 gint premax_width;
79 gint premax_height;
80 guint timeout_id;
81 };
82
83 static void
window_data_free(WindowData * data)84 window_data_free (WindowData *data)
85 {
86 if (data->settings != NULL)
87 g_object_unref (data->settings);
88
89 if (data->timeout_id > 0)
90 g_source_remove (data->timeout_id);
91
92 g_slice_free (WindowData, data);
93 }
94
95 static gboolean
window_update_settings(gpointer user_data)96 window_update_settings (gpointer user_data)
97 {
98 WindowData *data = user_data;
99 GSettings *settings = data->settings;
100
101 if (data->flags & E_RESTORE_WINDOW_SIZE) {
102 GdkWindowState state;
103 GdkWindow *window;
104 gboolean maximized;
105
106 window = gtk_widget_get_window (GTK_WIDGET (data->window));
107 state = gdk_window_get_state (window);
108 maximized = ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0);
109
110 g_settings_set_boolean (settings, "maximized", maximized);
111
112 if (!maximized) {
113 gint width, height;
114
115 gtk_window_get_size (data->window, &width, &height);
116
117 g_settings_set_int (settings, "width", width);
118 g_settings_set_int (settings, "height", height);
119 }
120 }
121
122 if (data->flags & E_RESTORE_WINDOW_POSITION) {
123 gint x, y;
124
125 gtk_window_get_position (data->window, &x, &y);
126
127 g_settings_set_int (settings, "x", x);
128 g_settings_set_int (settings, "y", y);
129 }
130
131 data->timeout_id = 0;
132
133 return FALSE;
134 }
135
136 static void
window_delayed_update_settings(WindowData * data)137 window_delayed_update_settings (WindowData *data)
138 {
139 if (data->timeout_id > 0)
140 g_source_remove (data->timeout_id);
141
142 data->timeout_id = e_named_timeout_add_seconds (
143 1, window_update_settings, data);
144 }
145
146 static gboolean
window_configure_event_cb(GtkWindow * window,GdkEventConfigure * event,WindowData * data)147 window_configure_event_cb (GtkWindow *window,
148 GdkEventConfigure *event,
149 WindowData *data)
150 {
151 window_delayed_update_settings (data);
152
153 return FALSE;
154 }
155
156 static gboolean
window_state_event_cb(GtkWindow * window,GdkEventWindowState * event,WindowData * data)157 window_state_event_cb (GtkWindow *window,
158 GdkEventWindowState *event,
159 WindowData *data)
160 {
161 gboolean window_was_unmaximized;
162
163 if (data->timeout_id > 0) {
164 g_source_remove (data->timeout_id);
165 data->timeout_id = 0;
166 }
167
168 window_was_unmaximized =
169 ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) &&
170 ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0);
171
172 if (window_was_unmaximized) {
173 gint width, height;
174
175 width = data->premax_width;
176 data->premax_width = 0;
177
178 height = data->premax_height;
179 data->premax_height = 0;
180
181 /* This only applies when the window is initially restored
182 * as maximized and is then unmaximized. GTK+ handles the
183 * unmaximized window size thereafter. */
184 if (width > 0 && height > 0)
185 gtk_window_resize (window, width, height);
186 }
187
188 window_delayed_update_settings (data);
189
190 return FALSE;
191 }
192
193 static gboolean
window_unmap_cb(GtkWindow * window,WindowData * data)194 window_unmap_cb (GtkWindow *window,
195 WindowData *data)
196 {
197 if (data->timeout_id > 0) {
198 g_source_remove (data->timeout_id);
199 data->timeout_id = 0;
200 }
201
202 /* Reset the flags so the window position and size are not
203 * accidentally reverted to their default value at the next run. */
204 data->flags = 0;
205
206 return FALSE;
207 }
208
209 /**
210 * e_get_accels_filename:
211 *
212 * Returns the name of the user data file containing custom keyboard
213 * accelerator specifications.
214 *
215 * Returns: filename for accelerator specifications
216 **/
217 const gchar *
e_get_accels_filename(void)218 e_get_accels_filename (void)
219 {
220 static gchar *filename = NULL;
221
222 if (G_UNLIKELY (filename == NULL)) {
223 const gchar *config_dir = e_get_user_config_dir ();
224 filename = g_build_filename (config_dir, "accels", NULL);
225 }
226
227 return filename;
228 }
229
230 /**
231 * e_show_uri:
232 * @parent: a parent #GtkWindow or %NULL
233 * @uri: the URI to show
234 *
235 * Launches the default application to show the given URI. The URI must
236 * be of a form understood by GIO. If the URI cannot be shown, it presents
237 * a dialog describing the error. The dialog is set as transient to @parent
238 * if @parent is non-%NULL.
239 **/
240 void
e_show_uri(GtkWindow * parent,const gchar * uri)241 e_show_uri (GtkWindow *parent,
242 const gchar *uri)
243 {
244 GtkWidget *dialog;
245 GdkScreen *screen = NULL;
246 gchar *scheme;
247 GError *error = NULL;
248 guint32 timestamp;
249 gboolean success;
250
251 g_return_if_fail (uri != NULL);
252
253 timestamp = gtk_get_current_event_time ();
254
255 if (parent != NULL)
256 screen = gtk_widget_get_screen (GTK_WIDGET (parent));
257
258 scheme = g_uri_parse_scheme (uri);
259
260 if (!scheme || !*scheme) {
261 gchar *schemed_uri;
262
263 schemed_uri = g_strconcat ("http://", uri, NULL);
264 success = gtk_show_uri (screen, schemed_uri, timestamp, &error);
265 g_free (schemed_uri);
266 } else {
267 success = gtk_show_uri (screen, uri, timestamp, &error);
268 }
269
270 g_free (scheme);
271
272 if (success)
273 return;
274
275 dialog = gtk_message_dialog_new_with_markup (
276 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
277 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
278 "<big><b>%s</b></big>",
279 _("Could not open the link."));
280
281 gtk_message_dialog_format_secondary_text (
282 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
283
284 gtk_dialog_run (GTK_DIALOG (dialog));
285
286 gtk_widget_destroy (dialog);
287 g_error_free (error);
288 }
289
290 static gboolean
e_misc_utils_is_help_package_installed(void)291 e_misc_utils_is_help_package_installed (void)
292 {
293 gboolean is_installed;
294 gchar *path;
295
296 /* Viewing user documentation requires the evolution help
297 * files. Look for one of the files it installs. */
298 path = g_build_filename (EVOLUTION_DATADIR, "help", "C", PACKAGE, "index.page", NULL);
299
300 is_installed = g_file_test (path, G_FILE_TEST_IS_REGULAR);
301
302 g_free (path);
303
304 if (is_installed) {
305 GAppInfo *help_handler;
306
307 help_handler = g_app_info_get_default_for_uri_scheme ("help");
308
309 is_installed = help_handler && g_app_info_get_commandline (help_handler);
310
311 g_clear_object (&help_handler);
312 }
313
314 return is_installed;
315 }
316
317 /**
318 * e_display_help:
319 * @parent: a parent #GtkWindow or %NULL
320 * @link_id: help section to present or %NULL
321 *
322 * Opens the user documentation to the section given by @link_id, or to the
323 * table of contents if @link_id is %NULL. If the user documentation cannot
324 * be opened, it presents a dialog describing the error. The dialog is set
325 * as transient to @parent if @parent is non-%NULL.
326 **/
327 void
e_display_help(GtkWindow * parent,const gchar * link_id)328 e_display_help (GtkWindow *parent,
329 const gchar *link_id)
330 {
331 GString *uri;
332 GtkWidget *dialog;
333 GdkScreen *screen = NULL;
334 GError *error = NULL;
335 guint32 timestamp;
336
337 if (e_misc_utils_is_help_package_installed ()) {
338 uri = g_string_new ("help:" PACKAGE);
339 } else {
340 uri = g_string_new ("https://help.gnome.org/users/" PACKAGE "/");
341 /* Use '/stable/' until https://bugzilla.gnome.org/show_bug.cgi?id=785522 is fixed */
342 g_string_append (uri, "stable/");
343 /* g_string_append_printf (uri, "%d.%d", EDS_MAJOR_VERSION, EDS_MINOR_VERSION); */
344 }
345
346 timestamp = gtk_get_current_event_time ();
347
348 if (parent != NULL)
349 screen = gtk_widget_get_screen (GTK_WIDGET (parent));
350
351 if (link_id != NULL) {
352 g_string_append_c (uri, '/');
353 g_string_append (uri, link_id);
354 }
355
356 if (gtk_show_uri (screen, uri->str, timestamp, &error))
357 goto exit;
358
359 dialog = gtk_message_dialog_new_with_markup (
360 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
361 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
362 "<big><b>%s</b></big>",
363 _("Could not display help for Evolution."));
364
365 gtk_message_dialog_format_secondary_text (
366 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
367
368 gtk_dialog_run (GTK_DIALOG (dialog));
369
370 gtk_widget_destroy (dialog);
371 g_error_free (error);
372
373 exit:
374 g_string_free (uri, TRUE);
375 }
376
377 /**
378 * e_restore_window:
379 * @window: a #GtkWindow
380 * @settings_path: a #GSettings path
381 * @flags: flags indicating which window features to restore
382 *
383 * This function can restore one of or both a window's size and position
384 * using #GSettings keys at @settings_path which conform to the relocatable
385 * schema "org.gnome.evolution.window".
386 *
387 * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's
388 * previously recorded size and maximize state.
389 *
390 * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to
391 * the previously recorded screen coordinates.
392 *
393 * The respective #GSettings values will be updated when the window is
394 * resized and/or moved.
395 **/
396 void
e_restore_window(GtkWindow * window,const gchar * settings_path,ERestoreWindowFlags flags)397 e_restore_window (GtkWindow *window,
398 const gchar *settings_path,
399 ERestoreWindowFlags flags)
400 {
401 WindowData *data;
402 GSettings *settings;
403 const gchar *schema;
404
405 g_return_if_fail (GTK_IS_WINDOW (window));
406 g_return_if_fail (settings_path != NULL);
407
408 schema = "org.gnome.evolution.window";
409 settings = g_settings_new_with_path (schema, settings_path);
410
411 data = g_slice_new0 (WindowData);
412 data->window = window;
413 data->settings = g_object_ref (settings);
414 data->flags = flags;
415
416 if (flags & E_RESTORE_WINDOW_SIZE) {
417 GdkScreen *screen;
418 GdkRectangle monitor_area;
419 gint x, y, width, height, monitor;
420
421 x = g_settings_get_int (settings, "x");
422 y = g_settings_get_int (settings, "y");
423
424 screen = gtk_window_get_screen (window);
425 monitor = gdk_screen_get_monitor_at_point (screen, x, y);
426 if (monitor < 0)
427 monitor = 0;
428
429 if (monitor >= gdk_screen_get_n_monitors (screen))
430 monitor = 0;
431
432 gdk_screen_get_monitor_workarea (
433 screen, monitor, &monitor_area);
434
435 width = g_settings_get_int (settings, "width");
436 height = g_settings_get_int (settings, "height");
437
438 /* Clamp the GSettings value to actual monitor area before restoring the size */
439 if (width > 0 && height > 0) {
440 if (width > 1.5 * monitor_area.width)
441 width = 1.5 * monitor_area.width;
442
443 if (height > 1.5 * monitor_area.height)
444 height = 1.5 * monitor_area.height;
445 }
446
447 if (width > 0 && height > 0)
448 gtk_window_resize (window, width, height);
449
450 if (g_settings_get_boolean (settings, "maximized")) {
451 gtk_window_get_size (window, &width, &height);
452
453 data->premax_width = width;
454 data->premax_height = height;
455
456 gtk_window_resize (
457 window,
458 monitor_area.width,
459 monitor_area.height);
460
461 gtk_window_maximize (window);
462 }
463 }
464
465 if (flags & E_RESTORE_WINDOW_POSITION) {
466 gint x, y;
467
468 x = g_settings_get_int (settings, "x");
469 y = g_settings_get_int (settings, "y");
470
471 gtk_window_move (window, x, y);
472 }
473
474 g_object_set_data_full (
475 G_OBJECT (window),
476 "e-util-window-data", data,
477 (GDestroyNotify) window_data_free);
478
479 g_signal_connect (
480 window, "configure-event",
481 G_CALLBACK (window_configure_event_cb), data);
482
483 g_signal_connect (
484 window, "window-state-event",
485 G_CALLBACK (window_state_event_cb), data);
486
487 g_signal_connect (
488 window, "unmap",
489 G_CALLBACK (window_unmap_cb), data);
490
491 g_object_unref (settings);
492 }
493
494 /**
495 * e_lookup_action:
496 * @ui_manager: a #GtkUIManager
497 * @action_name: the name of an action
498 *
499 * Returns the first #GtkAction named @action_name by traversing the
500 * list of action groups in @ui_manager. If no such action exists, the
501 * function emits a critical warning before returning %NULL, since this
502 * probably indicates a programming error and most code is not prepared
503 * to deal with lookup failures.
504 *
505 * Returns: the first #GtkAction named @action_name
506 **/
507 GtkAction *
e_lookup_action(GtkUIManager * ui_manager,const gchar * action_name)508 e_lookup_action (GtkUIManager *ui_manager,
509 const gchar *action_name)
510 {
511 GtkAction *action = NULL;
512 GList *iter;
513
514 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
515 g_return_val_if_fail (action_name != NULL, NULL);
516
517 iter = gtk_ui_manager_get_action_groups (ui_manager);
518
519 while (iter != NULL) {
520 GtkActionGroup *action_group = iter->data;
521
522 action = gtk_action_group_get_action (
523 action_group, action_name);
524 if (action != NULL)
525 return action;
526
527 iter = g_list_next (iter);
528 }
529
530 g_critical ("%s: action '%s' not found", G_STRFUNC, action_name);
531
532 return NULL;
533 }
534
535 /**
536 * e_lookup_action_group:
537 * @ui_manager: a #GtkUIManager
538 * @group_name: the name of an action group
539 *
540 * Returns the #GtkActionGroup in @ui_manager named @group_name. If no
541 * such action group exists, the function emits a critical warnings before
542 * returning %NULL, since this probably indicates a programming error and
543 * most code is not prepared to deal with lookup failures.
544 *
545 * Returns: the #GtkActionGroup named @group_name
546 **/
547 GtkActionGroup *
e_lookup_action_group(GtkUIManager * ui_manager,const gchar * group_name)548 e_lookup_action_group (GtkUIManager *ui_manager,
549 const gchar *group_name)
550 {
551 GList *iter;
552
553 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
554 g_return_val_if_fail (group_name != NULL, NULL);
555
556 iter = gtk_ui_manager_get_action_groups (ui_manager);
557
558 while (iter != NULL) {
559 GtkActionGroup *action_group = iter->data;
560 const gchar *name;
561
562 name = gtk_action_group_get_name (action_group);
563 if (strcmp (name, group_name) == 0)
564 return action_group;
565
566 iter = g_list_next (iter);
567 }
568
569 g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name);
570
571 return NULL;
572 }
573
574 /**
575 * e_action_compare_by_label:
576 * @action1: a #GtkAction
577 * @action2: a #GtkAction
578 *
579 * Compares the labels for @action1 and @action2 using g_utf8_collate().
580 *
581 * Returns: < 0 if @action1 compares before @action2, 0 if they
582 * compare equal, > 0 if @action1 compares after @action2
583 **/
584 gint
e_action_compare_by_label(GtkAction * action1,GtkAction * action2)585 e_action_compare_by_label (GtkAction *action1,
586 GtkAction *action2)
587 {
588 gchar *label1;
589 gchar *label2;
590 gint result;
591
592 /* XXX This is horribly inefficient but will generally only be
593 * used on short lists of actions during UI construction. */
594
595 if (action1 == action2)
596 return 0;
597
598 g_object_get (action1, "label", &label1, NULL);
599 g_object_get (action2, "label", &label2, NULL);
600
601 result = g_utf8_collate (label1, label2);
602
603 g_free (label1);
604 g_free (label2);
605
606 return result;
607 }
608
609 /**
610 * e_action_group_remove_all_actions:
611 * @action_group: a #GtkActionGroup
612 *
613 * Removes all actions from the action group.
614 **/
615 void
e_action_group_remove_all_actions(GtkActionGroup * action_group)616 e_action_group_remove_all_actions (GtkActionGroup *action_group)
617 {
618 GList *list, *iter;
619
620 /* XXX I've proposed this function for inclusion in GTK+.
621 * GtkActionGroup stores actions in an internal hash
622 * table and can do this more efficiently by calling
623 * g_hash_table_remove_all().
624 *
625 * http://bugzilla.gnome.org/show_bug.cgi?id=550485 */
626
627 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
628
629 list = gtk_action_group_list_actions (action_group);
630 for (iter = list; iter != NULL; iter = iter->next)
631 gtk_action_group_remove_action (action_group, iter->data);
632 g_list_free (list);
633 }
634
635 /**
636 * e_radio_action_get_current_action:
637 * @radio_action: a #GtkRadioAction
638 *
639 * Returns the currently active member of the group to which @radio_action
640 * belongs.
641 *
642 * Returns: the currently active group member
643 **/
644 GtkRadioAction *
e_radio_action_get_current_action(GtkRadioAction * radio_action)645 e_radio_action_get_current_action (GtkRadioAction *radio_action)
646 {
647 GSList *group;
648 gint current_value;
649
650 g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL);
651
652 group = gtk_radio_action_get_group (radio_action);
653 current_value = gtk_radio_action_get_current_value (radio_action);
654
655 while (group != NULL) {
656 gint value;
657
658 radio_action = GTK_RADIO_ACTION (group->data);
659 g_object_get (radio_action, "value", &value, NULL);
660
661 if (value == current_value)
662 return radio_action;
663
664 group = g_slist_next (group);
665 }
666
667 return NULL;
668 }
669
670 /**
671 * e_action_group_add_actions_localized:
672 * @action_group: a #GtkActionGroup to add @entries to
673 * @translation_domain: a translation domain to use
674 * to translate label and tooltip strings in @entries
675 * @entries: (array length=n_entries): an array of action descriptions
676 * @n_entries: the number of entries
677 * @user_data: data to pass to the action callbacks
678 *
679 * Adds #GtkAction-s defined by @entries to @action_group, with action's
680 * label and tooltip localized in the given translation domain, instead
681 * of the domain set on the @action_group.
682 *
683 * Since: 3.4
684 **/
685 void
e_action_group_add_actions_localized(GtkActionGroup * action_group,const gchar * translation_domain,const GtkActionEntry * entries,guint n_entries,gpointer user_data)686 e_action_group_add_actions_localized (GtkActionGroup *action_group,
687 const gchar *translation_domain,
688 const GtkActionEntry *entries,
689 guint n_entries,
690 gpointer user_data)
691 {
692 GtkActionGroup *tmp_group;
693 GList *list, *iter;
694 gint ii;
695
696 g_return_if_fail (action_group != NULL);
697 g_return_if_fail (entries != NULL);
698 g_return_if_fail (n_entries > 0);
699 g_return_if_fail (translation_domain != NULL);
700 g_return_if_fail (*translation_domain);
701
702 tmp_group = gtk_action_group_new ("temporary-group");
703 gtk_action_group_set_translation_domain (tmp_group, translation_domain);
704 gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data);
705
706 list = gtk_action_group_list_actions (tmp_group);
707 for (iter = list; iter != NULL; iter = iter->next) {
708 GtkAction *action = GTK_ACTION (iter->data);
709 const gchar *action_name;
710
711 g_object_ref (action);
712
713 action_name = gtk_action_get_name (action);
714
715 for (ii = 0; ii < n_entries; ii++) {
716 if (g_strcmp0 (entries[ii].name, action_name) == 0) {
717 gtk_action_group_remove_action (
718 tmp_group, action);
719 gtk_action_group_add_action_with_accel (
720 action_group, action,
721 entries[ii].accelerator);
722 break;
723 }
724 }
725
726 g_object_unref (action);
727 }
728
729 g_list_free (list);
730 g_object_unref (tmp_group);
731 }
732
733 /**
734 * e_builder_get_widget:
735 * @builder: a #GtkBuilder
736 * @widget_name: name of a widget in @builder
737 *
738 * Gets the widget named @widget_name. Note that this function does not
739 * increment the reference count of the returned widget. If @widget_name
740 * could not be found in the @builder<!-- -->'s object tree, a run-time
741 * warning is emitted since this usually indicates a programming error.
742 *
743 * This is a convenience function to work around the awkwardness of
744 * #GtkBuilder returning #GObject pointers, when the vast majority of
745 * the time you want a #GtkWidget pointer.
746 *
747 * If you need something from @builder other than a #GtkWidget, or you
748 * want to test for the existence of some widget name without incurring
749 * a run-time warning, use gtk_builder_get_object().
750 *
751 * Returns: the widget named @widget_name, or %NULL
752 **/
753 GtkWidget *
e_builder_get_widget(GtkBuilder * builder,const gchar * widget_name)754 e_builder_get_widget (GtkBuilder *builder,
755 const gchar *widget_name)
756 {
757 GObject *object;
758
759 g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
760 g_return_val_if_fail (widget_name != NULL, NULL);
761
762 object = gtk_builder_get_object (builder, widget_name);
763 if (object == NULL) {
764 g_warning ("Could not find widget '%s'", widget_name);
765 return NULL;
766 }
767
768 return GTK_WIDGET (object);
769 }
770
771 /**
772 * e_load_ui_builder_definition:
773 * @builder: a #GtkBuilder
774 * @basename: basename of the UI definition file
775 *
776 * Loads a UI definition into @builder from Evolution's UI directory.
777 * Failure here is fatal, since the application can't function without
778 * its UI definitions.
779 **/
780 void
e_load_ui_builder_definition(GtkBuilder * builder,const gchar * basename)781 e_load_ui_builder_definition (GtkBuilder *builder,
782 const gchar *basename)
783 {
784 gchar *filename;
785 GError *error = NULL;
786
787 g_return_if_fail (GTK_IS_BUILDER (builder));
788 g_return_if_fail (basename != NULL);
789
790 filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
791 gtk_builder_add_from_file (builder, filename, &error);
792 g_free (filename);
793
794 if (error != NULL) {
795 g_error ("%s: %s", basename, error->message);
796 g_warn_if_reached ();
797 }
798 }
799
800 static gdouble
e_get_ui_manager_definition_file_version(const gchar * filename)801 e_get_ui_manager_definition_file_version (const gchar *filename)
802 {
803 xmlDocPtr doc;
804 xmlNode *root;
805 gdouble version = -1.0;
806
807 g_return_val_if_fail (filename != NULL, version);
808
809 doc = e_xml_parse_file (filename);
810 if (!doc)
811 return version;
812
813 root = xmlDocGetRootElement (doc);
814 if (root && g_strcmp0 ((const gchar *) root->name, "ui") == 0) {
815 version = e_xml_get_double_prop_by_name_with_default (root, (const xmlChar *) "evolution-ui-version", -1.0);
816 }
817
818 xmlFreeDoc (doc);
819
820 return version;
821 }
822
823 static gchar *
e_pick_ui_manager_definition_file(const gchar * basename)824 e_pick_ui_manager_definition_file (const gchar *basename)
825 {
826 gchar *system_filename, *user_filename;
827 gdouble system_version, user_version;
828
829 g_return_val_if_fail (basename != NULL, NULL);
830
831 system_filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
832 user_filename = g_build_filename (e_get_user_config_dir (), "ui", basename, NULL);
833
834 if (!g_file_test (user_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
835 g_free (user_filename);
836
837 return system_filename;
838 }
839
840 user_version = e_get_ui_manager_definition_file_version (user_filename);
841 system_version = e_get_ui_manager_definition_file_version (system_filename);
842
843 /* Versions are equal and the system version is a positive number */
844 if (user_version - system_version >= -1e-9 &&
845 user_version - system_version <= 1e-9 &&
846 system_version > 1e-9) {
847 g_free (system_filename);
848
849 return user_filename;
850 }
851
852 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.",
853 user_filename, user_version, system_version);
854
855 g_free (user_filename);
856
857 return system_filename;
858 }
859
860 /**
861 * e_load_ui_manager_definition:
862 * @ui_manager: a #GtkUIManager
863 * @basename: basename of the UI definition file
864 *
865 * Loads a UI definition into @ui_manager from Evolution's UI directory.
866 * Failure here is fatal, since the application can't function without
867 * its UI definitions.
868 *
869 * Returns: The merge ID for the merged UI. The merge ID can be used to
870 * unmerge the UI with gtk_ui_manager_remove_ui().
871 **/
872 guint
e_load_ui_manager_definition(GtkUIManager * ui_manager,const gchar * basename)873 e_load_ui_manager_definition (GtkUIManager *ui_manager,
874 const gchar *basename)
875 {
876 gchar *filename;
877 guint merge_id;
878 GError *error = NULL;
879
880 g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), 0);
881 g_return_val_if_fail (basename != NULL, 0);
882
883 filename = e_pick_ui_manager_definition_file (basename);
884 merge_id = gtk_ui_manager_add_ui_from_file (
885 ui_manager, filename, &error);
886 g_free (filename);
887
888 if (error != NULL) {
889 g_error ("%s: %s", basename, error->message);
890 g_warn_if_reached ();
891 }
892
893 return merge_id;
894 }
895
896 /* Helper for e_categories_add_change_hook() */
897 static void
categories_changed_cb(GObject * useless_opaque_object,GHookList * hook_list)898 categories_changed_cb (GObject *useless_opaque_object,
899 GHookList *hook_list)
900 {
901 /* e_categories_register_change_listener() is broken because
902 * it requires callbacks to allow for some opaque GObject as
903 * the first argument (not does it document this). */
904 g_hook_list_invoke (hook_list, FALSE);
905 }
906
907 /* Helper for e_categories_add_change_hook() */
908 static void
categories_weak_notify_cb(GHookList * hook_list,gpointer where_the_object_was)909 categories_weak_notify_cb (GHookList *hook_list,
910 gpointer where_the_object_was)
911 {
912 GHook *hook;
913
914 /* This should not happen, but if we fail to find the hook for
915 * some reason, g_hook_destroy_link() will warn about the NULL
916 * pointer, which is all we would do anyway so no need to test
917 * for it ourselves. */
918 hook = g_hook_find_data (hook_list, TRUE, where_the_object_was);
919 g_hook_destroy_link (hook_list, hook);
920 }
921
922 /**
923 * e_categories_add_change_hook:
924 * @func: a hook function
925 * @object: a #GObject to be passed to @func, or %NULL
926 *
927 * A saner alternative to e_categories_register_change_listener().
928 *
929 * Adds a hook function to be called when a category is added, removed or
930 * modified. If @object is not %NULL, the hook function is automatically
931 * removed when @object is finalized.
932 **/
933 void
e_categories_add_change_hook(GHookFunc func,gpointer object)934 e_categories_add_change_hook (GHookFunc func,
935 gpointer object)
936 {
937 static gboolean initialized = FALSE;
938 static GHookList hook_list;
939 GHook *hook;
940
941 g_return_if_fail (func != NULL);
942
943 if (object != NULL)
944 g_return_if_fail (G_IS_OBJECT (object));
945
946 if (!initialized) {
947 g_hook_list_init (&hook_list, sizeof (GHook));
948 e_categories_register_change_listener (
949 G_CALLBACK (categories_changed_cb), &hook_list);
950 initialized = TRUE;
951 }
952
953 hook = g_hook_alloc (&hook_list);
954
955 hook->func = func;
956 hook->data = object;
957
958 if (object != NULL)
959 g_object_weak_ref (
960 G_OBJECT (object), (GWeakNotify)
961 categories_weak_notify_cb, &hook_list);
962
963 g_hook_append (&hook_list, hook);
964 }
965
966 /**
967 * e_flexible_strtod:
968 * @nptr: the string to convert to a numeric value.
969 * @endptr: if non-NULL, it returns the character after
970 * the last character used in the conversion.
971 *
972 * Converts a string to a gdouble value. This function detects
973 * strings either in the standard C locale or in the current locale.
974 *
975 * This function is typically used when reading configuration files or
976 * other non-user input that should not be locale dependent, but may
977 * have been in the past. To handle input from the user you should
978 * normally use the locale-sensitive system strtod function.
979 *
980 * To convert from a double to a string in a locale-insensitive way, use
981 * @g_ascii_dtostr.
982 *
983 * Returns: the gdouble value
984 **/
985 gdouble
e_flexible_strtod(const gchar * nptr,gchar ** endptr)986 e_flexible_strtod (const gchar *nptr,
987 gchar **endptr)
988 {
989 gchar *fail_pos;
990 gdouble val;
991 struct lconv *locale_data;
992 const gchar *decimal_point;
993 gint decimal_point_len;
994 const gchar *p, *decimal_point_pos;
995 const gchar *end = NULL; /* Silence gcc */
996 gchar *copy, *c;
997
998 g_return_val_if_fail (nptr != NULL, 0);
999
1000 fail_pos = NULL;
1001
1002 locale_data = localeconv ();
1003 decimal_point = locale_data->decimal_point;
1004 decimal_point_len = strlen (decimal_point);
1005
1006 g_return_val_if_fail (decimal_point_len != 0, 0);
1007
1008 decimal_point_pos = NULL;
1009 if (!strcmp (decimal_point, "."))
1010 return strtod (nptr, endptr);
1011
1012 p = nptr;
1013
1014 /* Skip leading space */
1015 while (isspace ((guchar) * p))
1016 p++;
1017
1018 /* Skip leading optional sign */
1019 if (*p == '+' || *p == '-')
1020 p++;
1021
1022 if (p[0] == '0' &&
1023 (p[1] == 'x' || p[1] == 'X')) {
1024 p += 2;
1025 /* HEX - find the (optional) decimal point */
1026
1027 while (isxdigit ((guchar) * p))
1028 p++;
1029
1030 if (*p == '.') {
1031 decimal_point_pos = p++;
1032
1033 while (isxdigit ((guchar) * p))
1034 p++;
1035
1036 if (*p == 'p' || *p == 'P')
1037 p++;
1038 if (*p == '+' || *p == '-')
1039 p++;
1040 while (isdigit ((guchar) * p))
1041 p++;
1042 end = p;
1043 } else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
1044 return strtod (nptr, endptr);
1045 }
1046 } else {
1047 while (isdigit ((guchar) * p))
1048 p++;
1049
1050 if (*p == '.') {
1051 decimal_point_pos = p++;
1052
1053 while (isdigit ((guchar) * p))
1054 p++;
1055
1056 if (*p == 'e' || *p == 'E')
1057 p++;
1058 if (*p == '+' || *p == '-')
1059 p++;
1060 while (isdigit ((guchar) * p))
1061 p++;
1062 end = p;
1063 } else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
1064 return strtod (nptr, endptr);
1065 }
1066 }
1067 /* For the other cases, we need not convert the decimal point */
1068
1069 if (!decimal_point_pos)
1070 return strtod (nptr, endptr);
1071
1072 /* We need to convert the '.' to the locale specific decimal point */
1073 copy = g_malloc (end - nptr + 1 + decimal_point_len);
1074
1075 c = copy;
1076 memcpy (c, nptr, decimal_point_pos - nptr);
1077 c += decimal_point_pos - nptr;
1078 memcpy (c, decimal_point, decimal_point_len);
1079 c += decimal_point_len;
1080 memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
1081 c += end - (decimal_point_pos + 1);
1082 *c = 0;
1083
1084 val = strtod (copy, &fail_pos);
1085
1086 if (fail_pos) {
1087 if (fail_pos > decimal_point_pos)
1088 fail_pos =
1089 (gchar *) nptr + (fail_pos - copy) -
1090 (decimal_point_len - 1);
1091 else
1092 fail_pos = (gchar *) nptr + (fail_pos - copy);
1093 }
1094
1095 g_free (copy);
1096
1097 if (endptr)
1098 *endptr = fail_pos;
1099
1100 return val;
1101 }
1102
1103 /**
1104 * e_ascii_dtostr:
1105 * @buffer: A buffer to place the resulting string in
1106 * @buf_len: The length of the buffer.
1107 * @format: The printf-style format to use for the
1108 * code to use for converting.
1109 * @d: The double to convert
1110 *
1111 * Converts a double to a string, using the '.' as
1112 * decimal_point. To format the number you pass in
1113 * a printf-style formating string. Allowed conversion
1114 * specifiers are eEfFgG.
1115 *
1116 * If you want to generates enough precision that converting
1117 * the string back using @g_strtod gives the same machine-number
1118 * (on machines with IEEE compatible 64bit doubles) use the format
1119 * string "%.17g". If you do this it is guaranteed that the size
1120 * of the resulting string will never be larger than
1121 * @G_ASCII_DTOSTR_BUF_SIZE bytes.
1122 *
1123 * Returns: the pointer to the buffer with the converted string
1124 **/
1125 gchar *
e_ascii_dtostr(gchar * buffer,gint buf_len,const gchar * format,gdouble d)1126 e_ascii_dtostr (gchar *buffer,
1127 gint buf_len,
1128 const gchar *format,
1129 gdouble d)
1130 {
1131 struct lconv *locale_data;
1132 const gchar *decimal_point;
1133 gint decimal_point_len;
1134 gchar *p;
1135 gint rest_len;
1136 gchar format_char;
1137
1138 g_return_val_if_fail (buffer != NULL, NULL);
1139 g_return_val_if_fail (format[0] == '%', NULL);
1140 g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL);
1141
1142 format_char = format[strlen (format) - 1];
1143
1144 g_return_val_if_fail (format_char == 'e' || format_char == 'E' ||
1145 format_char == 'f' || format_char == 'F' ||
1146 format_char == 'g' || format_char == 'G',
1147 NULL);
1148
1149 if (format[0] != '%')
1150 return NULL;
1151
1152 if (strpbrk (format + 1, "'l%"))
1153 return NULL;
1154
1155 if (!(format_char == 'e' || format_char == 'E' ||
1156 format_char == 'f' || format_char == 'F' ||
1157 format_char == 'g' || format_char == 'G'))
1158 return NULL;
1159
1160 g_snprintf (buffer, buf_len, format, d);
1161
1162 locale_data = localeconv ();
1163 decimal_point = locale_data->decimal_point;
1164 decimal_point_len = strlen (decimal_point);
1165
1166 g_return_val_if_fail (decimal_point_len != 0, NULL);
1167
1168 if (strcmp (decimal_point, ".")) {
1169 p = buffer;
1170
1171 if (*p == '+' || *p == '-')
1172 p++;
1173
1174 while (isdigit ((guchar) * p))
1175 p++;
1176
1177 if (strncmp (p, decimal_point, decimal_point_len) == 0) {
1178 *p = '.';
1179 p++;
1180 if (decimal_point_len > 1) {
1181 rest_len = strlen (p + (decimal_point_len - 1));
1182 memmove (
1183 p, p + (decimal_point_len - 1),
1184 rest_len);
1185 p[rest_len] = 0;
1186 }
1187 }
1188 }
1189
1190 return buffer;
1191 }
1192
1193 /**
1194 * e_str_without_underscores:
1195 * @string: the string to strip underscores from
1196 *
1197 * Strips underscores from a string in the same way
1198 * @gtk_label_new_with_mnemonics does. The returned string should be freed
1199 * using g_free().
1200 *
1201 * Returns: a newly-allocated string without underscores
1202 */
1203 gchar *
e_str_without_underscores(const gchar * string)1204 e_str_without_underscores (const gchar *string)
1205 {
1206 gchar *new_string;
1207 const gchar *sp;
1208 gchar *dp;
1209
1210 new_string = g_malloc (strlen (string) + 1);
1211
1212 dp = new_string;
1213 for (sp = string; *sp != '\0'; sp++) {
1214 if (*sp != '_') {
1215 *dp = *sp;
1216 dp++;
1217 } else if (sp[1] == '_') {
1218 /* Translate "__" in "_". */
1219 *dp = '_';
1220 dp++;
1221 sp++;
1222 }
1223 }
1224 *dp = 0;
1225
1226 return new_string;
1227 }
1228
1229 /**
1230 * e_str_replace_string
1231 * @text: the string to replace
1232 * @before: the string to be replaced
1233 * @after: the string to replaced with
1234 *
1235 * Replaces every occurrence of the string @before with the string @after in
1236 * the string @text and returns a #GString with result that should be freed
1237 * with g_string_free().
1238 *
1239 * Returns: a newly-allocated #GString
1240 */
1241 GString *
e_str_replace_string(const gchar * text,const gchar * before,const gchar * after)1242 e_str_replace_string (const gchar *text,
1243 const gchar *before,
1244 const gchar *after)
1245 {
1246 const gchar *p, *next;
1247 GString *str;
1248 gint find_len;
1249
1250 g_return_val_if_fail (text != NULL, NULL);
1251 g_return_val_if_fail (before != NULL, NULL);
1252 g_return_val_if_fail (*before, NULL);
1253
1254 find_len = strlen (before);
1255 str = g_string_new ("");
1256
1257 p = text;
1258 while (next = strstr (p, before), next) {
1259 if (p < next)
1260 g_string_append_len (str, p, next - p);
1261
1262 if (after && *after)
1263 g_string_append (str, after);
1264
1265 p = next + find_len;
1266 }
1267
1268 return g_string_append (str, p);
1269 }
1270
1271 gint
e_str_compare(gconstpointer x,gconstpointer y)1272 e_str_compare (gconstpointer x,
1273 gconstpointer y)
1274 {
1275 if (x == NULL || y == NULL) {
1276 if (x == y)
1277 return 0;
1278 else
1279 return x ? -1 : 1;
1280 }
1281
1282 return strcmp (x, y);
1283 }
1284
1285 gint
e_str_case_compare(gconstpointer x,gconstpointer y)1286 e_str_case_compare (gconstpointer x,
1287 gconstpointer y)
1288 {
1289 gchar *cx, *cy;
1290 gint res;
1291
1292 if (x == NULL || y == NULL) {
1293 if (x == y)
1294 return 0;
1295 else
1296 return x ? -1 : 1;
1297 }
1298
1299 cx = g_utf8_casefold (x, -1);
1300 cy = g_utf8_casefold (y, -1);
1301
1302 res = g_utf8_collate (cx, cy);
1303
1304 g_free (cx);
1305 g_free (cy);
1306
1307 return res;
1308 }
1309
1310 gint
e_collate_compare(gconstpointer x,gconstpointer y)1311 e_collate_compare (gconstpointer x,
1312 gconstpointer y)
1313 {
1314 if (x == NULL || y == NULL) {
1315 if (x == y)
1316 return 0;
1317 else
1318 return x ? -1 : 1;
1319 }
1320
1321 return g_utf8_collate (x, y);
1322 }
1323
1324 gint
e_int_compare(gconstpointer x,gconstpointer y)1325 e_int_compare (gconstpointer x,
1326 gconstpointer y)
1327 {
1328 gint nx = GPOINTER_TO_INT (x);
1329 gint ny = GPOINTER_TO_INT (y);
1330
1331 return (nx == ny) ? 0 : (nx < ny) ? -1 : 1;
1332 }
1333
1334 /**
1335 * e_color_to_value:
1336 * @color: a #GdkColor
1337 *
1338 * Converts a #GdkColor to a 24-bit RGB color value.
1339 *
1340 * Returns: a 24-bit color value
1341 **/
1342 guint32
e_color_to_value(const GdkColor * color)1343 e_color_to_value (const GdkColor *color)
1344 {
1345 GdkRGBA rgba;
1346
1347 g_return_val_if_fail (color != NULL, 0);
1348
1349 rgba.red = color->red / 65535.0;
1350 rgba.green = color->green / 65535.0;
1351 rgba.blue = color->blue / 65535.0;
1352 rgba.alpha = 0.0;
1353
1354 return e_rgba_to_value (&rgba);
1355 }
1356
1357 /**
1358 * e_rgba_to_value:
1359 * @rgba: a #GdkRGBA
1360 *
1361 * Converts #GdkRGBA to a 24-bit RGB color value
1362 *
1363 * Returns: a 24-bit color value
1364 **/
1365 guint32
e_rgba_to_value(const GdkRGBA * rgba)1366 e_rgba_to_value (const GdkRGBA *rgba)
1367 {
1368 guint16 red;
1369 guint16 green;
1370 guint16 blue;
1371
1372 g_return_val_if_fail (rgba != NULL, 0);
1373
1374 red = 255 * rgba->red;
1375 green = 255 * rgba->green;
1376 blue = 255 * rgba->blue;
1377
1378 return (guint32)
1379 ((((red & 0xFFu) << 16) |
1380 ((green & 0xFFu) << 8) |
1381 (blue & 0xFFu)) & 0xffffffu);
1382 }
1383
1384 /**
1385 * e_rgba_to_color:
1386 * @rgba: a source #GdkRGBA
1387 * @color: a destination #GdkColor
1388 *
1389 * Converts @rgba into @color, but loses the alpha channel from @rgba.
1390 **/
1391 void
e_rgba_to_color(const GdkRGBA * rgba,GdkColor * color)1392 e_rgba_to_color (const GdkRGBA *rgba,
1393 GdkColor *color)
1394 {
1395 g_return_if_fail (rgba != NULL);
1396 g_return_if_fail (color != NULL);
1397
1398 color->pixel = 0;
1399 color->red = rgba->red * 65535.0;
1400 color->green = rgba->green * 65535.0;
1401 color->blue = rgba->blue * 65535.0;
1402 }
1403
1404 /**
1405 * e_utils_get_theme_color:
1406 * @widget: a #GtkWidget instance
1407 * @color_names: comma-separated theme color names
1408 * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse()
1409 * @rgba: where to store the read color
1410 *
1411 * Reads named theme color from a #GtkStyleContext of @widget.
1412 * The @color_names are read one after another from left to right,
1413 * the next are meant as fallbacks, in case the theme doesn't
1414 * define the previous color. If none is found then the @fallback_color_ident
1415 * is set to @rgba.
1416 **/
1417 void
e_utils_get_theme_color(GtkWidget * widget,const gchar * color_names,const gchar * fallback_color_ident,GdkRGBA * rgba)1418 e_utils_get_theme_color (GtkWidget *widget,
1419 const gchar *color_names,
1420 const gchar *fallback_color_ident,
1421 GdkRGBA *rgba)
1422 {
1423 GtkStyleContext *style_context;
1424 gchar **names;
1425 gint ii;
1426
1427 g_return_if_fail (GTK_IS_WIDGET (widget));
1428 g_return_if_fail (color_names != NULL);
1429 g_return_if_fail (fallback_color_ident != NULL);
1430 g_return_if_fail (rgba != NULL);
1431
1432 style_context = gtk_widget_get_style_context (widget);
1433
1434 names = g_strsplit (color_names, ",", -1);
1435 for (ii = 0; names && names[ii]; ii++) {
1436 if (gtk_style_context_lookup_color (style_context, names[ii], rgba)) {
1437 g_strfreev (names);
1438 return;
1439 }
1440 }
1441
1442 g_strfreev (names);
1443
1444 g_warn_if_fail (gdk_rgba_parse (rgba, fallback_color_ident));
1445 }
1446
1447 /**
1448 * e_utils_get_theme_color_color:
1449 * @widget: a #GtkWidget instance
1450 * @color_names: comma-separated theme color names
1451 * @fallback_color_ident: fallback color identificator, in a format for gdk_rgba_parse()
1452 * @color: where to store the read color
1453 *
1454 * The same as e_utils_get_theme_color(), only populates #GdkColor,
1455 * instead of #GdkRGBA.
1456 **/
1457 void
e_utils_get_theme_color_color(GtkWidget * widget,const gchar * color_names,const gchar * fallback_color_ident,GdkColor * color)1458 e_utils_get_theme_color_color (GtkWidget *widget,
1459 const gchar *color_names,
1460 const gchar *fallback_color_ident,
1461 GdkColor *color)
1462 {
1463 GdkRGBA rgba;
1464
1465 g_return_if_fail (GTK_IS_WIDGET (widget));
1466 g_return_if_fail (color_names != NULL);
1467 g_return_if_fail (fallback_color_ident != NULL);
1468 g_return_if_fail (color != NULL);
1469
1470 e_utils_get_theme_color (widget, color_names, fallback_color_ident, &rgba);
1471
1472 e_rgba_to_color (&rgba, color);
1473 }
1474
1475 /* This is copied from gtk+ sources */
1476 static void
rgb_to_hls(gdouble * r,gdouble * g,gdouble * b)1477 rgb_to_hls (gdouble *r,
1478 gdouble *g,
1479 gdouble *b)
1480 {
1481 gdouble min;
1482 gdouble max;
1483 gdouble red;
1484 gdouble green;
1485 gdouble blue;
1486 gdouble h, l, s;
1487 gdouble delta;
1488
1489 red = *r;
1490 green = *g;
1491 blue = *b;
1492
1493 if (red > green) {
1494 if (red > blue)
1495 max = red;
1496 else
1497 max = blue;
1498
1499 if (green < blue)
1500 min = green;
1501 else
1502 min = blue;
1503 } else {
1504 if (green > blue)
1505 max = green;
1506 else
1507 max = blue;
1508
1509 if (red < blue)
1510 min = red;
1511 else
1512 min = blue;
1513 }
1514
1515 l = (max + min) / 2;
1516 s = 0;
1517 h = 0;
1518
1519 if (max != min) {
1520 if (l <= 0.5)
1521 s = (max - min) / (max + min);
1522 else
1523 s = (max - min) / (2 - max - min);
1524
1525 delta = max -min;
1526 if (red == max)
1527 h = (green - blue) / delta;
1528 else if (green == max)
1529 h = 2 + (blue - red) / delta;
1530 else if (blue == max)
1531 h = 4 + (red - green) / delta;
1532
1533 h *= 60;
1534 if (h < 0.0)
1535 h += 360;
1536 }
1537
1538 *r = h;
1539 *g = l;
1540 *b = s;
1541 }
1542
1543 /* This is copied from gtk+ sources */
1544 static void
hls_to_rgb(gdouble * h,gdouble * l,gdouble * s)1545 hls_to_rgb (gdouble *h,
1546 gdouble *l,
1547 gdouble *s)
1548 {
1549 gdouble hue;
1550 gdouble lightness;
1551 gdouble saturation;
1552 gdouble m1, m2;
1553 gdouble r, g, b;
1554
1555 lightness = *l;
1556 saturation = *s;
1557
1558 if (lightness <= 0.5)
1559 m2 = lightness * (1 + saturation);
1560 else
1561 m2 = lightness + saturation - lightness * saturation;
1562 m1 = 2 * lightness - m2;
1563
1564 if (saturation == 0) {
1565 *h = lightness;
1566 *l = lightness;
1567 *s = lightness;
1568 } else {
1569 hue = *h + 120;
1570 while (hue > 360)
1571 hue -= 360;
1572 while (hue < 0)
1573 hue += 360;
1574
1575 if (hue < 60)
1576 r = m1 + (m2 - m1) * hue / 60;
1577 else if (hue < 180)
1578 r = m2;
1579 else if (hue < 240)
1580 r = m1 + (m2 - m1) * (240 - hue) / 60;
1581 else
1582 r = m1;
1583
1584 hue = *h;
1585 while (hue > 360)
1586 hue -= 360;
1587 while (hue < 0)
1588 hue += 360;
1589
1590 if (hue < 60)
1591 g = m1 + (m2 - m1) * hue / 60;
1592 else if (hue < 180)
1593 g = m2;
1594 else if (hue < 240)
1595 g = m1 + (m2 - m1) * (240 - hue) / 60;
1596 else
1597 g = m1;
1598
1599 hue = *h - 120;
1600 while (hue > 360)
1601 hue -= 360;
1602 while (hue < 0)
1603 hue += 360;
1604
1605 if (hue < 60)
1606 b = m1 + (m2 - m1) * hue / 60;
1607 else if (hue < 180)
1608 b = m2;
1609 else if (hue < 240)
1610 b = m1 + (m2 - m1) * (240 - hue) / 60;
1611 else
1612 b = m1;
1613
1614 *h = r;
1615 *l = g;
1616 *s = b;
1617 }
1618 }
1619
1620 /* This is copied from gtk+ sources */
1621 void
e_utils_shade_color(const GdkRGBA * a,GdkRGBA * b,gdouble mult)1622 e_utils_shade_color (const GdkRGBA *a,
1623 GdkRGBA *b,
1624 gdouble mult)
1625 {
1626 gdouble red;
1627 gdouble green;
1628 gdouble blue;
1629
1630 g_return_if_fail (a != NULL);
1631 g_return_if_fail (b != NULL);
1632
1633 red = a->red;
1634 green = a->green;
1635 blue = a->blue;
1636
1637 rgb_to_hls (&red, &green, &blue);
1638
1639 green *= mult;
1640 if (green > 1.0)
1641 green = 1.0;
1642 else if (green < 0.0)
1643 green = 0.0;
1644
1645 blue *= mult;
1646 if (blue > 1.0)
1647 blue = 1.0;
1648 else if (blue < 0.0)
1649 blue = 0.0;
1650
1651 hls_to_rgb (&red, &green, &blue);
1652
1653 b->red = red;
1654 b->green = green;
1655 b->blue = blue;
1656 b->alpha = a->alpha;
1657 }
1658
1659 GdkRGBA
e_utils_get_text_color_for_background(const GdkRGBA * bg_rgba)1660 e_utils_get_text_color_for_background (const GdkRGBA *bg_rgba)
1661 {
1662 GdkRGBA text_rgba = { 0.0, 0.0, 0.0, 1.0 };
1663 gdouble brightness;
1664
1665 g_return_val_if_fail (bg_rgba != NULL, text_rgba);
1666
1667 brightness =
1668 (0.2109 * 255.0 * bg_rgba->red) +
1669 (0.5870 * 255.0 * bg_rgba->green) +
1670 (0.1021 * 255.0 * bg_rgba->blue);
1671
1672 if (brightness <= 140.0) {
1673 text_rgba.red = 1.0;
1674 text_rgba.green = 1.0;
1675 text_rgba.blue = 1.0;
1676 } else {
1677 text_rgba.red = 0.0;
1678 text_rgba.green = 0.0;
1679 text_rgba.blue = 0.0;
1680 }
1681
1682 text_rgba.alpha = 1.0;
1683
1684 return text_rgba;
1685 }
1686
1687 static gint
epow10(gint number)1688 epow10 (gint number)
1689 {
1690 gint value = 1;
1691
1692 while (number-- > 0)
1693 value *= 10;
1694
1695 return value;
1696 }
1697
1698 gchar *
e_format_number(gint number)1699 e_format_number (gint number)
1700 {
1701 GList *iterator, *list = NULL;
1702 struct lconv *locality;
1703 gint char_length = 0;
1704 gint group_count = 0;
1705 gchar *grouping;
1706 gint last_count = 3;
1707 gint divider;
1708 gchar *value;
1709 gchar *value_iterator;
1710
1711 locality = localeconv ();
1712 grouping = locality->grouping;
1713 while (number) {
1714 gchar *group;
1715 switch (*grouping) {
1716 default:
1717 last_count = *grouping;
1718 grouping++;
1719 /* coverity[fallthrough] */
1720 /* falls through */
1721 case 0:
1722 divider = epow10 (last_count);
1723 if (number >= divider) {
1724 group = g_strdup_printf (
1725 "%0*d", last_count, number % divider);
1726 } else {
1727 group = g_strdup_printf (
1728 "%d", number % divider);
1729 }
1730 number /= divider;
1731 break;
1732 case CHAR_MAX:
1733 group = g_strdup_printf ("%d", number);
1734 number = 0;
1735 break;
1736 }
1737 char_length += strlen (group);
1738 list = g_list_prepend (list, group);
1739 group_count++;
1740 }
1741
1742 if (list) {
1743 value = g_new (
1744 gchar, 1 + char_length + (group_count - 1) *
1745 strlen (locality->thousands_sep));
1746
1747 iterator = list;
1748 value_iterator = value;
1749
1750 strcpy (value_iterator, iterator->data);
1751 value_iterator += strlen (iterator->data);
1752 for (iterator = iterator->next; iterator; iterator = iterator->next) {
1753 strcpy (value_iterator, locality->thousands_sep);
1754 value_iterator += strlen (locality->thousands_sep);
1755
1756 strcpy (value_iterator, iterator->data);
1757 value_iterator += strlen (iterator->data);
1758 }
1759 g_list_foreach (list, (GFunc) g_free, NULL);
1760 g_list_free (list);
1761 return value;
1762 } else {
1763 return g_strdup ("0");
1764 }
1765 }
1766
1767 /* Perform a binary search for key in base which has nmemb elements
1768 * of size bytes each. The comparisons are done by (*compare)(). */
1769 void
e_bsearch(gconstpointer key,gconstpointer base,gsize nmemb,gsize size,ESortCompareFunc compare,gpointer closure,gsize * start,gsize * end)1770 e_bsearch (gconstpointer key,
1771 gconstpointer base,
1772 gsize nmemb,
1773 gsize size,
1774 ESortCompareFunc compare,
1775 gpointer closure,
1776 gsize *start,
1777 gsize *end)
1778 {
1779 gsize l, u, idx;
1780 gconstpointer p;
1781 gint comparison;
1782 if (!(start || end))
1783 return;
1784
1785 l = 0;
1786 u = nmemb;
1787 while (l < u) {
1788 idx = (l + u) / 2;
1789 p = (((const gchar *) base) + (idx * size));
1790 comparison = (*compare) (key, p, closure);
1791 if (comparison < 0)
1792 u = idx;
1793 else if (comparison > 0)
1794 l = idx + 1;
1795 else {
1796 gsize lsave, usave;
1797 lsave = l;
1798 usave = u;
1799 if (start) {
1800 while (l < u) {
1801 idx = (l + u) / 2;
1802 p = (((const gchar *) base) + (idx * size));
1803 comparison = (*compare) (key, p, closure);
1804 if (comparison <= 0)
1805 u = idx;
1806 else
1807 l = idx + 1;
1808 }
1809 *start = l;
1810
1811 l = lsave;
1812 u = usave;
1813 }
1814 if (end) {
1815 while (l < u) {
1816 idx = (l + u) / 2;
1817 p = (((const gchar *) base) + (idx * size));
1818 comparison = (*compare) (key, p, closure);
1819 if (comparison < 0)
1820 u = idx;
1821 else
1822 l = idx + 1;
1823 }
1824 *end = l;
1825 }
1826 return;
1827 }
1828 }
1829
1830 if (start)
1831 *start = l;
1832 if (end)
1833 *end = l;
1834 }
1835
1836 /* Function to do a last minute fixup of the AM/PM stuff if the locale
1837 * and gettext haven't done it right. Most English speaking countries
1838 * except the USA use the 24 hour clock (UK, Australia etc). However
1839 * since they are English nobody bothers to write a language
1840 * translation (gettext) file. So the locale turns off the AM/PM, but
1841 * gettext does not turn on the 24 hour clock. Leaving a mess.
1842 *
1843 * This routine checks if AM/PM are defined in the locale, if not it
1844 * forces the use of the 24 hour clock.
1845 *
1846 * The function itself is a front end on strftime and takes exactly
1847 * the same arguments.
1848 *
1849 * TODO: Actually remove the '%p' from the fixed up string so that
1850 * there isn't a stray space.
1851 */
1852
1853 gsize
e_strftime_fix_am_pm(gchar * str,gsize max,const gchar * fmt,const struct tm * tm)1854 e_strftime_fix_am_pm (gchar *str,
1855 gsize max,
1856 const gchar *fmt,
1857 const struct tm *tm)
1858 {
1859 gchar buf[10];
1860 gchar *sp;
1861 gchar *ffmt;
1862 gsize ret;
1863
1864 if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) {
1865 /* No AM/PM involved - can use the fmt string directly */
1866 ret = e_strftime (str, max, fmt, tm);
1867 } else {
1868 /* Get the AM/PM symbol from the locale */
1869 e_strftime (buf, 10, "%p", tm);
1870
1871 if (buf[0]) {
1872 /* AM/PM have been defined in the locale
1873 * so we can use the fmt string directly. */
1874 ret = e_strftime (str, max, fmt, tm);
1875 } else {
1876 /* No AM/PM defined by locale
1877 * must change to 24 hour clock. */
1878 ffmt = g_strdup (fmt);
1879 for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) {
1880 /* Maybe this should be 'k', but I have never
1881 * seen a 24 clock actually use that format. */
1882 sp[1]='H';
1883 }
1884 for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) {
1885 sp[1]='H';
1886 }
1887 ret = e_strftime (str, max, ffmt, tm);
1888 g_free (ffmt);
1889 }
1890 }
1891
1892 return (ret);
1893 }
1894
1895 gsize
e_utf8_strftime_fix_am_pm(gchar * str,gsize max,const gchar * fmt,const struct tm * tm)1896 e_utf8_strftime_fix_am_pm (gchar *str,
1897 gsize max,
1898 const gchar *fmt,
1899 const struct tm *tm)
1900 {
1901 gsize sz, ret;
1902 gchar *locale_fmt, *buf;
1903
1904 locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL);
1905 if (!locale_fmt)
1906 return 0;
1907
1908 ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm);
1909 if (!ret) {
1910 g_free (locale_fmt);
1911 return 0;
1912 }
1913
1914 buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL);
1915 if (!buf) {
1916 g_free (locale_fmt);
1917 return 0;
1918 }
1919
1920 if (sz >= max) {
1921 gchar *tmp = buf + max - 1;
1922 tmp = g_utf8_find_prev_char (buf, tmp);
1923 if (tmp)
1924 sz = tmp - buf;
1925 else
1926 sz = 0;
1927 }
1928 memcpy (str, buf, sz);
1929 str[sz] = '\0';
1930 g_free (locale_fmt);
1931 g_free (buf);
1932 return sz;
1933 }
1934
1935 /**
1936 * e_utf8_strftime_match_lc_messages:
1937 * @string: The string to store the result in.
1938 * @max: The size of the @string.
1939 * @fmt: The formatting to use on @tm.
1940 * @tm: The time value to format.
1941 *
1942 * The UTF-8 equivalent of e_strftime (), which also
1943 * makes sure that the locale used for time and date
1944 * formatting matches the locale used by the
1945 * application so that, for example, the quoted
1946 * message header produced by the mail composer in a
1947 * reply uses only one locale (i.e. LC_MESSAGES, where
1948 * available, overrides LC_TIME for consistency).
1949 *
1950 * Returns: The number of characters placed in @string.
1951 *
1952 * Since: 3.22
1953 **/
1954 gsize
e_utf8_strftime_match_lc_messages(gchar * string,gsize max,const gchar * fmt,const struct tm * tm)1955 e_utf8_strftime_match_lc_messages (gchar *string,
1956 gsize max,
1957 const gchar *fmt,
1958 const struct tm *tm)
1959 {
1960 gsize ret;
1961 #if defined(LC_MESSAGES) && defined(LC_TIME)
1962 gchar *ctime, *cmessages, *saved_locale;
1963
1964 /* Use LC_MESSAGES instead of LC_TIME for time
1965 * formatting (for consistency).
1966 */
1967 ctime = setlocale (LC_TIME, NULL);
1968 saved_locale = g_strdup (ctime);
1969 g_return_val_if_fail (saved_locale != NULL, 0);
1970 cmessages = setlocale (LC_MESSAGES, NULL);
1971 setlocale (LC_TIME, cmessages);
1972 #endif
1973
1974 ret = e_utf8_strftime(string, max, fmt, tm);
1975
1976 #if defined(LC_MESSAGES) && defined(LC_TIME)
1977 /* Restore LC_TIME, if it has been changed to match
1978 * LC_MESSAGES.
1979 */
1980 setlocale (LC_TIME, saved_locale);
1981 g_free (saved_locale);
1982 #endif
1983
1984 return ret;
1985 }
1986
1987 /**
1988 * e_get_month_name:
1989 * @month: month index
1990 * @abbreviated: if %TRUE, abbreviate the month name
1991 *
1992 * Returns the localized name for @month. If @abbreviated is %TRUE,
1993 * returns the locale's abbreviated month name.
1994 *
1995 * Returns: localized month name
1996 **/
1997 const gchar *
e_get_month_name(GDateMonth month,gboolean abbreviated)1998 e_get_month_name (GDateMonth month,
1999 gboolean abbreviated)
2000 {
2001 /* Make the indices correspond to the enum values. */
2002 static const gchar *abbr_names[G_DATE_DECEMBER + 1];
2003 static const gchar *full_names[G_DATE_DECEMBER + 1];
2004 static gboolean first_time = TRUE;
2005
2006 g_return_val_if_fail (month >= G_DATE_JANUARY, NULL);
2007 g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL);
2008
2009 if (G_UNLIKELY (first_time)) {
2010 gchar buffer[256];
2011 GDateMonth ii;
2012 GDate date;
2013
2014 memset (abbr_names, 0, sizeof (abbr_names));
2015 memset (full_names, 0, sizeof (full_names));
2016
2017 /* First Julian day was in January. */
2018 g_date_set_julian (&date, 1);
2019
2020 for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) {
2021 g_date_strftime (buffer, sizeof (buffer), "%b", &date);
2022 abbr_names[ii] = g_intern_string (buffer);
2023 g_date_strftime (buffer, sizeof (buffer), "%B", &date);
2024 full_names[ii] = g_intern_string (buffer);
2025 g_date_add_months (&date, 1);
2026 }
2027
2028 first_time = FALSE;
2029 }
2030
2031 return abbreviated ? abbr_names[month] : full_names[month];
2032 }
2033
2034 /**
2035 * e_get_weekday_name:
2036 * @weekday: weekday index
2037 * @abbreviated: if %TRUE, abbreviate the weekday name
2038 *
2039 * Returns the localized name for @weekday. If @abbreviated is %TRUE,
2040 * returns the locale's abbreviated weekday name.
2041 *
2042 * Returns: localized weekday name
2043 **/
2044 const gchar *
e_get_weekday_name(GDateWeekday weekday,gboolean abbreviated)2045 e_get_weekday_name (GDateWeekday weekday,
2046 gboolean abbreviated)
2047 {
2048 /* Make the indices correspond to the enum values. */
2049 static const gchar *abbr_names[G_DATE_SUNDAY + 1];
2050 static const gchar *full_names[G_DATE_SUNDAY + 1];
2051 static gboolean first_time = TRUE;
2052
2053 g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL);
2054 g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL);
2055
2056 if (G_UNLIKELY (first_time)) {
2057 gchar buffer[256];
2058 GDateWeekday ii;
2059 GDate date;
2060
2061 memset (abbr_names, 0, sizeof (abbr_names));
2062 memset (full_names, 0, sizeof (full_names));
2063
2064 /* First Julian day was a Monday. */
2065 g_date_set_julian (&date, 1);
2066
2067 for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) {
2068 g_date_strftime (buffer, sizeof (buffer), "%a", &date);
2069 abbr_names[ii] = g_intern_string (buffer);
2070 g_date_strftime (buffer, sizeof (buffer), "%A", &date);
2071 full_names[ii] = g_intern_string (buffer);
2072 g_date_add_days (&date, 1);
2073 }
2074
2075 first_time = FALSE;
2076 }
2077
2078 return abbreviated ? abbr_names[weekday] : full_names[weekday];
2079 }
2080
2081 /**
2082 * e_weekday_get_next:
2083 * @weekday: a #GDateWeekday
2084 *
2085 * Returns the #GDateWeekday after @weekday.
2086 *
2087 * Returns: the day after @weekday
2088 **/
2089 GDateWeekday
e_weekday_get_next(GDateWeekday weekday)2090 e_weekday_get_next (GDateWeekday weekday)
2091 {
2092 GDateWeekday next;
2093
2094 /* Verbose for readability. */
2095 switch (weekday) {
2096 case G_DATE_MONDAY:
2097 next = G_DATE_TUESDAY;
2098 break;
2099 case G_DATE_TUESDAY:
2100 next = G_DATE_WEDNESDAY;
2101 break;
2102 case G_DATE_WEDNESDAY:
2103 next = G_DATE_THURSDAY;
2104 break;
2105 case G_DATE_THURSDAY:
2106 next = G_DATE_FRIDAY;
2107 break;
2108 case G_DATE_FRIDAY:
2109 next = G_DATE_SATURDAY;
2110 break;
2111 case G_DATE_SATURDAY:
2112 next = G_DATE_SUNDAY;
2113 break;
2114 case G_DATE_SUNDAY:
2115 next = G_DATE_MONDAY;
2116 break;
2117 default:
2118 next = G_DATE_BAD_WEEKDAY;
2119 break;
2120 }
2121
2122 return next;
2123 }
2124
2125 /**
2126 * e_weekday_get_prev:
2127 * @weekday: a #GDateWeekday
2128 *
2129 * Returns the #GDateWeekday before @weekday.
2130 *
2131 * Returns: the day before @weekday
2132 **/
2133 GDateWeekday
e_weekday_get_prev(GDateWeekday weekday)2134 e_weekday_get_prev (GDateWeekday weekday)
2135 {
2136 GDateWeekday prev;
2137
2138 /* Verbose for readability. */
2139 switch (weekday) {
2140 case G_DATE_MONDAY:
2141 prev = G_DATE_SUNDAY;
2142 break;
2143 case G_DATE_TUESDAY:
2144 prev = G_DATE_MONDAY;
2145 break;
2146 case G_DATE_WEDNESDAY:
2147 prev = G_DATE_TUESDAY;
2148 break;
2149 case G_DATE_THURSDAY:
2150 prev = G_DATE_WEDNESDAY;
2151 break;
2152 case G_DATE_FRIDAY:
2153 prev = G_DATE_THURSDAY;
2154 break;
2155 case G_DATE_SATURDAY:
2156 prev = G_DATE_FRIDAY;
2157 break;
2158 case G_DATE_SUNDAY:
2159 prev = G_DATE_SATURDAY;
2160 break;
2161 default:
2162 prev = G_DATE_BAD_WEEKDAY;
2163 break;
2164 }
2165
2166 return prev;
2167 }
2168
2169 /**
2170 * e_weekday_add_days:
2171 * @weekday: a #GDateWeekday
2172 * @n_days: number of days to add
2173 *
2174 * Increments @weekday by @n_days.
2175 *
2176 * Returns: a #GDateWeekday
2177 **/
2178 GDateWeekday
e_weekday_add_days(GDateWeekday weekday,guint n_days)2179 e_weekday_add_days (GDateWeekday weekday,
2180 guint n_days)
2181 {
2182 g_return_val_if_fail (
2183 g_date_valid_weekday (weekday),
2184 G_DATE_BAD_WEEKDAY);
2185
2186 n_days %= 7; /* Weekdays repeat every 7 days. */
2187
2188 while (n_days-- > 0)
2189 weekday = e_weekday_get_next (weekday);
2190
2191 return weekday;
2192 }
2193
2194 /**
2195 * e_weekday_subtract_days:
2196 * @weekday: a #GDateWeekday
2197 * @n_days: number of days to subtract
2198 *
2199 * Decrements @weekday by @n_days.
2200 *
2201 * Returns: a #GDateWeekday
2202 **/
2203 GDateWeekday
e_weekday_subtract_days(GDateWeekday weekday,guint n_days)2204 e_weekday_subtract_days (GDateWeekday weekday,
2205 guint n_days)
2206 {
2207 g_return_val_if_fail (
2208 g_date_valid_weekday (weekday),
2209 G_DATE_BAD_WEEKDAY);
2210
2211 n_days %= 7; /* Weekdays repeat every 7 days. */
2212
2213 while (n_days-- > 0)
2214 weekday = e_weekday_get_prev (weekday);
2215
2216 return weekday;
2217 }
2218
2219 /**
2220 * e_weekday_get_days_between:
2221 * @weekday1: a #GDateWeekday
2222 * @weekday2: a #GDateWeekday
2223 *
2224 * Counts the number of days starting at @weekday1 and ending at @weekday2.
2225 *
2226 * Returns: the number of days
2227 **/
2228 guint
e_weekday_get_days_between(GDateWeekday weekday1,GDateWeekday weekday2)2229 e_weekday_get_days_between (GDateWeekday weekday1,
2230 GDateWeekday weekday2)
2231 {
2232 guint n_days = 0;
2233
2234 g_return_val_if_fail (g_date_valid_weekday (weekday1), 0);
2235 g_return_val_if_fail (g_date_valid_weekday (weekday2), 0);
2236
2237 while (weekday1 != weekday2) {
2238 n_days++;
2239 weekday1 = e_weekday_get_next (weekday1);
2240 }
2241
2242 return n_days;
2243 }
2244
2245 /**
2246 * e_weekday_to_tm_wday:
2247 * @weekday: a #GDateWeekday
2248 *
2249 * Converts a #GDateWeekday to the numbering used in
2250 * <structname>struct tm</structname>.
2251 *
2252 * Returns: number of days since Sunday (0 - 6)
2253 **/
2254 gint
e_weekday_to_tm_wday(GDateWeekday weekday)2255 e_weekday_to_tm_wday (GDateWeekday weekday)
2256 {
2257 gint tm_wday;
2258
2259 switch (weekday) {
2260 case G_DATE_MONDAY:
2261 tm_wday = 1;
2262 break;
2263 case G_DATE_TUESDAY:
2264 tm_wday = 2;
2265 break;
2266 case G_DATE_WEDNESDAY:
2267 tm_wday = 3;
2268 break;
2269 case G_DATE_THURSDAY:
2270 tm_wday = 4;
2271 break;
2272 case G_DATE_FRIDAY:
2273 tm_wday = 5;
2274 break;
2275 case G_DATE_SATURDAY:
2276 tm_wday = 6;
2277 break;
2278 case G_DATE_SUNDAY:
2279 tm_wday = 0;
2280 break;
2281 default:
2282 g_return_val_if_reached (-1);
2283 }
2284
2285 return tm_wday;
2286 }
2287
2288 /**
2289 * e_weekday_from_tm_wday:
2290 * @tm_wday: number of days since Sunday (0 - 6)
2291 *
2292 * Converts a weekday in the numbering used in
2293 * <structname>struct tm</structname> to a #GDateWeekday.
2294 *
2295 * Returns: a #GDateWeekday
2296 **/
2297 GDateWeekday
e_weekday_from_tm_wday(gint tm_wday)2298 e_weekday_from_tm_wday (gint tm_wday)
2299 {
2300 GDateWeekday weekday;
2301
2302 switch (tm_wday) {
2303 case 0:
2304 weekday = G_DATE_SUNDAY;
2305 break;
2306 case 1:
2307 weekday = G_DATE_MONDAY;
2308 break;
2309 case 2:
2310 weekday = G_DATE_TUESDAY;
2311 break;
2312 case 3:
2313 weekday = G_DATE_WEDNESDAY;
2314 break;
2315 case 4:
2316 weekday = G_DATE_THURSDAY;
2317 break;
2318 case 5:
2319 weekday = G_DATE_FRIDAY;
2320 break;
2321 case 6:
2322 weekday = G_DATE_SATURDAY;
2323 break;
2324 default:
2325 g_return_val_if_reached (G_DATE_BAD_WEEKDAY);
2326 }
2327
2328 return weekday;
2329 }
2330
2331 /* Evolution Locks for crash recovery */
2332 static const gchar *
get_lock_filename(void)2333 get_lock_filename (void)
2334 {
2335 static gchar *filename = NULL;
2336
2337 if (G_UNLIKELY (filename == NULL))
2338 filename = g_build_filename (
2339 e_get_user_config_dir (), ".running", NULL);
2340
2341 return filename;
2342 }
2343
2344 gboolean
e_file_lock_create(void)2345 e_file_lock_create (void)
2346 {
2347 const gchar *filename = get_lock_filename ();
2348 gboolean status = FALSE;
2349 FILE *file;
2350
2351 file = g_fopen (filename, "w");
2352 if (file != NULL) {
2353 /* The lock file also serves as a PID file. */
2354 g_fprintf (
2355 file, "%" G_GINT64_FORMAT "\n",
2356 (gint64) getpid ());
2357 fclose (file);
2358 status = TRUE;
2359 } else {
2360 const gchar *errmsg = g_strerror (errno);
2361 g_warning ("Lock file creation failed: %s", errmsg);
2362 }
2363
2364 return status;
2365 }
2366
2367 void
e_file_lock_destroy(void)2368 e_file_lock_destroy (void)
2369 {
2370 const gchar *filename = get_lock_filename ();
2371
2372 if (g_unlink (filename) == -1) {
2373 const gchar *errmsg = g_strerror (errno);
2374 g_warning ("Lock file deletion failed: %s", errmsg);
2375 }
2376 }
2377
2378 gboolean
e_file_lock_exists(void)2379 e_file_lock_exists (void)
2380 {
2381 const gchar *filename = get_lock_filename ();
2382
2383 return g_file_test (filename, G_FILE_TEST_EXISTS);
2384 }
2385
2386 /* Returns a PID stored in the lock file; 0 if no such file exists. */
2387 GPid
e_file_lock_get_pid(void)2388 e_file_lock_get_pid (void)
2389 {
2390 const gchar *filename = get_lock_filename ();
2391 gchar *contents = NULL;
2392 GPid pid = (GPid) 0;
2393 gint64 n_int64;
2394
2395 if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
2396 return pid;
2397 }
2398
2399 /* Try to extract an integer value from the string. */
2400 n_int64 = g_ascii_strtoll (contents, NULL, 10);
2401 if (n_int64 > 0 && n_int64 < G_MAXINT64) {
2402 /* XXX Probably not portable. */
2403 pid = (GPid) n_int64;
2404 }
2405
2406 g_free (contents);
2407
2408 return pid;
2409 }
2410
2411 /**
2412 * e_util_guess_mime_type:
2413 * @filename: a local file name, or URI
2414 * @localfile: %TRUE to check the file content, FALSE to check only the name
2415 *
2416 * Tries to determine the MIME type for @filename. Free the returned
2417 * string with g_free().
2418 *
2419 * Returns: the MIME type of @filename, or %NULL if the MIME type could
2420 * not be determined
2421 **/
2422 gchar *
e_util_guess_mime_type(const gchar * filename,gboolean localfile)2423 e_util_guess_mime_type (const gchar *filename,
2424 gboolean localfile)
2425 {
2426 gchar *mime_type = NULL;
2427
2428 g_return_val_if_fail (filename != NULL, NULL);
2429
2430 if (localfile) {
2431 GFile *file;
2432 GFileInfo *fi;
2433
2434 if (strstr (filename, "://"))
2435 file = g_file_new_for_uri (filename);
2436 else
2437 file = g_file_new_for_path (filename);
2438
2439 fi = g_file_query_info (
2440 file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
2441 G_FILE_QUERY_INFO_NONE, NULL, NULL);
2442 if (fi) {
2443 mime_type = g_content_type_get_mime_type (
2444 g_file_info_get_content_type (fi));
2445 g_object_unref (fi);
2446 }
2447
2448 g_object_unref (file);
2449 }
2450
2451 if (!mime_type) {
2452 /* file doesn't exists locally, thus guess based on the filename */
2453 gboolean uncertain = FALSE;
2454 gchar *content_type;
2455
2456 content_type = g_content_type_guess (filename, NULL, 0, &uncertain);
2457 if (content_type) {
2458 mime_type = g_content_type_get_mime_type (content_type);
2459 g_free (content_type);
2460 }
2461 }
2462
2463 return mime_type;
2464 }
2465
2466 GSList *
e_util_get_category_filter_options(void)2467 e_util_get_category_filter_options (void)
2468 {
2469 GSList *res = NULL;
2470 GList *clist, *l;
2471
2472 clist = e_categories_dup_list ();
2473 for (l = clist; l; l = l->next) {
2474 const gchar *cname = l->data;
2475 struct _filter_option *fo;
2476
2477 if (!e_categories_is_searchable (cname))
2478 continue;
2479
2480 fo = g_new0 (struct _filter_option, 1);
2481
2482 fo->title = g_strdup (cname);
2483 fo->value = g_strdup (cname);
2484 res = g_slist_prepend (res, fo);
2485 }
2486
2487 g_list_free_full (clist, g_free);
2488
2489 return g_slist_reverse (res);
2490 }
2491
2492 /**
2493 * e_util_dup_searchable_categories:
2494 *
2495 * Returns a list of the searchable categories, with each item being a UTF-8
2496 * category name. The list should be freed with g_list_free() when done with it,
2497 * and the items should be freed with g_free(). Everything can be freed at once
2498 * using g_list_free_full().
2499 *
2500 * Returns: (transfer full) (element-type utf8): a list of searchable category
2501 * names; free with g_list_free_full()
2502 */
2503 GList *
e_util_dup_searchable_categories(void)2504 e_util_dup_searchable_categories (void)
2505 {
2506 GList *res = NULL, *all_categories, *l;
2507
2508 all_categories = e_categories_dup_list ();
2509 for (l = all_categories; l; l = l->next) {
2510 gchar *cname = l->data;
2511
2512 /* Steal the string from e_categories_dup_list(). */
2513 if (e_categories_is_searchable (cname))
2514 res = g_list_prepend (res, (gpointer) cname);
2515 else
2516 g_free (cname);
2517 }
2518
2519 /* NOTE: Do *not* free the items. They have been freed or stolen
2520 * above. */
2521 g_list_free (all_categories);
2522
2523 return g_list_reverse (res);
2524 }
2525 /**
2526 * e_util_get_open_source_job_info:
2527 * @extension_name: an extension name of the source
2528 * @source_display_name: an ESource's display name
2529 * @description: (out) (transfer-full): a description to use
2530 * @alert_ident: (out) (transfer-full): an alert ident to use on failure
2531 * @alert_arg_0: (out) (transfer-full): an alert argument 0 to use on failure
2532 *
2533 * Populates @description, @alert_ident and @alert_arg_0 to be used
2534 * to open an #ESource with extension @extension_name. The values
2535 * can be used for functions like e_alert_sink_submit_thread_job().
2536 *
2537 * If #TRUE is returned, then the caller is responsible to free
2538 * all @description, @alert_ident and @alert_arg_0 with g_free(),
2539 * when no longer needed.
2540 *
2541 * Returns: #TRUE, if the values for @description, @alert_ident and @alert_arg_0
2542 * were set for the given @extension_name; when #FALSE is returned, then
2543 * none of these out variables are changed.
2544 *
2545 * Since: 3.16
2546 **/
2547 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)2548 e_util_get_open_source_job_info (const gchar *extension_name,
2549 const gchar *source_display_name,
2550 gchar **description,
2551 gchar **alert_ident,
2552 gchar **alert_arg_0)
2553 {
2554 g_return_val_if_fail (extension_name != NULL, FALSE);
2555 g_return_val_if_fail (source_display_name != NULL, FALSE);
2556 g_return_val_if_fail (description != NULL, FALSE);
2557 g_return_val_if_fail (alert_ident != NULL, FALSE);
2558 g_return_val_if_fail (alert_arg_0 != NULL, FALSE);
2559
2560 if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) {
2561 *alert_ident = g_strdup ("calendar:failed-open-calendar");
2562 *description = g_strdup_printf (_("Opening calendar “%s”"), source_display_name);
2563 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) {
2564 *alert_ident = g_strdup ("calendar:failed-open-memos");
2565 *description = g_strdup_printf (_("Opening memo list “%s”"), source_display_name);
2566 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) {
2567 *alert_ident = g_strdup ("calendar:failed-open-tasks");
2568 *description = g_strdup_printf (_("Opening task list “%s”"), source_display_name);
2569 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) {
2570 *alert_ident = g_strdup ("addressbook:load-error");
2571 *description = g_strdup_printf (_("Opening address book “%s”"), source_display_name);
2572 } else {
2573 return FALSE;
2574 }
2575
2576 *alert_arg_0 = g_strdup (source_display_name);
2577
2578 return TRUE;
2579 }
2580
2581 /**
2582 * e_util_propagate_open_source_job_error:
2583 * @job_data: an #EAlertSinkThreadJobData instance
2584 * @extension_name: what extension name had beeing opened
2585 * @local_error: (allow none): a #GError as obtained in a thread job; can be NULL for success
2586 * @error: (allow none): an output #GError, to which propagate the @local_error
2587 *
2588 * Propagates (and cosumes) the @local_error into the @error, eventually
2589 * changes alert_ident for the @job_data for well-known error codes,
2590 * where is available better error description.
2591 *
2592 * Since: 3.16
2593 **/
2594 void
e_util_propagate_open_source_job_error(EAlertSinkThreadJobData * job_data,const gchar * extension_name,GError * local_error,GError ** error)2595 e_util_propagate_open_source_job_error (EAlertSinkThreadJobData *job_data,
2596 const gchar *extension_name,
2597 GError *local_error,
2598 GError **error)
2599 {
2600 const gchar *alert_ident = NULL;
2601
2602 g_return_if_fail (job_data != NULL);
2603 g_return_if_fail (extension_name != NULL);
2604
2605 if (!local_error)
2606 return;
2607
2608 if (!error) {
2609 g_error_free (local_error);
2610 return;
2611 }
2612
2613 if (g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) {
2614 if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) {
2615 alert_ident = "calendar:prompt-no-contents-offline-calendar";
2616 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) {
2617 alert_ident = "calendar:prompt-no-contents-offline-memos";
2618 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) {
2619 alert_ident = "calendar:prompt-no-contents-offline-tasks";
2620 } else if (g_ascii_strcasecmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) {
2621 }
2622 }
2623
2624 if (alert_ident)
2625 e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident);
2626
2627 g_propagate_error (error, local_error);
2628 }
2629
2630 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)2631 e_util_open_client_sync (EAlertSinkThreadJobData *job_data,
2632 EClientCache *client_cache,
2633 const gchar *extension_name,
2634 ESource *source,
2635 guint32 wait_for_connected_seconds,
2636 GCancellable *cancellable,
2637 GError **error)
2638 {
2639 gchar *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL;
2640 EClient *client = NULL;
2641 ESourceRegistry *registry;
2642 gchar *display_name;
2643 GError *local_error = NULL;
2644
2645 registry = e_client_cache_ref_registry (client_cache);
2646 display_name = e_util_get_source_full_name (registry, source);
2647 g_clear_object (®istry);
2648
2649 g_warn_if_fail (e_util_get_open_source_job_info (extension_name,
2650 display_name, &description, &alert_ident, &alert_arg_0));
2651
2652 g_free (display_name);
2653
2654 camel_operation_push_message (cancellable, "%s", description);
2655
2656 client = e_client_cache_get_client_sync (client_cache, source, extension_name, wait_for_connected_seconds, cancellable, &local_error);
2657
2658 camel_operation_pop_message (cancellable);
2659
2660 if (!client) {
2661 e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident);
2662 e_alert_sink_thread_job_set_alert_arg_0 (job_data, alert_arg_0);
2663
2664 e_util_propagate_open_source_job_error (job_data, extension_name, local_error, error);
2665 }
2666
2667 g_free (description);
2668 g_free (alert_ident);
2669 g_free (alert_arg_0);
2670
2671 return client;
2672 }
2673
2674 /**
2675 * e_binding_transform_color_to_string:
2676 * @binding: a #GBinding
2677 * @source_value: a #GValue of type #GDK_TYPE_COLOR
2678 * @target_value: a #GValue of type #G_TYPE_STRING
2679 * @not_used: not used
2680 *
2681 * Transforms a #GdkColor value to a color string specification.
2682 *
2683 * Returns: %TRUE always
2684 **/
2685 gboolean
e_binding_transform_color_to_string(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)2686 e_binding_transform_color_to_string (GBinding *binding,
2687 const GValue *source_value,
2688 GValue *target_value,
2689 gpointer not_used)
2690 {
2691 const GdkColor *color;
2692 gchar *string;
2693
2694 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2695
2696 color = g_value_get_boxed (source_value);
2697 if (!color) {
2698 g_value_set_string (target_value, "");
2699 } else {
2700 /* encode color manually, because css styles expect colors in #rrggbb,
2701 * not in #rrrrggggbbbb, which is a result of gdk_color_to_string()
2702 */
2703 string = g_strdup_printf (
2704 "#%02x%02x%02x",
2705 (gint) color->red * 256 / 65536,
2706 (gint) color->green * 256 / 65536,
2707 (gint) color->blue * 256 / 65536);
2708 g_value_set_string (target_value, string);
2709 g_free (string);
2710 }
2711
2712 return TRUE;
2713 }
2714
2715 /**
2716 * e_binding_transform_string_to_color:
2717 * @binding: a #GBinding
2718 * @source_value: a #GValue of type #G_TYPE_STRING
2719 * @target_value: a #GValue of type #GDK_TYPE_COLOR
2720 * @not_used: not used
2721 *
2722 * Transforms a color string specification to a #GdkColor.
2723 *
2724 * Returns: %TRUE if color string specification was valid
2725 **/
2726 gboolean
e_binding_transform_string_to_color(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)2727 e_binding_transform_string_to_color (GBinding *binding,
2728 const GValue *source_value,
2729 GValue *target_value,
2730 gpointer not_used)
2731 {
2732 GdkColor color;
2733 const gchar *string;
2734 gboolean success = FALSE;
2735
2736 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2737
2738 string = g_value_get_string (source_value);
2739 if (gdk_color_parse (string, &color)) {
2740 g_value_set_boxed (target_value, &color);
2741 success = TRUE;
2742 }
2743
2744 return success;
2745 }
2746
2747 /**
2748 * e_binding_transform_source_to_uid:
2749 * @binding: a #GBinding
2750 * @source_value: a #GValue of type #E_TYPE_SOURCE
2751 * @target_value: a #GValue of type #G_TYPE_STRING
2752 * @registry: an #ESourceRegistry
2753 *
2754 * Transforms an #ESource object to its UID string.
2755 *
2756 * Returns: %TRUE if @source_value was an #ESource object
2757 **/
2758 gboolean
e_binding_transform_source_to_uid(GBinding * binding,const GValue * source_value,GValue * target_value,ESourceRegistry * registry)2759 e_binding_transform_source_to_uid (GBinding *binding,
2760 const GValue *source_value,
2761 GValue *target_value,
2762 ESourceRegistry *registry)
2763 {
2764 ESource *source;
2765 const gchar *string;
2766 gboolean success = FALSE;
2767
2768 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2769 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
2770
2771 source = g_value_get_object (source_value);
2772 if (E_IS_SOURCE (source)) {
2773 string = e_source_get_uid (source);
2774 g_value_set_string (target_value, string);
2775 success = TRUE;
2776 }
2777
2778 return success;
2779 }
2780
2781 /**
2782 * e_binding_transform_uid_to_source:
2783 * @binding: a #GBinding
2784 * @source_value: a #GValue of type #G_TYPE_STRING
2785 * @target_value: a #GValue of type #E_TYPE_SOURCe
2786 * @registry: an #ESourceRegistry
2787 *
2788 * Transforms an #ESource UID string to the corresponding #ESource object
2789 * in @registry.
2790 *
2791 * Returns: %TRUE if @registry had an #ESource object with a matching
2792 * UID string
2793 **/
2794 gboolean
e_binding_transform_uid_to_source(GBinding * binding,const GValue * source_value,GValue * target_value,ESourceRegistry * registry)2795 e_binding_transform_uid_to_source (GBinding *binding,
2796 const GValue *source_value,
2797 GValue *target_value,
2798 ESourceRegistry *registry)
2799 {
2800 ESource *source;
2801 const gchar *string;
2802 gboolean success = FALSE;
2803
2804 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2805 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
2806
2807 string = g_value_get_string (source_value);
2808 if (string == NULL || *string == '\0')
2809 return FALSE;
2810
2811 source = e_source_registry_ref_source (registry, string);
2812 if (source != NULL) {
2813 g_value_take_object (target_value, source);
2814 success = TRUE;
2815 }
2816
2817 return success;
2818 }
2819
2820 /**
2821 * e_binding_transform_text_non_null:
2822 * @binding: a #GBinding
2823 * @source_value: a #GValue of type #G_TYPE_STRING
2824 * @target_value: a #GValue of type #G_TYPE_STRING
2825 * @user_data: custom user data, unused
2826 *
2827 * Transforms a text value to a text value which is never NULL;
2828 * an empty string is used instead of NULL.
2829 *
2830 * Returns: %TRUE on success
2831 **/
2832 gboolean
e_binding_transform_text_non_null(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer user_data)2833 e_binding_transform_text_non_null (GBinding *binding,
2834 const GValue *source_value,
2835 GValue *target_value,
2836 gpointer user_data)
2837 {
2838 const gchar *str;
2839
2840 g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
2841 g_return_val_if_fail (source_value != NULL, FALSE);
2842 g_return_val_if_fail (target_value != NULL, FALSE);
2843
2844 str = g_value_get_string (source_value);
2845 if (!str)
2846 str = "";
2847
2848 g_value_set_string (target_value, str);
2849
2850 return TRUE;
2851 }
2852
2853 /**
2854 * e_binding_bind_object_text_property:
2855 * @source: the source #GObject
2856 * @source_property: the text property on the source to bind
2857 * @target: the target #GObject
2858 * @target_property: the text property on the target to bind
2859 * @flags: flags to pass to e_binding_bind_property_full()
2860 *
2861 * Installs a new text property object binding, using e_binding_bind_property_full(),
2862 * with transform functions to make sure that a NULL pointer is not
2863 * passed in either way. Instead of NULL an empty string is used.
2864 *
2865 * Returns: the #GBinding instance representing the binding between the two #GObject instances;
2866 * there applies the same rules to it as for the result of e_binding_bind_property_full().
2867 **/
2868 GBinding *
e_binding_bind_object_text_property(gpointer source,const gchar * source_property,gpointer target,const gchar * target_property,GBindingFlags flags)2869 e_binding_bind_object_text_property (gpointer source,
2870 const gchar *source_property,
2871 gpointer target,
2872 const gchar *target_property,
2873 GBindingFlags flags)
2874 {
2875 GObjectClass *klass;
2876 GParamSpec *property;
2877
2878 g_return_val_if_fail (G_IS_OBJECT (source), NULL);
2879 g_return_val_if_fail (source_property != NULL, NULL);
2880 g_return_val_if_fail (G_IS_OBJECT (target), NULL);
2881 g_return_val_if_fail (target_property != NULL, NULL);
2882
2883 klass = G_OBJECT_GET_CLASS (source);
2884 property = g_object_class_find_property (klass, source_property);
2885 g_return_val_if_fail (property != NULL, NULL);
2886 g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL);
2887
2888 klass = G_OBJECT_GET_CLASS (target);
2889 property = g_object_class_find_property (klass, target_property);
2890 g_return_val_if_fail (property != NULL, NULL);
2891 g_return_val_if_fail (property->value_type == G_TYPE_STRING, NULL);
2892
2893 return e_binding_bind_property_full (source, source_property,
2894 target, target_property,
2895 flags,
2896 e_binding_transform_text_non_null,
2897 e_binding_transform_text_non_null,
2898 NULL, NULL);
2899 }
2900
2901 typedef struct _EConnectNotifyData {
2902 GConnectFlags flags;
2903 GValue *old_value;
2904
2905 GCallback c_handler;
2906 gpointer user_data;
2907 } EConnectNotifyData;
2908
2909 static EConnectNotifyData *
e_connect_notify_data_new(GCallback c_handler,gpointer user_data,guint32 connect_flags)2910 e_connect_notify_data_new (GCallback c_handler,
2911 gpointer user_data,
2912 guint32 connect_flags)
2913 {
2914 EConnectNotifyData *connect_data;
2915
2916 connect_data = g_new0 (EConnectNotifyData, 1);
2917 connect_data->flags = connect_flags;
2918 connect_data->c_handler = c_handler;
2919 connect_data->user_data = user_data;
2920
2921 return connect_data;
2922 }
2923
2924 static void
e_connect_notify_data_free(EConnectNotifyData * data)2925 e_connect_notify_data_free (EConnectNotifyData *data)
2926 {
2927 if (!data)
2928 return;
2929
2930 if (data->old_value) {
2931 g_value_unset (data->old_value);
2932 g_free (data->old_value);
2933 }
2934 g_free (data);
2935 }
2936
2937 static gboolean
e_value_equal(GValue * value1,GValue * value2)2938 e_value_equal (GValue *value1,
2939 GValue *value2)
2940 {
2941 if (value1 == value2)
2942 return TRUE;
2943
2944 if (!value1 || !value2)
2945 return FALSE;
2946
2947 #define testType(_uc,_lc) G_STMT_START { \
2948 if (G_VALUE_HOLDS_ ## _uc (value1)) \
2949 return g_value_get_ ## _lc (value1) == g_value_get_ ## _lc (value2); \
2950 } G_STMT_END
2951
2952 testType (BOOLEAN, boolean);
2953 testType (BOXED, boxed);
2954 testType (CHAR, schar);
2955 testType (DOUBLE, double);
2956 testType (ENUM, enum);
2957 testType (FLAGS, flags);
2958 testType (FLOAT, float);
2959 testType (GTYPE, gtype);
2960 testType (INT, int);
2961 testType (INT64, int64);
2962 testType (LONG, long);
2963 testType (OBJECT, object);
2964 testType (POINTER, pointer);
2965 testType (UCHAR, uchar);
2966 testType (UINT, uint);
2967 testType (UINT64, uint64);
2968 testType (ULONG, ulong);
2969
2970 #undef testType
2971
2972 if (G_VALUE_HOLDS_PARAM (value1)) {
2973 GParamSpec *param1, *param2;
2974
2975 param1 = g_value_get_param (value1);
2976 param2 = g_value_get_param (value2);
2977
2978 return param1 && param2 &&
2979 g_strcmp0 (param1->name, param2->name) == 0 &&
2980 param1->flags == param2->flags &&
2981 param1->value_type == param2->value_type &&
2982 param1->owner_type == param2->owner_type;
2983 } else if (G_VALUE_HOLDS_STRING (value1)) {
2984 const gchar *string1, *string2;
2985
2986 string1 = g_value_get_string (value1);
2987 string2 = g_value_get_string (value2);
2988
2989 return g_strcmp0 (string1, string2) == 0;
2990 } else if (G_VALUE_HOLDS_VARIANT (value1)) {
2991 GVariant *variant1, *variant2;
2992
2993 variant1 = g_value_get_variant (value1);
2994 variant2 = g_value_get_variant (value2);
2995
2996 return variant1 == variant2 ||
2997 (variant1 && variant2 && g_variant_equal (variant1, variant2));
2998 }
2999
3000 return FALSE;
3001 }
3002
3003 static void
e_signal_connect_notify_cb(gpointer instance,GParamSpec * param,gpointer user_data)3004 e_signal_connect_notify_cb (gpointer instance,
3005 GParamSpec *param,
3006 gpointer user_data)
3007 {
3008 EConnectNotifyData *connect_data = user_data;
3009 GValue *value;
3010
3011 g_return_if_fail (connect_data != NULL);
3012
3013 value = g_new0 (GValue, 1);
3014 g_value_init (value, param->value_type);
3015 g_object_get_property (instance, param->name, value);
3016
3017 if (!e_value_equal (connect_data->old_value, value)) {
3018 typedef void (* NotifyCBType) (gpointer instance, GParamSpec *param, gpointer user_data);
3019 NotifyCBType c_handler = (NotifyCBType) connect_data->c_handler;
3020
3021 if (connect_data->old_value) {
3022 g_value_unset (connect_data->old_value);
3023 g_free (connect_data->old_value);
3024 }
3025 connect_data->old_value = value;
3026
3027 if (connect_data->flags == G_CONNECT_SWAPPED) {
3028 c_handler (connect_data->user_data, param, instance);
3029 } else {
3030 c_handler (instance, param, connect_data->user_data);
3031 }
3032 } else {
3033 g_value_unset (value);
3034 g_free (value);
3035 }
3036 }
3037
3038 /**
3039 * e_signal_connect_notify:
3040 *
3041 * This installs a special handler in front of @c_handler, which will
3042 * call the @c_handler only if the property value changed since the last
3043 * time it was checked. Due to this, these handlers cannot be disconnected
3044 * by by any of the g_signal_handlers_* functions, but only with the returned
3045 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
3046 * to make it easier.
3047 **/
3048 gulong
e_signal_connect_notify(gpointer instance,const gchar * notify_name,GCallback c_handler,gpointer user_data)3049 e_signal_connect_notify (gpointer instance,
3050 const gchar *notify_name,
3051 GCallback c_handler,
3052 gpointer user_data)
3053 {
3054 EConnectNotifyData *connect_data;
3055
3056 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
3057
3058 connect_data = e_connect_notify_data_new (c_handler, user_data, 0);
3059
3060 return g_signal_connect_data (instance,
3061 notify_name,
3062 G_CALLBACK (e_signal_connect_notify_cb),
3063 connect_data,
3064 (GClosureNotify) e_connect_notify_data_free,
3065 0);
3066 }
3067
3068 /**
3069 * e_signal_connect_notify_after:
3070 *
3071 * This installs a special handler in front of @c_handler, which will
3072 * call the @c_handler only if the property value changed since the last
3073 * time it was checked. Due to this, these handlers cannot be disconnected
3074 * by by any of the g_signal_handlers_* functions, but only with the returned
3075 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
3076 * to make it easier.
3077 **/
3078 gulong
e_signal_connect_notify_after(gpointer instance,const gchar * notify_name,GCallback c_handler,gpointer user_data)3079 e_signal_connect_notify_after (gpointer instance,
3080 const gchar *notify_name,
3081 GCallback c_handler,
3082 gpointer user_data)
3083 {
3084 EConnectNotifyData *connect_data;
3085
3086 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
3087
3088 connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_AFTER);
3089
3090 return g_signal_connect_data (instance,
3091 notify_name,
3092 G_CALLBACK (e_signal_connect_notify_cb),
3093 connect_data,
3094 (GClosureNotify) e_connect_notify_data_free,
3095 G_CONNECT_AFTER);
3096 }
3097
3098 /**
3099 * e_signal_connect_notify_swapped:
3100 *
3101 * This installs a special handler in front of @c_handler, which will
3102 * call the @c_handler only if the property value changed since the last
3103 * time it was checked. Due to this, these handlers cannot be disconnected
3104 * by by any of the g_signal_handlers_* functions, but only with the returned
3105 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
3106 * to make it easier.
3107 **/
3108 gulong
e_signal_connect_notify_swapped(gpointer instance,const gchar * notify_name,GCallback c_handler,gpointer user_data)3109 e_signal_connect_notify_swapped (gpointer instance,
3110 const gchar *notify_name,
3111 GCallback c_handler,
3112 gpointer user_data)
3113 {
3114 EConnectNotifyData *connect_data;
3115
3116 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
3117
3118 connect_data = e_connect_notify_data_new (c_handler, user_data, G_CONNECT_SWAPPED);
3119
3120 return g_signal_connect_data (instance,
3121 notify_name,
3122 G_CALLBACK (e_signal_connect_notify_cb),
3123 connect_data,
3124 (GClosureNotify) e_connect_notify_data_free,
3125 0);
3126 }
3127
3128 /**
3129 * e_signal_connect_notify_object:
3130 *
3131 * This installs a special handler in front of @c_handler, which will
3132 * call the @c_handler only if the property value changed since the last
3133 * time it was checked. Due to this, these handlers cannot be disconnected
3134 * by by any of the g_signal_handlers_* functions, but only with the returned
3135 * handler ID. A convenient e_signal_disconnect_notify_handler() was added
3136 * to make it easier.
3137 **/
3138 gulong
e_signal_connect_notify_object(gpointer instance,const gchar * notify_name,GCallback c_handler,gpointer gobject,GConnectFlags connect_flags)3139 e_signal_connect_notify_object (gpointer instance,
3140 const gchar *notify_name,
3141 GCallback c_handler,
3142 gpointer gobject,
3143 GConnectFlags connect_flags)
3144 {
3145 EConnectNotifyData *connect_data;
3146 GClosure *closure;
3147
3148 g_return_val_if_fail (g_str_has_prefix (notify_name, "notify::"), 0);
3149
3150 if (!gobject) {
3151 if ((connect_flags & G_CONNECT_SWAPPED) != 0)
3152 return e_signal_connect_notify_swapped (instance, notify_name, c_handler, gobject);
3153 else if ((connect_flags & G_CONNECT_AFTER) != 0)
3154 e_signal_connect_notify_after (instance, notify_name, c_handler, gobject);
3155 else
3156 g_warn_if_fail (connect_flags == 0);
3157
3158 return e_signal_connect_notify (instance, notify_name, c_handler, gobject);
3159 }
3160
3161 g_return_val_if_fail (G_IS_OBJECT (gobject), 0);
3162
3163 connect_data = e_connect_notify_data_new (c_handler, gobject, connect_flags & G_CONNECT_SWAPPED);
3164 closure = g_cclosure_new (
3165 G_CALLBACK (e_signal_connect_notify_cb),
3166 connect_data,
3167 (GClosureNotify) e_connect_notify_data_free);
3168
3169 g_object_watch_closure (G_OBJECT (gobject), closure);
3170
3171 return g_signal_connect_closure (instance,
3172 notify_name,
3173 closure,
3174 connect_flags & G_CONNECT_AFTER);
3175 }
3176
3177 /**
3178 * e_signal_disconnect_notify_handler:
3179 *
3180 * Convenient handler disconnect function to be used with
3181 * returned handler IDs from:
3182 * e_signal_connect_notify()
3183 * e_signal_connect_notify_after()
3184 * e_signal_connect_notify_swapped()
3185 * e_signal_connect_notify_object()
3186 * but not necessarily only with these functions.
3187 **/
3188 void
e_signal_disconnect_notify_handler(gpointer instance,gulong * handler_id)3189 e_signal_disconnect_notify_handler (gpointer instance,
3190 gulong *handler_id)
3191 {
3192 g_return_if_fail (instance != NULL);
3193 g_return_if_fail (handler_id != NULL);
3194
3195 if (!*handler_id)
3196 return;
3197
3198 g_signal_handler_disconnect (instance, *handler_id);
3199 *handler_id = 0;
3200 }
3201
3202 static GMutex settings_hash_lock;
3203 static GHashTable *settings_hash = NULL;
3204
3205 /**
3206 * e_util_ref_settings:
3207 * @schema_id: the id of the schema to reference settings for
3208 *
3209 * Either returns an existing referenced #GSettings object for the given @schema_id,
3210 * or creates a new one and remembers it for later use, to avoid having too many
3211 * #GSettings objects created for the same @schema_id.
3212 *
3213 * Returns: A #GSettings for the given @schema_id. The returned #GSettings object
3214 * is referenced, thus free it with g_object_unref() when done with it.
3215 *
3216 * Since: 3.16
3217 **/
3218 GSettings *
e_util_ref_settings(const gchar * schema_id)3219 e_util_ref_settings (const gchar *schema_id)
3220 {
3221 GSettings *settings;
3222
3223 g_return_val_if_fail (schema_id != NULL, NULL);
3224 g_return_val_if_fail (*schema_id, NULL);
3225
3226 g_mutex_lock (&settings_hash_lock);
3227
3228 if (!settings_hash) {
3229 settings_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3230 }
3231
3232 settings = g_hash_table_lookup (settings_hash, schema_id);
3233 if (!settings) {
3234 settings = g_settings_new (schema_id);
3235 g_hash_table_insert (settings_hash, g_strdup (schema_id), settings);
3236 }
3237
3238 if (settings)
3239 g_object_ref (settings);
3240
3241 g_mutex_unlock (&settings_hash_lock);
3242
3243 return settings;
3244 }
3245
3246 /**
3247 * e_util_cleanup_settings:
3248 *
3249 * Frees all the memory taken by e_util_ref_settings().
3250 *
3251 * Since: 3.16
3252 **/
3253 void
e_util_cleanup_settings(void)3254 e_util_cleanup_settings (void)
3255 {
3256 g_mutex_lock (&settings_hash_lock);
3257
3258 g_clear_pointer (&settings_hash, g_hash_table_destroy);
3259
3260 g_mutex_unlock (&settings_hash_lock);
3261 }
3262
3263 static gdouble
get_screen_dpi(GdkScreen * screen)3264 get_screen_dpi (GdkScreen *screen)
3265 {
3266 gdouble dpi;
3267 gdouble dp, di;
3268
3269 dpi = gdk_screen_get_resolution (screen);
3270 if (dpi != -1)
3271 return dpi;
3272
3273 dp = hypot (gdk_screen_get_width (screen), gdk_screen_get_height (screen));
3274 di = hypot (gdk_screen_get_width_mm (screen), gdk_screen_get_height_mm (screen)) / 25.4;
3275
3276 return dp / di;
3277 }
3278
3279 guint
e_util_normalize_font_size(GtkWidget * widget,gdouble font_size)3280 e_util_normalize_font_size (GtkWidget *widget,
3281 gdouble font_size)
3282 {
3283 /* WebKit2 uses font sizes in pixels. */
3284 GdkScreen *screen;
3285 gdouble dpi;
3286
3287 if (widget) {
3288 screen = gtk_widget_has_screen (widget) ?
3289 gtk_widget_get_screen (widget) : gdk_screen_get_default ();
3290 } else {
3291 screen = gdk_screen_get_default ();
3292 }
3293
3294 dpi = screen ? get_screen_dpi (screen) : 96;
3295
3296 return font_size / 72.0 * dpi;
3297 }
3298
3299 /**
3300 * e_util_prompt_user:
3301 * @parent: parent window
3302 * @settings_schema: name of the settings schema where @promptkey belongs.
3303 * @promptkey: settings key to check if we should prompt the user or not.
3304 * @tag: e_alert tag.
3305 *
3306 * Convenience function to query the user with a Yes/No dialog and a
3307 * "Do not show this dialog again" checkbox. If the user checks that
3308 * checkbox, then @promptkey is set to %FALSE, otherwise it is set to
3309 * %TRUE.
3310 *
3311 * Returns %TRUE if the user clicks Yes or %FALSE otherwise.
3312 **/
3313 gboolean
e_util_prompt_user(GtkWindow * parent,const gchar * settings_schema,const gchar * promptkey,const gchar * tag,...)3314 e_util_prompt_user (GtkWindow *parent,
3315 const gchar *settings_schema,
3316 const gchar *promptkey,
3317 const gchar *tag,
3318 ...)
3319 {
3320 GtkWidget *dialog;
3321 GtkWidget *check = NULL;
3322 GtkWidget *container;
3323 va_list ap;
3324 gint button;
3325 GSettings *settings = NULL;
3326 EAlert *alert = NULL;
3327
3328 if (promptkey) {
3329 settings = e_util_ref_settings (settings_schema);
3330
3331 if (!g_settings_get_boolean (settings, promptkey)) {
3332 g_object_unref (settings);
3333 return TRUE;
3334 }
3335 }
3336
3337 va_start (ap, tag);
3338 alert = e_alert_new_valist (tag, ap);
3339 va_end (ap);
3340
3341 dialog = e_alert_dialog_new (parent, alert);
3342 g_object_unref (alert);
3343
3344 container = e_alert_dialog_get_content_area (E_ALERT_DIALOG (dialog));
3345
3346 if (promptkey) {
3347 check = gtk_check_button_new_with_mnemonic (
3348 _("_Do not show this message again"));
3349 gtk_box_pack_start (
3350 GTK_BOX (container), check, FALSE, FALSE, 0);
3351 gtk_widget_show (check);
3352 }
3353
3354 button = gtk_dialog_run (GTK_DIALOG (dialog));
3355 if (promptkey)
3356 g_settings_set_boolean (
3357 settings, promptkey,
3358 !gtk_toggle_button_get_active (
3359 GTK_TOGGLE_BUTTON (check)));
3360
3361 gtk_widget_destroy (dialog);
3362
3363 g_clear_object (&settings);
3364
3365 return button == GTK_RESPONSE_YES;
3366 }
3367
3368 typedef struct _EUtilSimpleAsyncResultThreadData {
3369 GSimpleAsyncResult *simple;
3370 GSimpleAsyncThreadFunc func;
3371 GCancellable *cancellable;
3372 } EUtilSimpleAsyncResultThreadData;
3373
3374 static void
e_util_simple_async_result_thread(gpointer data,gpointer user_data)3375 e_util_simple_async_result_thread (gpointer data,
3376 gpointer user_data)
3377 {
3378 EUtilSimpleAsyncResultThreadData *thread_data = data;
3379 GError *error = NULL;
3380
3381 g_return_if_fail (thread_data != NULL);
3382 g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (thread_data->simple));
3383 g_return_if_fail (thread_data->func != NULL);
3384
3385 if (g_cancellable_set_error_if_cancelled (thread_data->cancellable, &error)) {
3386 g_simple_async_result_take_error (thread_data->simple, error);
3387 } else {
3388 thread_data->func (thread_data->simple,
3389 g_async_result_get_source_object (G_ASYNC_RESULT (thread_data->simple)),
3390 thread_data->cancellable);
3391 }
3392
3393 g_simple_async_result_complete_in_idle (thread_data->simple);
3394
3395 g_clear_object (&thread_data->simple);
3396 g_clear_object (&thread_data->cancellable);
3397 g_slice_free (EUtilSimpleAsyncResultThreadData, thread_data);
3398 }
3399
3400 /**
3401 * e_util_run_simple_async_result_in_thread:
3402 * @simple: a #GSimpleAsyncResult
3403 * @func: a #GSimpleAsyncThreadFunc to execute in the thread
3404 * @cancellable: an optional #GCancellable, or %NULL
3405 *
3406 * Similar to g_simple_async_result_run_in_thread(), except
3407 * it doesn't use GTask internally, thus doesn't block the GTask
3408 * thread pool with possibly long job.
3409 *
3410 * It doesn't behave exactly the same as the g_simple_async_result_run_in_thread(),
3411 * the @cancellable checking is not done before the finish.
3412 *
3413 * Since: 3.18
3414 **/
3415 void
e_util_run_simple_async_result_in_thread(GSimpleAsyncResult * simple,GSimpleAsyncThreadFunc func,GCancellable * cancellable)3416 e_util_run_simple_async_result_in_thread (GSimpleAsyncResult *simple,
3417 GSimpleAsyncThreadFunc func,
3418 GCancellable *cancellable)
3419 {
3420 static GThreadPool *thread_pool = NULL;
3421 static GMutex thread_pool_mutex;
3422 EUtilSimpleAsyncResultThreadData *thread_data;
3423
3424 g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple));
3425 g_return_if_fail (func != NULL);
3426
3427 g_mutex_lock (&thread_pool_mutex);
3428
3429 if (!thread_pool)
3430 thread_pool = g_thread_pool_new (e_util_simple_async_result_thread, NULL, 20, FALSE, NULL);
3431
3432 thread_data = g_slice_new0 (EUtilSimpleAsyncResultThreadData);
3433 thread_data->simple = g_object_ref (simple);
3434 thread_data->func = func;
3435 thread_data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
3436
3437 g_thread_pool_push (thread_pool, thread_data, NULL);
3438
3439 g_mutex_unlock (&thread_pool_mutex);
3440 }
3441
3442 /**
3443 * e_util_is_running_gnome:
3444 *
3445 * Returns: Whether the current running desktop environment is GNOME.
3446 *
3447 * Since: 3.18
3448 **/
3449 gboolean
e_util_is_running_gnome(void)3450 e_util_is_running_gnome (void)
3451 {
3452 #ifdef G_OS_WIN32
3453 return FALSE;
3454 #else
3455 static gint runs_gnome = -1;
3456
3457 if (runs_gnome == -1) {
3458 const gchar *desktop;
3459 desktop = g_getenv ("XDG_CURRENT_DESKTOP");
3460 runs_gnome = 0;
3461 if (desktop != NULL) {
3462 gint ii;
3463 gchar **desktops = g_strsplit (desktop, ":", -1);
3464 for (ii = 0; desktops[ii]; ii++) {
3465 if (!g_ascii_strcasecmp (desktops[ii], "gnome")) {
3466 runs_gnome = 1;
3467 break;
3468 }
3469 }
3470 g_strfreev (desktops);
3471 }
3472
3473 if (runs_gnome) {
3474 GDesktopAppInfo *app_info;
3475
3476 app_info = g_desktop_app_info_new ("gnome-notifications-panel.desktop");
3477 if (!app_info) {
3478 runs_gnome = 0;
3479 }
3480
3481 g_clear_object (&app_info);
3482 }
3483 }
3484
3485 return runs_gnome != 0;
3486 #endif
3487 }
3488
3489 /**
3490 * e_util_is_running_flatpak:
3491 *
3492 * Returns: Whether running in Flatpak.
3493 *
3494 * Since: 3.32
3495 **/
3496 gboolean
e_util_is_running_flatpak(void)3497 e_util_is_running_flatpak (void)
3498 {
3499 #ifdef G_OS_UNIX
3500 static gint is_flatpak = -1;
3501
3502 if (is_flatpak == -1) {
3503 if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) ||
3504 g_getenv ("EVOLUTION_FLATPAK") != NULL) /* Only for debugging purposes */
3505 is_flatpak = 1;
3506 else
3507 is_flatpak = 0;
3508 }
3509
3510 return is_flatpak == 1;
3511 #else
3512 return FALSE;
3513 #endif
3514 }
3515
3516 /**
3517 * e_util_set_entry_issue_hint:
3518 * @entry: a #GtkEntry
3519 * @hint: (allow none): a hint to set, or %NULL to unset
3520 *
3521 * Sets a @hint on the secondary @entry icon about an entered value issue,
3522 * or unsets it, when the @hint is %NULL.
3523 *
3524 * Since: 3.20
3525 **/
3526 void
e_util_set_entry_issue_hint(GtkWidget * entry,const gchar * hint)3527 e_util_set_entry_issue_hint (GtkWidget *entry,
3528 const gchar *hint)
3529 {
3530 GtkEntry *eentry;
3531
3532 g_return_if_fail (GTK_IS_ENTRY (entry));
3533
3534 eentry = GTK_ENTRY (entry);
3535
3536 if (hint) {
3537 gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
3538 gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, hint);
3539 } else {
3540 gtk_entry_set_icon_from_icon_name (eentry, GTK_ENTRY_ICON_SECONDARY, NULL);
3541 gtk_entry_set_icon_tooltip_text (eentry, GTK_ENTRY_ICON_SECONDARY, NULL);
3542 }
3543 }
3544
3545 static GThread *main_thread = NULL;
3546
3547 void
e_util_init_main_thread(GThread * thread)3548 e_util_init_main_thread (GThread *thread)
3549 {
3550 g_return_if_fail (main_thread == NULL);
3551
3552 main_thread = thread ? thread : g_thread_self ();
3553 }
3554
3555 gboolean
e_util_is_main_thread(GThread * thread)3556 e_util_is_main_thread (GThread *thread)
3557 {
3558 return thread ? thread == main_thread : g_thread_self () == main_thread;
3559 }
3560
3561 /**
3562 * e_util_save_image_from_clipboard:
3563 * @clipboard: a #GtkClipboard
3564 * @hint: (allow none): a hint to set, or %NULL to unset
3565 *
3566 * Saves the image from @clipboard to a temporary file and returns its URI.
3567 *
3568 * Since: 3.22
3569 **/
3570 gchar *
e_util_save_image_from_clipboard(GtkClipboard * clipboard)3571 e_util_save_image_from_clipboard (GtkClipboard *clipboard)
3572 {
3573 GdkPixbuf *pixbuf = NULL;
3574 gchar *tmpl;
3575 gchar *filename = NULL;
3576 gchar *uri = NULL;
3577 GError *error = NULL;
3578
3579 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), NULL);
3580
3581 /* Extract the image data from the clipboard. */
3582 pixbuf = gtk_clipboard_wait_for_image (clipboard);
3583 g_return_val_if_fail (pixbuf != NULL, FALSE);
3584
3585 tmpl = g_strconcat (_("Image"), "-XXXXXX.png", NULL);
3586
3587 /* Reserve a temporary file. */
3588 filename = e_mktemp (tmpl);
3589
3590 g_free (tmpl);
3591
3592 if (filename == NULL) {
3593 g_set_error (
3594 &error, G_FILE_ERROR,
3595 g_file_error_from_errno (errno),
3596 "Could not create temporary file: %s",
3597 g_strerror (errno));
3598 goto exit;
3599 }
3600
3601 /* Save the pixbuf as a temporary file in image/png format. */
3602 if (!gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL))
3603 goto exit;
3604
3605 /* Convert the filename to a URI. */
3606 uri = g_filename_to_uri (filename, NULL, &error);
3607
3608 exit:
3609 if (error != NULL) {
3610 g_warning ("%s", error->message);
3611 g_error_free (error);
3612 }
3613
3614 g_object_unref (pixbuf);
3615 g_free (filename);
3616
3617 return uri;
3618 }
3619
3620 static void
e_util_stop_signal_emission_cb(gpointer instance,const gchar * signal_name)3621 e_util_stop_signal_emission_cb (gpointer instance,
3622 const gchar *signal_name)
3623 {
3624 g_signal_stop_emission_by_name (instance, signal_name);
3625 }
3626
3627 /**
3628 * e_util_check_gtk_bindings_in_key_press_event_cb:
3629 * @widget: a #GtkWidget, most often a #GtkWindow
3630 * @event: a #GdkEventKey
3631 *
3632 * A callback function for GtkWidget::key-press-event signal,
3633 * which checks whether currently focused widget inside @widget,
3634 * if it's a #GtkWindow, or a toplevel window containing the @widget,
3635 * will consume the @event due to gtk+ bindings and if so, then
3636 * it'll stop processing the event further. When it's connected
3637 * on a #GtkWindow, then it can prevent the event to be used
3638 * for shortcuts of actions.
3639 *
3640 * Returns: %TRUE to stop other handlers from being invoked for
3641 * the event, %FALSE to propagate the event further.
3642 *
3643 * Since: 3.22
3644 **/
3645 gboolean
e_util_check_gtk_bindings_in_key_press_event_cb(GtkWidget * widget,GdkEvent * event)3646 e_util_check_gtk_bindings_in_key_press_event_cb (GtkWidget *widget,
3647 GdkEvent *event)
3648 {
3649 GdkEventKey *key_event = (GdkEventKey *) event;
3650 GtkWindow *window = NULL;
3651 GtkWidget *focused;
3652
3653 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
3654 g_return_val_if_fail (event != NULL, FALSE);
3655 g_return_val_if_fail (event->type == GDK_KEY_PRESS, FALSE);
3656
3657 if (GTK_IS_WINDOW (widget)) {
3658 window = GTK_WINDOW (widget);
3659 } else {
3660 GtkWidget *toplevel;
3661
3662 toplevel = gtk_widget_get_toplevel (widget);
3663 if (GTK_IS_WINDOW (toplevel))
3664 window = GTK_WINDOW (toplevel);
3665 }
3666
3667 if (!window)
3668 return FALSE;
3669
3670 focused = gtk_window_get_focus (window);
3671 if (!focused)
3672 return FALSE;
3673
3674 if (gtk_bindings_activate_event (G_OBJECT (focused), key_event))
3675 return TRUE;
3676
3677 if (WEBKIT_IS_WEB_VIEW (focused) &&
3678 (key_event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0) {
3679 GtkWidget *text_view;
3680 gboolean may_use;
3681
3682 /* WebKit uses GtkTextView to process key bindings. Do the same. */
3683 text_view = gtk_text_view_new ();
3684
3685 /* Stop emissing for clipboard signals, to not populate the text_view */
3686 g_signal_connect (text_view, "copy-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "copy-clipboard");
3687 g_signal_connect (text_view, "cut-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "cut-clipboard");
3688 g_signal_connect (text_view, "paste-clipboard", G_CALLBACK (e_util_stop_signal_emission_cb), (gpointer) "paste-clipboard");
3689
3690 may_use = gtk_bindings_activate_event (G_OBJECT (text_view), key_event);
3691 gtk_widget_destroy (text_view);
3692
3693 if (may_use) {
3694 gboolean result = FALSE;
3695
3696 g_signal_emit_by_name (focused, "key-press-event", event, &result);
3697
3698 return result;
3699 }
3700 }
3701
3702 return FALSE;
3703 }
3704
3705 /**
3706 * e_util_save_file_chooser_folder:
3707 * @file_chooser: a #GtkFileChooser
3708 *
3709 * Saves the current folder of the @file_chooser, thus it could be used
3710 * by e_util_load_file_chooser_folder() to open it in the last chosen folder.
3711 *
3712 * Since: 3.24
3713 **/
3714 void
e_util_save_file_chooser_folder(GtkFileChooser * file_chooser)3715 e_util_save_file_chooser_folder (GtkFileChooser *file_chooser)
3716 {
3717 GSettings *settings;
3718 gchar *uri;
3719
3720 g_return_if_fail (GTK_IS_FILE_CHOOSER (file_chooser));
3721
3722 uri = gtk_file_chooser_get_current_folder_uri (file_chooser);
3723 if (uri && g_str_has_prefix (uri, "file://")) {
3724 settings = e_util_ref_settings ("org.gnome.evolution.shell");
3725 g_settings_set_string (settings, "file-chooser-folder", uri);
3726 g_object_unref (settings);
3727 }
3728
3729 g_free (uri);
3730 }
3731
3732 /**
3733 * e_util_load_file_chooser_folder:
3734 * @file_chooser: a #GtkFileChooser
3735 *
3736 * Sets the current folder to the @file_chooser to the one previously saved
3737 * by e_util_save_file_chooser_folder(). The function does nothing if none
3738 * or invalid is saved.
3739 *
3740 * Since: 3.24
3741 **/
3742 void
e_util_load_file_chooser_folder(GtkFileChooser * file_chooser)3743 e_util_load_file_chooser_folder (GtkFileChooser *file_chooser)
3744 {
3745 GSettings *settings;
3746 gchar *uri;
3747
3748 g_return_if_fail (GTK_IS_FILE_CHOOSER (file_chooser));
3749
3750 settings = e_util_ref_settings ("org.gnome.evolution.shell");
3751 uri = g_settings_get_string (settings, "file-chooser-folder");
3752 g_object_unref (settings);
3753
3754 if (uri && g_str_has_prefix (uri, "file://")) {
3755 gchar *filename;
3756
3757 filename = g_filename_from_uri (uri, NULL, NULL);
3758 if (filename && g_file_test (filename, G_FILE_TEST_IS_DIR))
3759 gtk_file_chooser_set_current_folder_uri (file_chooser, uri);
3760
3761 g_free (filename);
3762 }
3763
3764 g_free (uri);
3765 }
3766
3767 /**
3768 * e_util_get_webkit_developer_mode_enabled:
3769 *
3770 * Returns: Whether WebKit developer mode is enabled. This is read only
3771 * once, thus any changes in the GSettings property require restart
3772 * of the Evolution.
3773 *
3774 * Since: 3.24
3775 **/
3776 gboolean
e_util_get_webkit_developer_mode_enabled(void)3777 e_util_get_webkit_developer_mode_enabled (void)
3778 {
3779 static gchar enabled = -1;
3780
3781 if (enabled == -1) {
3782 GSettings *settings;
3783
3784 settings = e_util_ref_settings ("org.gnome.evolution.shell");
3785 enabled = g_settings_get_boolean (settings, "webkit-developer-mode") ? 1 : 0;
3786 g_clear_object (&settings);
3787 }
3788
3789 return enabled != 0;
3790 }
3791
3792 /**
3793 * e_util_next_uri_from_uri_list:
3794 * @uri_list: array of URIs separated by new lines
3795 * @len: (out): a length of the found URI
3796 * @list_len: (out): a length of the array
3797 *
3798 * Returns: A newly allocated string with found URI.
3799 *
3800 * Since: 3.26
3801 **/
3802 gchar *
e_util_next_uri_from_uri_list(guchar ** uri_list,gint * len,gint * list_len)3803 e_util_next_uri_from_uri_list (guchar **uri_list,
3804 gint *len,
3805 gint *list_len)
3806 {
3807 guchar *uri, *begin;
3808
3809 begin = *uri_list;
3810 *len = 0;
3811 while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) {
3812 (*uri_list) ++;
3813 (*len) ++;
3814 (*list_len) --;
3815 }
3816
3817 uri = (guchar *) g_strndup ((gchar *) begin, *len);
3818
3819 while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) {
3820 (*uri_list) ++;
3821 (*list_len) --;
3822 }
3823
3824 return (gchar *) uri;
3825 }
3826
3827 /**
3828 * e_util_resize_window_for_screen:
3829 * @window: a #GtkWindow
3830 * @window_width: the @window width without @children, or -1 to compute
3831 * @window_height: the @window height without @children, or -1 to compute
3832 * @children: (element-type GtkWidget): (nullable): a #GSList with children to calculate with
3833 *
3834 * Calculates the size of the @window considering preferred sizes of @children,
3835 * and shrinks the @window in case it won't be completely visible on the screen
3836 * it is assigned to.
3837 *
3838 * Since: 3.26
3839 **/
3840 void
e_util_resize_window_for_screen(GtkWindow * window,gint window_width,gint window_height,const GSList * children)3841 e_util_resize_window_for_screen (GtkWindow *window,
3842 gint window_width,
3843 gint window_height,
3844 const GSList *children)
3845 {
3846 gint width = -1, height = -1, content_width = -1, content_height = -1, current_width = -1, current_height = -1;
3847 GtkRequisition requisition;
3848 const GSList *link;
3849
3850 g_return_if_fail (GTK_IS_WINDOW (window));
3851
3852 gtk_window_get_default_size (window, &width, &height);
3853 if (width < 0 || height < 0) {
3854 gtk_widget_get_preferred_size (GTK_WIDGET (window), &requisition, NULL);
3855
3856 width = requisition.width;
3857 height = requisition.height;
3858 }
3859
3860 for (link = children; link; link = g_slist_next (link)) {
3861 GtkWidget *widget = link->data;
3862
3863 if (GTK_IS_SCROLLED_WINDOW (widget))
3864 widget = gtk_bin_get_child (GTK_BIN (widget));
3865
3866 if (GTK_IS_VIEWPORT (widget))
3867 widget = gtk_bin_get_child (GTK_BIN (widget));
3868
3869 if (!GTK_IS_WIDGET (widget))
3870 continue;
3871
3872 gtk_widget_get_preferred_size (widget, &requisition, NULL);
3873
3874 if (requisition.width > content_width)
3875 content_width = requisition.width;
3876 if (requisition.height > content_height)
3877 content_height = requisition.height;
3878
3879 widget = gtk_widget_get_parent (widget);
3880 if (GTK_IS_VIEWPORT (widget))
3881 widget = gtk_widget_get_parent (widget);
3882
3883 if (GTK_IS_WIDGET (widget)) {
3884 gtk_widget_get_preferred_size (widget, &requisition, NULL);
3885
3886 if (current_width == -1 || current_width < requisition.width)
3887 current_width = requisition.width;
3888 if (current_height == -1 || current_height < requisition.height)
3889 current_height = requisition.height;
3890 }
3891 }
3892
3893 if (content_width > 0 && content_height > 0 && width > 0 && height > 0) {
3894 GdkScreen *screen;
3895 GdkRectangle monitor_area;
3896 gint x = 0, y = 0, monitor;
3897
3898 screen = gtk_window_get_screen (GTK_WINDOW (window));
3899 gtk_window_get_position (GTK_WINDOW (window), &x, &y);
3900
3901 monitor = gdk_screen_get_monitor_at_point (screen, x, y);
3902 if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen))
3903 monitor = 0;
3904
3905 gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area);
3906
3907 /* When the children are packed inside the window then influence the window
3908 size too, thus subtract it, if possible. */
3909 if (window_width < 0) {
3910 if (current_width > 0 && current_width < width)
3911 width -= current_width;
3912 } else {
3913 width = window_width;
3914 }
3915
3916 if (window_height < 0) {
3917 if (current_height > 0 && current_height < height)
3918 height -= current_height;
3919 } else {
3920 height = window_height;
3921 }
3922
3923 if (content_width > monitor_area.width - width)
3924 content_width = monitor_area.width - width;
3925
3926 if (content_height > monitor_area.height - height)
3927 content_height = monitor_area.height - height;
3928
3929 if (content_width > 0 && content_height > 0)
3930 gtk_window_set_default_size (GTK_WINDOW (window), width + content_width, height + content_height);
3931 }
3932 }
3933
3934 /**
3935 * e_util_query_ldap_root_dse_sync:
3936 * @host: an LDAP server host name
3937 * @port: an LDAP server port
3938 * @out_root_dse: (out) (transfer full): NULL-terminated array of the server root DSE-s, or %NULL on error
3939 * @cancellable: optional #GCancellable object, or %NULL
3940 * @error: return location for a #GError, or %NULL
3941 *
3942 * Queries an LDAP server identified by @host and @port for supported
3943 * search bases and returns them as a NULL-terminated array of strings
3944 * at @out_root_dse. It sets @out_root_dse to NULL on error.
3945 * Free the returned @out_root_dse with g_strfreev() when no longer needed.
3946 *
3947 * The function fails and sets @error to G_IO_ERROR_NOT_SUPPORTED when
3948 * Evolution had been compiled without LDAP support.
3949 *
3950 * Returns: Whether succeeded.
3951 *
3952 * Since: 3.28
3953 **/
3954 gboolean
e_util_query_ldap_root_dse_sync(const gchar * host,guint16 port,gchar *** out_root_dse,GCancellable * cancellable,GError ** error)3955 e_util_query_ldap_root_dse_sync (const gchar *host,
3956 guint16 port,
3957 gchar ***out_root_dse,
3958 GCancellable *cancellable,
3959 GError **error)
3960 {
3961 #ifdef HAVE_LDAP
3962 G_LOCK_DEFINE_STATIC (ldap);
3963 LDAP *ldap = NULL;
3964 LDAPMessage *result = NULL;
3965 struct timeval timeout;
3966 gchar **values = NULL, **root_dse;
3967 gint ldap_error;
3968 gint option;
3969 gint version;
3970 gint ii;
3971 const gchar *attrs[] = { "namingContexts", NULL };
3972
3973 g_return_val_if_fail (host && *host, FALSE);
3974 g_return_val_if_fail (port > 0, FALSE);
3975 g_return_val_if_fail (out_root_dse != NULL, FALSE);
3976
3977 *out_root_dse = NULL;
3978
3979 timeout.tv_sec = 60;
3980 timeout.tv_usec = 0;
3981
3982 G_LOCK (ldap);
3983
3984 if (g_cancellable_set_error_if_cancelled (cancellable, error))
3985 goto exit;
3986
3987 ldap = ldap_init (host, port);
3988 if (!ldap) {
3989 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
3990 _("This address book server might be unreachable or the server name may be misspelled or your network connection could be down."));
3991 goto exit;
3992 }
3993
3994 if (g_cancellable_set_error_if_cancelled (cancellable, error))
3995 goto exit;
3996
3997 version = LDAP_VERSION3;
3998 option = LDAP_OPT_PROTOCOL_VERSION;
3999 ldap_error = ldap_set_option (ldap, option, &version);
4000 if (ldap_error != LDAP_OPT_SUCCESS) {
4001 g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
4002 _("Failed to set protocol version to LDAPv3 (%d): %s"), ldap_error,
4003 ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error"));
4004 goto exit;
4005 }
4006
4007 if (g_cancellable_set_error_if_cancelled (cancellable, error))
4008 goto exit;
4009
4010 /* FIXME Use the user's actual authentication settings. */
4011 ldap_error = ldap_simple_bind_s (ldap, NULL, NULL);
4012 if (ldap_error != LDAP_SUCCESS) {
4013 g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
4014 _("Failed to authenticate with LDAP server (%d): %s"), ldap_error,
4015 ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error"));
4016 goto exit;
4017 }
4018
4019 if (g_cancellable_set_error_if_cancelled (cancellable, error))
4020 goto exit;
4021
4022 ldap_error = ldap_search_ext_s (
4023 ldap, LDAP_ROOT_DSE, LDAP_SCOPE_BASE,
4024 "(objectclass=*)", (gchar **) attrs, 0,
4025 NULL, NULL, &timeout, LDAP_NO_LIMIT, &result);
4026 if (ldap_error != LDAP_SUCCESS) {
4027 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4028 _("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"),
4029 ldap_error, ldap_err2string (ldap_error) ? ldap_err2string (ldap_error) : _("Unknown error"));
4030 goto exit;
4031 }
4032
4033 if (g_cancellable_set_error_if_cancelled (cancellable, error))
4034 goto exit;
4035
4036 values = ldap_get_values (ldap, result, "namingContexts");
4037 if (values == NULL || values[0] == NULL || *values[0] == '\0') {
4038 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4039 _("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."));
4040 goto exit;
4041 }
4042
4043 if (g_cancellable_set_error_if_cancelled (cancellable, error))
4044 goto exit;
4045
4046 root_dse = g_new0 (gchar *, g_strv_length (values) + 1);
4047
4048 for (ii = 0; values[ii]; ii++) {
4049 root_dse[ii] = g_strdup (values[ii]);
4050 }
4051
4052 root_dse[ii] = NULL;
4053
4054 *out_root_dse = root_dse;
4055
4056 exit:
4057 if (values)
4058 ldap_value_free (values);
4059
4060 if (result)
4061 ldap_msgfree (result);
4062
4063 if (ldap)
4064 ldap_unbind_s (ldap);
4065
4066 G_UNLOCK (ldap);
4067
4068 return *out_root_dse != NULL;
4069
4070 #else /* HAVE_LDAP */
4071 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
4072 _("Evolution had not been compiled with LDAP support"));
4073
4074 return FALSE;
4075 #endif
4076 }
4077
4078 static GHashTable *iso_639_table = NULL;
4079 static GHashTable *iso_3166_table = NULL;
4080
4081 #ifdef HAVE_ISO_CODES
4082
4083 #define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale"
4084
4085 #ifdef G_OS_WIN32
4086 #ifdef DATADIR
4087 #undef DATADIR
4088 #endif
4089 #include <shlobj.h>
4090
4091 static gchar *
_get_iso_codes_prefix(void)4092 _get_iso_codes_prefix (void)
4093 {
4094 static gchar retval[1000];
4095 static gint beenhere = 0;
4096 gchar *temp_dir = 0;
4097
4098 if (beenhere)
4099 return retval;
4100
4101 if (!(temp_dir = g_win32_get_package_installation_directory_of_module (_e_get_dll_hmodule ()))) {
4102 strcpy (retval, ISO_CODES_PREFIX);
4103 return retval;
4104 }
4105
4106 strcpy (retval, temp_dir);
4107 g_free (temp_dir);
4108 beenhere = 1;
4109 return retval;
4110 }
4111
4112 static gchar *
_get_isocodeslocaledir(void)4113 _get_isocodeslocaledir (void)
4114 {
4115 static gchar retval[1000];
4116 static gint beenhere = 0;
4117
4118 if (beenhere)
4119 return retval;
4120
4121 g_snprintf (retval, sizeof (retval), "%s\\share\\locale", _get_iso_codes_prefix ());
4122 beenhere = 1;
4123 return retval;
4124 }
4125
4126 #undef ISO_CODES_PREFIX
4127 #define ISO_CODES_PREFIX _get_iso_codes_prefix ()
4128
4129 #undef ISOCODESLOCALEDIR
4130 #define ISOCODESLOCALEDIR _get_isocodeslocaledir ()
4131
4132 #endif
4133
4134 #define ISO_639_DOMAIN "iso_639"
4135 #define ISO_3166_DOMAIN "iso_3166"
4136
4137 static void
iso_639_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer data,GError ** error)4138 iso_639_start_element (GMarkupParseContext *context,
4139 const gchar *element_name,
4140 const gchar **attribute_names,
4141 const gchar **attribute_values,
4142 gpointer data,
4143 GError **error)
4144 {
4145 GHashTable *hash_table = data;
4146 const gchar *iso_639_1_code = NULL;
4147 const gchar *iso_639_2_code = NULL;
4148 const gchar *name = NULL;
4149 const gchar *code = NULL;
4150 gint ii;
4151
4152 if (g_strcmp0 (element_name, "iso_639_entry") != 0) {
4153 return;
4154 }
4155
4156 for (ii = 0; attribute_names[ii] != NULL; ii++) {
4157 if (strcmp (attribute_names[ii], "name") == 0)
4158 name = attribute_values[ii];
4159 else if (strcmp (attribute_names[ii], "iso_639_1_code") == 0)
4160 iso_639_1_code = attribute_values[ii];
4161 else if (strcmp (attribute_names[ii], "iso_639_2T_code") == 0)
4162 iso_639_2_code = attribute_values[ii];
4163 }
4164
4165 code = (iso_639_1_code != NULL) ? iso_639_1_code : iso_639_2_code;
4166
4167 if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
4168 g_hash_table_insert (
4169 hash_table, g_strdup (code),
4170 g_strdup (dgettext (ISO_639_DOMAIN, name)));
4171 }
4172
4173 static void
iso_3166_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer data,GError ** error)4174 iso_3166_start_element (GMarkupParseContext *context,
4175 const gchar *element_name,
4176 const gchar **attribute_names,
4177 const gchar **attribute_values,
4178 gpointer data,
4179 GError **error)
4180 {
4181 GHashTable *hash_table = data;
4182 const gchar *name = NULL;
4183 const gchar *code = NULL;
4184 gint ii;
4185
4186 if (strcmp (element_name, "iso_3166_entry") != 0)
4187 return;
4188
4189 for (ii = 0; attribute_names[ii] != NULL; ii++) {
4190 if (strcmp (attribute_names[ii], "name") == 0)
4191 name = attribute_values[ii];
4192 else if (strcmp (attribute_names[ii], "alpha_2_code") == 0)
4193 code = attribute_values[ii];
4194 }
4195
4196 if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
4197 g_hash_table_insert (
4198 hash_table, g_ascii_strdown (code, -1),
4199 g_strdup (dgettext (ISO_3166_DOMAIN, name)));
4200 }
4201
4202 static GMarkupParser iso_639_parser = {
4203 iso_639_start_element,
4204 NULL, NULL, NULL, NULL
4205 };
4206
4207 static GMarkupParser iso_3166_parser = {
4208 iso_3166_start_element,
4209 NULL, NULL, NULL, NULL
4210 };
4211
4212 static void
iso_codes_parse(const GMarkupParser * parser,const gchar * basename,GHashTable * hash_table)4213 iso_codes_parse (const GMarkupParser *parser,
4214 const gchar *basename,
4215 GHashTable *hash_table)
4216 {
4217 GMappedFile *mapped_file;
4218 gchar *filename;
4219 GError *error = NULL;
4220
4221 filename = g_build_filename (
4222 ISO_CODES_PREFIX, "share", "xml",
4223 "iso-codes", basename, NULL);
4224 mapped_file = g_mapped_file_new (filename, FALSE, &error);
4225 g_free (filename);
4226
4227 if (mapped_file != NULL) {
4228 GMarkupParseContext *context;
4229 const gchar *contents;
4230 gsize length;
4231
4232 context = g_markup_parse_context_new (
4233 parser, 0, hash_table, NULL);
4234 contents = g_mapped_file_get_contents (mapped_file);
4235 length = g_mapped_file_get_length (mapped_file);
4236 g_markup_parse_context_parse (
4237 context, contents, length, &error);
4238 g_markup_parse_context_free (context);
4239 #if GLIB_CHECK_VERSION(2,21,3)
4240 g_mapped_file_unref (mapped_file);
4241 #else
4242 g_mapped_file_free (mapped_file);
4243 #endif
4244 }
4245
4246 if (error != NULL) {
4247 g_warning ("%s: %s", basename, error->message);
4248 g_error_free (error);
4249 }
4250 }
4251
4252 #endif /* HAVE_ISO_CODES */
4253
4254 /**
4255 * e_util_get_language_info:
4256 * @language_tag: Language tag to get its name for, like "en_US"
4257 * @out_language_name: (out) (nullable) (transfer full): Return location for the language name, or %NULL
4258 * @out_country_name: (out) (nullable) (transfer full): Return location for the country name, or %NULL
4259 *
4260 * Splits language tag into a localized language name and country name (the variant).
4261 * The @out_language_name is always filled when the function returns %TRUE, but
4262 * the @out_countr_name can be %NULL. That's for cases when the @language_tag
4263 * contains only the country part, like "en".
4264 *
4265 * The function returns %FALSE when it could not decode language name from
4266 * the given @language_tag. When either of the @out_language_name and @out_country_name
4267 * is non-NULL and the function returns %TRUE, then their respective values
4268 * should be freed with g_free(), when no longer needed.
4269 *
4270 * Returns: %TRUE, when could get at least language name from the @language_tag,
4271 * %FALSE otherwise.
4272 *
4273 * Since: 3.32
4274 **/
4275 gboolean
e_util_get_language_info(const gchar * language_tag,gchar ** out_language_name,gchar ** out_country_name)4276 e_util_get_language_info (const gchar *language_tag,
4277 gchar **out_language_name,
4278 gchar **out_country_name)
4279 {
4280 const gchar *iso_639_name;
4281 const gchar *iso_3166_name;
4282 gchar *lowercase;
4283 gchar **tokens;
4284
4285 g_return_val_if_fail (language_tag != NULL, FALSE);
4286
4287 if (out_language_name)
4288 *out_language_name = NULL;
4289 if (out_country_name)
4290 *out_country_name = NULL;
4291
4292 /* Split language code into lowercase tokens. */
4293 lowercase = g_ascii_strdown (language_tag, -1);
4294 tokens = g_strsplit (lowercase, "_", -1);
4295 g_free (lowercase);
4296
4297 g_return_val_if_fail (tokens != NULL, FALSE);
4298
4299 if (!iso_639_table && !iso_3166_table) {
4300 #if defined (ENABLE_NLS) && defined (HAVE_ISO_CODES)
4301 bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR);
4302 bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8");
4303
4304 bindtextdomain (ISO_3166_DOMAIN, ISOCODESLOCALEDIR);
4305 bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8");
4306 #endif /* ENABLE_NLS && HAVE_ISO_CODES */
4307
4308 iso_639_table = g_hash_table_new_full (
4309 (GHashFunc) g_str_hash,
4310 (GEqualFunc) g_str_equal,
4311 (GDestroyNotify) g_free,
4312 (GDestroyNotify) g_free);
4313
4314 iso_3166_table = g_hash_table_new_full (
4315 (GHashFunc) g_str_hash,
4316 (GEqualFunc) g_str_equal,
4317 (GDestroyNotify) g_free,
4318 (GDestroyNotify) g_free);
4319
4320 #ifdef HAVE_ISO_CODES
4321 iso_codes_parse (
4322 &iso_639_parser, "iso_639.xml", iso_639_table);
4323 iso_codes_parse (
4324 &iso_3166_parser, "iso_3166.xml", iso_3166_table);
4325 #endif /* HAVE_ISO_CODES */
4326 }
4327
4328 iso_639_name = g_hash_table_lookup (iso_639_table, tokens[0]);
4329
4330 if (!iso_639_name) {
4331 g_strfreev (tokens);
4332 return FALSE;
4333 }
4334
4335 if (out_language_name)
4336 *out_language_name = g_strdup (iso_639_name);
4337
4338 if (g_strv_length (tokens) < 2)
4339 goto exit;
4340
4341 if (out_country_name) {
4342 iso_3166_name = g_hash_table_lookup (iso_3166_table, tokens[1]);
4343
4344 if (iso_3166_name)
4345 *out_country_name = g_strdup (iso_3166_name);
4346 else
4347 *out_country_name = g_strdup (tokens[1]);
4348 }
4349
4350 exit:
4351 if (out_language_name && *out_language_name) {
4352 gchar *ptr;
4353
4354 /* When the language name has ';' then strip the string at it */
4355 ptr = strchr (*out_language_name, ';');
4356 if (ptr)
4357 *ptr = '\0';
4358 }
4359
4360 if (out_country_name && *out_country_name) {
4361 gchar *ptr;
4362
4363 /* When the country name has two or more ';' then strip the string at the second of them */
4364 ptr = strchr (*out_country_name, ';');
4365 if (ptr)
4366 ptr = strchr (ptr + 1, ';');
4367 if (ptr)
4368 *ptr = '\0';
4369 }
4370
4371 g_strfreev (tokens);
4372
4373 return TRUE;
4374 }
4375
4376 /**
4377 * e_util_get_language_name:
4378 * @language_tag: Language tag to get its name for, like "en_US"
4379 *
4380 * Returns: (transfer full): Newly allocated string with localized language name
4381 *
4382 * Since: 3.32
4383 **/
4384 gchar *
e_util_get_language_name(const gchar * language_tag)4385 e_util_get_language_name (const gchar *language_tag)
4386 {
4387 gchar *language_name = NULL, *country_name = NULL;
4388 gchar *res;
4389
4390 g_return_val_if_fail (language_tag != NULL, NULL);
4391
4392 if (!e_util_get_language_info (language_tag, &language_name, &country_name)) {
4393 return g_strdup_printf (
4394 /* Translators: %s is the language ISO code. */
4395 C_("language", "Unknown (%s)"), language_tag);
4396 }
4397
4398 if (!country_name)
4399 return language_name;
4400
4401 res = g_strdup_printf (
4402 /* Translators: The first %s is the language name, and the
4403 * second is the country name. Example: "French (France)" */
4404 C_("language", "%s (%s)"), language_name, country_name);
4405
4406 g_free (language_name);
4407 g_free (country_name);
4408
4409 return res;
4410 }
4411
4412 /**
4413 * e_misc_util_free_global_memory:
4414 *
4415 * Frees global memory allocated by evolution-util library.
4416 * This is usually called at the end of the application.
4417 *
4418 * Since: 3.32
4419 **/
4420 void
e_misc_util_free_global_memory(void)4421 e_misc_util_free_global_memory (void)
4422 {
4423 g_clear_pointer (&iso_639_table, g_hash_table_destroy);
4424 g_clear_pointer (&iso_3166_table, g_hash_table_destroy);
4425
4426 e_util_cleanup_settings ();
4427 e_spell_checker_free_global_memory ();
4428 e_simple_async_result_free_global_memory ();
4429 }
4430
4431 /**
4432 * e_util_can_preview_filename:
4433 * @filename: (nullable): a file name to test
4434 *
4435 * Returns: Whether the @filename can be used to create a preview
4436 * in GtkFileChooser and such widgets. For example directories,
4437 * pipes and sockets cannot by used.
4438 *
4439 * Since: 3.32
4440 **/
4441 gboolean
e_util_can_preview_filename(const gchar * filename)4442 e_util_can_preview_filename (const gchar *filename)
4443 {
4444 GStatBuf st;
4445
4446 if (!filename || !*filename)
4447 return FALSE;
4448
4449 return g_stat (filename, &st) == 0
4450 && !S_ISDIR (st.st_mode)
4451 #ifdef S_ISFIFO
4452 && !S_ISFIFO (st.st_mode)
4453 #endif
4454 #ifdef S_ISSOCK
4455 && !S_ISSOCK (st.st_mode)
4456 #endif
4457 ;
4458 }
4459
4460 /**
4461 * e_util_markup_append_escaped:
4462 * @buffer: a #GString buffer to append escaped text to
4463 * @format: printf-like format of the string to append
4464 * @...: arguments for the format
4465 *
4466 * Appends escaped markup text into @buffer. This function is
4467 * similar to g_markup_printf_escaped(), except it appends
4468 * the escaped text into a #GString.
4469 *
4470 * Since: 3.36
4471 **/
4472 void
e_util_markup_append_escaped(GString * buffer,const gchar * format,...)4473 e_util_markup_append_escaped (GString *buffer,
4474 const gchar *format,
4475 ...)
4476 {
4477 va_list va;
4478 gchar *escaped;
4479
4480 g_return_if_fail (buffer != NULL);
4481 g_return_if_fail (format != NULL);
4482
4483 va_start (va, format);
4484 escaped = g_markup_vprintf_escaped (format, va);
4485 va_end (va);
4486
4487 g_string_append (buffer, escaped);
4488
4489 g_free (escaped);
4490 }
4491
4492 void
e_util_enum_supported_locales(void)4493 e_util_enum_supported_locales (void)
4494 {
4495 GString *locale;
4496 gchar *previous_locale;
4497 gint ii, category = LC_ALL;
4498
4499 #if defined(LC_MESSAGES)
4500 category = LC_MESSAGES;
4501 #endif
4502
4503 previous_locale = g_strdup (setlocale (category, NULL));
4504
4505 locale = g_string_sized_new (32);
4506
4507 for (ii = 0; e_supported_locales[ii].code; ii++) {
4508 gchar *catalog_filename;
4509
4510 catalog_filename = g_build_filename (EVOLUTION_LOCALEDIR, e_supported_locales[ii].code, "LC_MESSAGES", GETTEXT_PACKAGE ".mo", NULL);
4511
4512 if (catalog_filename && g_file_test (catalog_filename, G_FILE_TEST_EXISTS)) {
4513 g_string_printf (locale, "%s.UTF-8", e_supported_locales[ii].locale);
4514
4515 if (!setlocale (category, locale->str)) {
4516 e_supported_locales[ii].locale = NULL;
4517 }
4518 } else {
4519 e_supported_locales[ii].locale = NULL;
4520 }
4521
4522 g_free (catalog_filename);
4523 }
4524
4525 setlocale (category, previous_locale);
4526
4527 g_string_free (locale, TRUE);
4528 g_free (previous_locale);
4529 }
4530
4531 const ESupportedLocales *
e_util_get_supported_locales(void)4532 e_util_get_supported_locales (void)
4533 {
4534 return e_supported_locales;
4535 }
4536
4537 gchar *
e_util_get_uri_tooltip(const gchar * uri)4538 e_util_get_uri_tooltip (const gchar *uri)
4539 {
4540 CamelInternetAddress *address;
4541 CamelURL *curl;
4542 const gchar *format = NULL;
4543 GString *message = NULL;
4544 gchar *who;
4545
4546 if (!uri || !*uri)
4547 goto exit;
4548
4549 if (g_str_has_prefix (uri, "mailto:"))
4550 format = _("Click to mail %s");
4551 else if (g_str_has_prefix (uri, "callto:") ||
4552 g_str_has_prefix (uri, "h323:") ||
4553 g_str_has_prefix (uri, "sip:") ||
4554 g_str_has_prefix (uri, "tel:"))
4555 format = _("Click to call %s");
4556 else if (g_str_has_prefix (uri, "##"))
4557 message = g_string_new (_("Click to hide/unhide addresses"));
4558 else if (g_str_has_prefix (uri, "mail:")) {
4559 const gchar *fragment;
4560 SoupURI *soup_uri;
4561
4562 soup_uri = soup_uri_new (uri);
4563 if (!soup_uri)
4564 goto exit;
4565
4566 message = g_string_new (NULL);
4567 fragment = soup_uri_get_fragment (soup_uri);
4568
4569 if (fragment && *fragment)
4570 g_string_append_printf (message, _("Go to the section %s of the message"), fragment);
4571 else
4572 g_string_append (message, _("Go to the beginning of the message"));
4573
4574 soup_uri_free (soup_uri);
4575 } else {
4576 message = g_string_new (NULL);
4577
4578 g_string_append_printf (message, _("Click to open %s"), uri);
4579 }
4580
4581 if (!format)
4582 goto exit;
4583
4584 /* XXX Use something other than Camel here. Surely
4585 * there's other APIs around that can do this. */
4586 curl = camel_url_new (uri, NULL);
4587 address = camel_internet_address_new ();
4588 camel_address_decode (CAMEL_ADDRESS (address), curl->path);
4589 camel_internet_address_sanitize_ascii_domain (address);
4590 who = camel_address_format (CAMEL_ADDRESS (address));
4591 g_object_unref (address);
4592 camel_url_free (curl);
4593
4594 if (!who)
4595 who = g_strdup (strchr (uri, ':') + 1);
4596
4597 message = g_string_new (NULL);
4598
4599 g_string_append_printf (message, format, who);
4600
4601 g_free (who);
4602
4603 exit:
4604
4605 if (!message)
4606 return NULL;
4607
4608 /* This limits the chars that appear as in some
4609 links the size of chars can extend out of the screen */
4610 if (g_utf8_strlen (message->str, -1) > 150) {
4611 gchar *pos;
4612
4613 pos = g_utf8_offset_to_pointer (message->str, 150);
4614 g_string_truncate (message, pos - message->str);
4615 g_string_append (message , _("…"));
4616 }
4617
4618 return g_string_free (message, FALSE);
4619 }
4620
4621 void
e_util_ensure_scrolled_window_height(GtkScrolledWindow * scrolled_window)4622 e_util_ensure_scrolled_window_height (GtkScrolledWindow *scrolled_window)
4623 {
4624 GtkWidget *toplevel;
4625 GdkScreen *screen;
4626 gint toplevel_height, scw_height, require_scw_height = 0, max_height;
4627
4628 g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
4629
4630 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scrolled_window));
4631 if (!toplevel || !gtk_widget_is_toplevel (toplevel))
4632 return;
4633
4634 scw_height = gtk_widget_get_allocated_height (GTK_WIDGET (scrolled_window));
4635
4636 gtk_widget_get_preferred_height_for_width (gtk_bin_get_child (GTK_BIN (scrolled_window)),
4637 gtk_widget_get_allocated_width (GTK_WIDGET (scrolled_window)),
4638 &require_scw_height, NULL);
4639
4640 if (scw_height >= require_scw_height) {
4641 if (require_scw_height > 0)
4642 gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
4643 return;
4644 }
4645
4646 if (!GTK_IS_WINDOW (toplevel) ||
4647 !gtk_widget_get_window (toplevel))
4648 return;
4649
4650 screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
4651 if (screen) {
4652 gint monitor;
4653 GdkRectangle workarea;
4654
4655 monitor = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (toplevel));
4656 if (monitor < 0)
4657 monitor = 0;
4658
4659 gdk_screen_get_monitor_workarea (screen, monitor, &workarea);
4660
4661 /* can enlarge up to 4 / 5 monitor's work area height */
4662 max_height = workarea.height * 4 / 5;
4663 } else {
4664 return;
4665 }
4666
4667 toplevel_height = gtk_widget_get_allocated_height (toplevel);
4668 if (toplevel_height + require_scw_height - scw_height > max_height)
4669 return;
4670
4671 gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
4672 }
4673
4674 void
e_util_make_safe_filename(gchar * filename)4675 e_util_make_safe_filename (gchar *filename)
4676 {
4677 const gchar *unsafe_chars = "/\\";
4678 GSettings *settings;
4679 gchar *pp, *ts, *illegal_chars;
4680 gunichar cc;
4681
4682 g_return_if_fail (filename != NULL);
4683
4684 settings = e_util_ref_settings ("org.gnome.evolution.shell");
4685 illegal_chars = g_settings_get_string (settings, "filename-illegal-chars");
4686 g_clear_object (&settings);
4687
4688 pp = filename;
4689
4690 while (pp && *pp) {
4691 cc = g_utf8_get_char (pp);
4692 ts = pp;
4693 pp = g_utf8_next_char (pp);
4694
4695 if (!g_unichar_isprint (cc) ||
4696 (cc < 0xff && (strchr (unsafe_chars, cc & 0xff) ||
4697 (illegal_chars && *illegal_chars && strchr (illegal_chars, cc & 0xff))))) {
4698 while (ts < pp)
4699 *ts++ = '_';
4700 }
4701 }
4702
4703 g_free (illegal_chars);
4704 }
4705