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