From b6313289303f2263649c70ea477e04aea4a994ec Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 4 Aug 2009 15:04:02 +0200 Subject: Bug #205137 - Configurable date formats in components --- .../GNOME_Evolution_Addressbook.server.in.in | 4 +- addressbook/gui/component/autocompletion-config.c | 9 +- addressbook/gui/widgets/e-addressbook-view.c | 11 +- calendar/gui/dialogs/cal-prefs-dialog.c | 8 +- calendar/gui/dialogs/cal-prefs-dialog.glade | 79 +++ calendar/gui/e-cal-list-view.c | 6 + calendar/gui/e-calendar-table.c | 5 + calendar/gui/e-calendar-view.c | 8 +- calendar/gui/e-cell-date-edit-text.c | 7 +- calendar/gui/e-memo-table.c | 5 + e-util/Makefile.am | 2 + e-util/e-datetime-format.c | 571 +++++++++++++++++++++ e-util/e-datetime-format.h | 46 ++ mail/em-format-html-display.c | 29 +- mail/em-format-html.c | 34 +- mail/em-mailer-prefs.c | 8 +- mail/mail-config.glade | 93 ++++ mail/message-list.c | 8 +- po/POTFILES.in | 1 + widgets/table/e-cell-date.c | 79 +-- widgets/table/e-cell-date.h | 1 + 21 files changed, 893 insertions(+), 121 deletions(-) create mode 100644 e-util/e-datetime-format.c create mode 100644 e-util/e-datetime-format.h diff --git a/addressbook/gui/component/GNOME_Evolution_Addressbook.server.in.in b/addressbook/gui/component/GNOME_Evolution_Addressbook.server.in.in index 823d5bd89f..4000f01526 100644 --- a/addressbook/gui/component/GNOME_Evolution_Addressbook.server.in.in +++ b/addressbook/gui/component/GNOME_Evolution_Addressbook.server.in.in @@ -115,10 +115,10 @@ + _value="Contacts"/> + _value="Configure contacts and autocompletion here"/> diff --git a/addressbook/gui/component/autocompletion-config.c b/addressbook/gui/component/autocompletion-config.c index 9773f5bd5b..ebb2381e1e 100644 --- a/addressbook/gui/component/autocompletion-config.c +++ b/addressbook/gui/component/autocompletion-config.c @@ -39,6 +39,8 @@ #include #include +#include "e-util/e-datetime-format.h" + typedef struct { EvolutionConfigControl *config_control; @@ -159,7 +161,7 @@ autocompletion_config_control_new (void) { AutocompletionConfig *ac; CORBA_Environment ev; - GtkWidget *scrolledwin, *vbox, *itembox, *w; + GtkWidget *scrolledwin, *vbox, *itembox, *w, *table; ac = g_new0 (AutocompletionConfig, 1); @@ -177,6 +179,11 @@ autocompletion_config_control_new (void) g_signal_connect (w, "toggled", (GCallback)show_address_check_toggled_cb, ac); gtk_box_pack_start (GTK_BOX (itembox), w, FALSE, FALSE, 0); + itembox = add_section (vbox, _("Date/Time Format"), FALSE); + table = gtk_table_new (1, 3, FALSE); + gtk_box_pack_start (GTK_BOX (itembox), table, TRUE, TRUE, 0); + e_datetime_format_add_setup_widget (table, 0, "addressbook", "table", DTFormatKindDateTime, _("Table column:")); + itembox = add_section (vbox, _("Look up in address books"), TRUE); ac->source_list = e_source_list_new_for_gconf_default ("/apps/evolution/addressbook/sources"); diff --git a/addressbook/gui/widgets/e-addressbook-view.c b/addressbook/gui/widgets/e-addressbook-view.c index 25e55666d4..283b25f89f 100644 --- a/addressbook/gui/widgets/e-addressbook-view.c +++ b/addressbook/gui/widgets/e-addressbook-view.c @@ -26,6 +26,7 @@ #include #include #include
+#include
#include #include #include @@ -1281,18 +1282,26 @@ static void create_table_view (EABView *view) { ETableModel *adapter; + ETableExtras *extras; + ECell *cell; GtkWidget *table; gchar *etspecfile; adapter = eab_table_adapter_new(view->model); + extras = e_table_extras_new (); + + /* set proper format component for a default 'date' cell renderer */ + cell = e_table_extras_get_cell (extras, "date"); + e_cell_date_set_format_component (E_CELL_DATE (cell), "addressbook"); + /* Here we create the table. We give it the three pieces of the table we've created, the header, the model, and the initial layout. It does the rest. */ etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, "e-addressbook-view.etspec", NULL); - table = e_table_scrolled_new_from_spec_file (adapter, NULL, etspecfile, NULL); + table = e_table_scrolled_new_from_spec_file (adapter, extras, etspecfile, NULL); g_free (etspecfile); view->object = G_OBJECT(adapter); diff --git a/calendar/gui/dialogs/cal-prefs-dialog.c b/calendar/gui/dialogs/cal-prefs-dialog.c index 2c19acaadd..2fc17202cc 100644 --- a/calendar/gui/dialogs/cal-prefs-dialog.c +++ b/calendar/gui/dialogs/cal-prefs-dialog.c @@ -31,6 +31,7 @@ #include "../calendar-config.h" #include "cal-prefs-dialog.h" #include +#include "e-util/e-datetime-format.h" #include #include #include @@ -779,7 +780,7 @@ calendar_prefs_dialog_construct (CalendarPrefsDialog *prefs) ECalConfig *ec; ECalConfigTargetPrefs *target; gint i; - GtkWidget *toplevel; + GtkWidget *toplevel, *table; GSList *l; const gchar *working_day_names[] = { "sun_button", @@ -859,6 +860,11 @@ calendar_prefs_dialog_construct (CalendarPrefsDialog *prefs) toplevel = e_config_create_widget ((EConfig *)ec); gtk_container_add (GTK_CONTAINER (prefs), toplevel); + /* date/time format */ + table = glade_xml_get_widget (gui, "datetime_format_table"); + e_datetime_format_add_setup_widget (table, 0, "calendar", "table", DTFormatKindDateTime, _("Time and date:")); + e_datetime_format_add_setup_widget (table, 1, "calendar", "table", DTFormatKindDate, _("Date only:")); + show_config (prefs); /* FIXME: weakref? */ setup_changes (prefs); diff --git a/calendar/gui/dialogs/cal-prefs-dialog.glade b/calendar/gui/dialogs/cal-prefs-dialog.glade index 63e9e6092f..984e509b64 100644 --- a/calendar/gui/dialogs/cal-prefs-dialog.glade +++ b/calendar/gui/dialogs/cal-prefs-dialog.glade @@ -1024,6 +1024,85 @@ Days 3 + + + + True + <span weight="bold">Date/Time Format</span> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + False + 12 + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 1 + 3 + False + 0 + 0 + + + 0 + True + True + + + + + 0 + True + True + + 1 diff --git a/calendar/gui/e-cal-list-view.c b/calendar/gui/e-cal-list-view.c index 0081dc1340..1da7f16037 100644 --- a/calendar/gui/e-cal-list-view.c +++ b/calendar/gui/e-cal-list-view.c @@ -40,6 +40,7 @@ #include
#include
#include
+#include
#include #include #include @@ -256,6 +257,11 @@ setup_e_table (ECalListView *cal_list_view) e_table_extras_add_compare (extras, "date-compare", date_compare_cb); + + /* set proper format component for a default 'date' cell renderer */ + cell = e_table_extras_get_cell (extras, "date"); + e_cell_date_set_format_component (E_CELL_DATE (cell), "calendar"); + /* Create table view */ etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, diff --git a/calendar/gui/e-calendar-table.c b/calendar/gui/e-calendar-table.c index 70f347bae3..284922c029 100644 --- a/calendar/gui/e-calendar-table.c +++ b/calendar/gui/e-calendar-table.c @@ -42,6 +42,7 @@ #include
#include
#include
+#include
#include #include #include @@ -687,6 +688,10 @@ e_calendar_table_init (ECalendarTable *cal_table) e_table_extras_add_pixbuf(extras, "complete", pixbuf); g_object_unref(pixbuf); + /* set proper format component for a default 'date' cell renderer */ + cell = e_table_extras_get_cell (extras, "date"); + e_cell_date_set_format_component (E_CELL_DATE (cell), "calendar"); + /* Create the table */ etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, diff --git a/calendar/gui/e-calendar-view.c b/calendar/gui/e-calendar-view.c index cd42454d64..286db6b5f1 100644 --- a/calendar/gui/e-calendar-view.c +++ b/calendar/gui/e-calendar-view.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -2222,16 +2223,11 @@ tooltip_grab (GtkWidget *tooltip, GdkEventKey *event, ECalendarView *view) static gchar * get_label (struct icaltimetype *tt, icaltimezone *f_zone, icaltimezone *t_zone) { - gchar buffer[1000]; struct tm tmp_tm; tmp_tm = icaltimetype_to_tm_with_zone (tt, f_zone, t_zone); - e_time_format_date_and_time (&tmp_tm, - calendar_config_get_24_hour_format (), - FALSE, FALSE, - buffer, 1000); - return g_strdup (buffer); + return e_datetime_format_format_tm ("calendar", "table", DTFormatKindDateTime, &tmp_tm); } void diff --git a/calendar/gui/e-cell-date-edit-text.c b/calendar/gui/e-cell-date-edit-text.c index 64dd48ddf1..5836b8ac66 100644 --- a/calendar/gui/e-cell-date-edit-text.c +++ b/calendar/gui/e-cell-date-edit-text.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "e-cell-date-edit-text.h" @@ -63,7 +64,6 @@ ecd_get_text (ECellText *cell, ETableModel *model, gint col, gint row) ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (cell); ECellDateEditValue *dv = e_table_model_value_at (model, col, row); struct tm tmp_tm; - gchar buffer[64]; if (!dv) return g_strdup (""); @@ -74,10 +74,7 @@ ecd_get_text (ECellText *cell, ETableModel *model, gint col, gint row) it will be set to the current timezone. See set_value(). */ tmp_tm = icaltimetype_to_tm_with_zone (&dv->tt, dv->zone, ecd->zone); - e_time_format_date_and_time (&tmp_tm, ecd->use_24_hour_format, - !dv->tt.is_date, FALSE, - buffer, sizeof (buffer)); - return g_strdup (buffer); + return e_datetime_format_format_tm ("calendar", "table", dv->tt.is_date ? DTFormatKindDate : DTFormatKindDateTime, &tmp_tm); } static void diff --git a/calendar/gui/e-memo-table.c b/calendar/gui/e-memo-table.c index 47b3d4410c..50158899ac 100644 --- a/calendar/gui/e-memo-table.c +++ b/calendar/gui/e-memo-table.c @@ -41,6 +41,7 @@ #include
#include
#include
+#include
#include #include #include @@ -255,6 +256,10 @@ e_memo_table_init (EMemoTable *memo_table) e_table_extras_add_cell(extras, "icon", cell); e_table_extras_add_pixbuf(extras, "icon", icon_pixbufs[0]); + /* set proper format component for a default 'date' cell renderer */ + cell = e_table_extras_get_cell (extras, "date"); + e_cell_date_set_format_component (E_CELL_DATE (cell), "calendar"); + /* Create the table */ etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, diff --git a/e-util/Makefile.am b/e-util/Makefile.am index d723e3b290..35d254b4eb 100644 --- a/e-util/Makefile.am +++ b/e-util/Makefile.am @@ -45,6 +45,7 @@ eutilinclude_HEADERS = \ e-config.h \ e-corba-utils.h \ e-cursor.h \ + e-datetime-format.h \ e-dialog-utils.h \ e-dialog-widgets.h \ e-error.h \ @@ -85,6 +86,7 @@ libeutil_la_SOURCES = \ e-config.c \ e-corba-utils.c \ e-cursor.c \ + e-datetime-format.c \ e-dialog-utils.c \ e-dialog-widgets.c \ e-error.c \ diff --git a/e-util/e-datetime-format.c b/e-util/e-datetime-format.c new file mode 100644 index 0000000000..c6900ff8ca --- /dev/null +++ b/e-util/e-datetime-format.c @@ -0,0 +1,571 @@ +/* + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Copyright (C) 1999-2009 Novell, Inc. (www.novell.com) + * + */ + +#include +#include +#include + +#include "e-datetime-format.h" +#include "e-util.h" + +#define KEYS_FILENAME "datetime-formats" +#define KEYS_GROUPNAME "formats" + +#ifdef G_OS_WIN32 +/* The localtime() in Microsoft's C library *is* thread-safe */ +#define localtime_r(timep, result) (localtime (timep) ? memcpy ((result), localtime (timep), sizeof (*(result))) : 0) +#endif + +static GHashTable *key2fmt = NULL; + +static GKeyFile *setup_keyfile = NULL; /* used on the combo */ +static gint setup_keyfile_instances = 0; + +static void +save_keyfile (GKeyFile *keyfile) +{ + gchar *contents; + gchar *filename; + gsize length; + GError *error = NULL; + + g_return_if_fail (keyfile != NULL); + + filename = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL); + contents = g_key_file_to_data (keyfile, &length, NULL); + + g_file_set_contents (filename, contents, length, &error); + + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (contents); + g_free (filename); +} + +static void +ensure_loaded (void) +{ + GKeyFile *keyfile; + gchar *str, **keys; + gint i; + + if (key2fmt) + return; + + key2fmt = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + keyfile = g_key_file_new (); + + str = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL); + g_key_file_load_from_file (keyfile, str, G_KEY_FILE_NONE, NULL); + g_free (str); + + keys = g_key_file_get_keys (keyfile, KEYS_GROUPNAME, NULL, NULL); + + if (keys) { + for (i = 0; keys [i]; i++) { + str = g_key_file_get_string (keyfile, KEYS_GROUPNAME, keys [i], NULL); + if (str) + g_hash_table_insert (key2fmt, g_strdup (keys [i]), str); + } + + g_strfreev (keys); + } + + g_key_file_free (keyfile); +} + +static const gchar * +get_default_format (DTFormatKind kind, const gchar *key) +{ + const gchar *res = NULL; + + ensure_loaded (); + + switch (kind) { + case DTFormatKindDate: + res = g_hash_table_lookup (key2fmt, "Default-Date"); + if (!res) + res = "%x"; + break; + case DTFormatKindTime: + res = g_hash_table_lookup (key2fmt, "Default-Time"); + if (!res) + res = "%X"; + break; + case DTFormatKindDateTime: + res = g_hash_table_lookup (key2fmt, "Default-DateTime"); + if (!res && key && g_str_has_prefix (key, "mail-table")) + res = "%ad %H:%M"; + if (!res) + res = "%x %X"; /* %c is also possible, but it doesn't play well with time zone identifiers */ + break; + case DTFormatKindShortDate: + res = g_hash_table_lookup (key2fmt, "Default-ShortDate"); + if (!res) + res = "%A, %B %d"; + break; + } + + if (!res) + res = "%x %X"; + + return res; +} + +static const gchar * +get_format_internal (const gchar *key, DTFormatKind kind) +{ + const gchar *res; + + ensure_loaded (); + + g_return_val_if_fail (key != NULL, NULL); + g_return_val_if_fail (key2fmt != NULL, NULL); + + res = g_hash_table_lookup (key2fmt, key); + if (!res) + res = get_default_format (kind, key); + + return res; +} + +static void +set_format_internal (const gchar *key, const gchar *fmt, GKeyFile *keyfile) +{ + ensure_loaded (); + + g_return_if_fail (key != NULL); + g_return_if_fail (key2fmt != NULL); + g_return_if_fail (keyfile != NULL); + + if (!fmt || !*fmt) { + g_hash_table_remove (key2fmt, key); + g_key_file_remove_key (keyfile, KEYS_GROUPNAME, key, NULL); + } else { + g_hash_table_insert (key2fmt, g_strdup (key), g_strdup (fmt)); + g_key_file_set_string (keyfile, KEYS_GROUPNAME, key, fmt); + } +} + +static gchar * +format_relative_date (time_t tvalue, time_t ttoday, const struct tm *value, const struct tm *today) +{ + gchar *res = g_strdup (get_default_format (DTFormatKindDate, NULL)); + + g_return_val_if_fail (value != NULL, res); + g_return_val_if_fail (today != NULL, res); + + /* if it's more than a week, use the default date format */ + if (ttoday - tvalue > 7 * 24 * 60 * 60 || + tvalue - ttoday > 7 * 24 * 60 * 60) + return res; + + g_free (res); + + if (value->tm_year == today->tm_year && + value->tm_mon == today->tm_mon && + value->tm_mday == today->tm_mday) { + res = g_strdup (_("Today")); + } else { + gint diff = (gint) (tvalue - ttoday); + gint since_midnight = today->tm_sec + (60 * today->tm_min) + (60 * 60 * today->tm_hour); + gboolean future = (diff > 0); + + if (!future) + diff *= -1; + + diff = (diff - since_midnight) / (24 * 60 * 60); + if (diff <= 1) { + if (future) + res = g_strdup (_("Tomorrow")); + else + res = g_strdup (_("Yesterday")); + } else { + if (future) + res = g_strdup_printf (_("%d days from now"), diff); + else + res = g_strdup_printf (_("%d days ago"), diff); + } + } + + return res; +} + +static gchar * +format_internal (const gchar *key, DTFormatKind kind, time_t tvalue, struct tm *tm_value) +{ + const gchar *fmt; + gchar buff[129]; + GString *use_fmt = NULL; + gint i, last = 0; + struct tm today, value; + time_t ttoday; + + tzset(); + if (!tm_value) { + localtime_r (&tvalue, &value); + tm_value = &value; + } else { + /* recalculate tvalue to local (system) timezone */ + tvalue = mktime (tm_value); + localtime_r (&tvalue, &value); + } + + fmt = get_format_internal (key, kind); + for (i = 0; fmt [i]; i++) { + if (fmt [i] == '%') { + if (fmt [i + 1] == '%') { + i++; + } else if (fmt [i + 1] == 'a' && fmt [i + 2] == 'd' && (fmt [i + 3] == 0 || !g_ascii_isalpha (fmt [i + 3]))) { + gchar *ad; + + /* "%ad" for abbreviated date */ + if (!use_fmt) { + use_fmt = g_string_new (""); + + ttoday = time (NULL); + localtime_r (&ttoday, &today); + } + + g_string_append_len (use_fmt, fmt + last, i - last); + last = i + 3; + i += 2; + + ad = format_relative_date (tvalue, ttoday, &value, &today); + if (ad) + g_string_append (use_fmt, ad); + else if (g_ascii_isspace (fmt [i + 3])) + i++; + + g_free (ad); + } + } + } + + if (use_fmt && last < i) { + g_string_append_len (use_fmt, fmt + last, i - last); + } + + e_utf8_strftime_fix_am_pm (buff, sizeof (buff) - 1, use_fmt ? use_fmt->str : fmt, tm_value); + + if (use_fmt) + g_string_free (use_fmt, TRUE); + + return g_strstrip (g_strdup (buff)); +} + +static void +fill_combo_formats (GtkWidget *combo, const gchar *key, DTFormatKind kind) +{ + const gchar *date_items [] = { + N_ ("Use locale default"), + "%m/%d/%y", /* American style */ + "%m/%d/%Y", /* American style, full year */ + "%d.%m.%y", /* non-American style */ + "%d.%m.%Y", /* non-American style, full year */ + "%ad", /* abbreviated date, like "Today" */ + NULL + }; + + const gchar *time_items [] = { + N_ ("Use locale default"), + "%I:%M:%S %p", /* 12hours style */ + "%I:%M %p", /* 12hours style, without seconds */ + "%H:%M:%S", /* 24hours style */ + "%H:%M", /* 24hours style, without seconds */ + NULL + }; + + const gchar *datetime_items [] = { + N_ ("Use locale default"), + "%m/%d/%y %I:%M:%S %p", /* American style */ + "%m/%d/%Y %I:%M:%S %p", /* American style, full year */ + "%m/%d/%y %I:%M %p", /* American style, without seconds */ + "%m/%d/%Y %I:%M %p", /* American style, without seconds, full year */ + "%ad %I:%M:%S %p", /* %ad is an abbreviated date, like "Today" */ + "%ad %I:%M %p", /* %ad is an abbreviated date, like "Today", without seconds */ + "%d.%m.%y %H:%M:%S", /* non-American style */ + "%d.%m.%Y %H:%M:%S", /* non-American style, full year */ + "%d.%m.%y %H:%M", /* non-American style, without seconds */ + "%d.%m.%Y %H:%M", /* non-American style, without seconds, full year */ + "%ad %H:%M:%S", + "%ad %H:%M", /* without seconds */ + NULL + }; + + const gchar *shortdate_items [] = { + "%A, %B %d", + "%A, %d %B", + "%a, %b %d", + "%a, %d %b", + NULL + }; + + const gchar **items = NULL; + int i, idx = 0; + const gchar *fmt; + + g_return_if_fail (GTK_IS_COMBO_BOX_ENTRY (combo)); + + switch (kind) { + case DTFormatKindDate: + items = date_items; + break; + case DTFormatKindTime: + items = time_items; + break; + case DTFormatKindDateTime: + items = datetime_items; + break; + case DTFormatKindShortDate: + items = shortdate_items; + break; + } + + g_return_if_fail (items != NULL); + + fmt = get_format_internal (key, kind); + + for (i = 0; items [i]; i++) { + if (i == 0) { + gtk_combo_box_append_text ((GtkComboBox *) combo, _(items[i])); + } else { + gtk_combo_box_append_text ((GtkComboBox *) combo, items[i]); + if (!idx && fmt && g_str_equal (fmt, items[i])) + idx = i; + } + } + + if (idx == 0 && fmt && !g_str_equal (fmt, get_default_format (kind, key))) { + gtk_combo_box_append_text ((GtkComboBox *) combo, fmt); + idx = i; + } + + gtk_combo_box_set_active ((GtkComboBox *) combo, idx); +} + +static void +update_preview_widget (GtkWidget *combo) +{ + GtkWidget *preview; + const gchar *key; + gchar *value; + time_t now; + + g_return_if_fail (combo != NULL); + g_return_if_fail (GTK_IS_COMBO_BOX_ENTRY (combo)); + + preview = g_object_get_data (G_OBJECT (combo), "preview-label"); + g_return_if_fail (preview != NULL); + g_return_if_fail (GTK_IS_LABEL (preview)); + + key = g_object_get_data (G_OBJECT (combo), "format-key"); + g_return_if_fail (key != NULL); + + time (&now); + + value = format_internal (key, GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "format-kind")), now, NULL); + gtk_label_set_text (GTK_LABEL (preview), value ? value : ""); + g_free (value); +} + +static void +format_combo_changed_cb (GtkWidget *combo, gpointer user_data) +{ + const gchar *key; + DTFormatKind kind; + GKeyFile *keyfile; + + g_return_if_fail (combo != NULL); + g_return_if_fail (GTK_IS_COMBO_BOX_ENTRY (combo)); + + key = g_object_get_data (G_OBJECT (combo), "format-key"); + g_return_if_fail (key != NULL); + + kind = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "format-kind")); + keyfile = g_object_get_data (G_OBJECT (combo), "setup-key-file"); + + if (kind != DTFormatKindShortDate && gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) == 0) { + /* use locale default */ + set_format_internal (key, NULL, keyfile); + } else { + gchar *text; + + text = gtk_combo_box_get_active_text (GTK_COMBO_BOX (combo)); + set_format_internal (key, text, keyfile); + g_free (text); + } + + update_preview_widget (combo); + + /* save on every change only because 'unref_setup_keyfile' is never called :( + how about in kill-bonobo? */ + save_keyfile (keyfile); +} + +static gchar * +gen_key (const gchar *component, const gchar *part, DTFormatKind kind) +{ + const gchar *kind_str = NULL; + + g_return_val_if_fail (component != NULL, NULL); + g_return_val_if_fail (*component != 0, NULL); + + + switch (kind) { + case DTFormatKindDate: + kind_str = "Date"; + break; + case DTFormatKindTime: + kind_str = "Time"; + break; + case DTFormatKindDateTime: + kind_str = "DateTime"; + break; + case DTFormatKindShortDate: + kind_str = "ShortDate"; + break; + } + + return g_strconcat (component, (part && *part) ? "-" : "", part && *part ? part : "", "-", kind_str, NULL); +} + +static void +unref_setup_keyfile (gpointer ptr) +{ + g_return_if_fail (ptr == setup_keyfile); + g_return_if_fail (setup_keyfile != NULL); + g_return_if_fail (setup_keyfile_instances > 0); + + /* this is never called :( */ + setup_keyfile_instances--; + if (setup_keyfile_instances == 0) { + save_keyfile (setup_keyfile); + g_key_file_free (setup_keyfile); + setup_keyfile = NULL; + } +} + +/** + * e_datetime_format_add_setup_widget: + * @table: Where to attach widgets. Requires 3 columns. + * @row: On which row to attach. + * @component: Component identifier for the format. Cannot be empty nor NULL. + * @part: Part in the component, can be NULL or empty string. + * @kind: Kind of the format for the component/part. + * @caption: Caption for the widget, can be NULL, then the "Format:" is used. + * + * Adds a setup widget for a component and part. The table should have 3 columns. + * All the work related to loading and saving the value is done automatically, + * on user's changes. + **/ +void +e_datetime_format_add_setup_widget (GtkWidget *table, gint row, const gchar *component, const gchar *part, DTFormatKind kind, const gchar *caption) +{ + GtkWidget *label, *combo, *preview, *align; + gchar *key; + + g_return_if_fail (table != NULL); + g_return_if_fail (row >= 0); + g_return_if_fail (component != NULL); + g_return_if_fail (*component != 0); + + key = gen_key (component, part, kind); + + label = gtk_label_new_with_mnemonic (caption ? caption : _("Format:")); + combo = gtk_combo_box_entry_new_text (); + + fill_combo_formats (combo, key, kind); + gtk_label_set_mnemonic_widget ((GtkLabel *)label, combo); + + align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (align), combo); + + gtk_table_attach ((GtkTable *) table, label, 0, 1, row, row + 1, 0, 0, 2, 0); + gtk_table_attach ((GtkTable *) table, align, 1, 2, row, row + 1, 0, 0, 2, 0); + + preview = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (preview), 0.0, 0.5); + gtk_label_set_ellipsize (GTK_LABEL (preview), PANGO_ELLIPSIZE_END); + gtk_table_attach ((GtkTable *) table, preview, 2, 3, row, row + 1, GTK_EXPAND | GTK_FILL, 0, 2, 0); + + if (!setup_keyfile) { + gchar *filename; + + filename = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL); + setup_keyfile = g_key_file_new (); + g_key_file_load_from_file (setup_keyfile, filename, G_KEY_FILE_NONE, NULL); + g_free (filename); + + setup_keyfile_instances = 1; + } else { + setup_keyfile_instances++; + } + + g_object_set_data (G_OBJECT (combo), "preview-label", preview); + g_object_set_data (G_OBJECT (combo), "format-kind", GINT_TO_POINTER (kind)); + g_object_set_data_full (G_OBJECT (combo), "format-key", key, g_free); + g_object_set_data_full (G_OBJECT (combo), "setup-key-file", setup_keyfile, unref_setup_keyfile); + g_signal_connect (combo, "changed", G_CALLBACK (format_combo_changed_cb), NULL); + + update_preview_widget (combo); + + gtk_widget_show_all (table); +} + +gchar * +e_datetime_format_format (const gchar *component, const gchar *part, DTFormatKind kind, time_t value) +{ + gchar *key, *res; + + g_return_val_if_fail (component != NULL, NULL); + g_return_val_if_fail (*component != 0, NULL); + + key = gen_key (component, part, kind); + g_return_val_if_fail (key != NULL, NULL); + + res = format_internal (key, kind, value, NULL); + + g_free (key); + + return res; +} + +gchar * +e_datetime_format_format_tm (const gchar *component, const gchar *part, DTFormatKind kind, struct tm *tm_time) +{ + gchar *key, *res; + + g_return_val_if_fail (component != NULL, NULL); + g_return_val_if_fail (*component != 0, NULL); + g_return_val_if_fail (tm_time != NULL, NULL); + + key = gen_key (component, part, kind); + g_return_val_if_fail (key != NULL, NULL); + + res = format_internal (key, kind, 0, tm_time); + + g_free (key); + + return res; +} diff --git a/e-util/e-datetime-format.h b/e-util/e-datetime-format.h new file mode 100644 index 0000000000..80f7d18826 --- /dev/null +++ b/e-util/e-datetime-format.h @@ -0,0 +1,46 @@ +/* + * + * Customizable date/time formatting in Evolution + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Copyright (C) 1999-2009 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __E_DATETIME_FORMAT__ +#define __E_DATETIME_FORMAT__ + +#include +#include +#include + +G_BEGIN_DECLS + +typedef enum _DTFormatKind { + DTFormatKindDate, + DTFormatKindTime, + DTFormatKindDateTime, + DTFormatKindShortDate +} DTFormatKind; + +void e_datetime_format_add_setup_widget (GtkWidget *table, gint row, const gchar *component, const gchar *part, DTFormatKind kind, const gchar *caption); + +gchar *e_datetime_format_format (const gchar *component, const gchar *part, DTFormatKind kind, time_t value); +gchar *e_datetime_format_format_tm (const gchar *component, const gchar *part, DTFormatKind kind, struct tm *tm_time); + +G_END_DECLS + +#endif diff --git a/mail/em-format-html-display.c b/mail/em-format-html-display.c index 312e5a1fb5..003eb37f8c 100644 --- a/mail/em-format-html-display.c +++ b/mail/em-format-html-display.c @@ -63,6 +63,7 @@ #include #include +#include "e-util/e-datetime-format.h" #include #include @@ -82,16 +83,6 @@ #include "widgets/misc/e-attachment-button.h" #include "widgets/misc/e-attachment-view.h" -#ifdef G_OS_WIN32 -/* Undefine the similar macro from ,it doesn't check if - * localtime() returns NULL. - */ -#undef localtime_r - -/* The localtime() in Microsoft's C library is MT-safe */ -#define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0) -#endif - #define d(x) struct _EMFormatHTMLDisplayPrivate { @@ -953,9 +944,7 @@ static void efhd_message_prefix(EMFormat *emf, CamelStream *stream, CamelMimePar { const gchar *flag, *comp, *due; time_t date; - gchar due_date[128]; - struct tm due_tm; - gchar *iconpath; + gchar *iconpath, *due_date_str; if (emf->folder == NULL || emf->uid == NULL || (flag = camel_folder_get_message_user_tag(emf->folder, emf->uid, "follow-up")) == NULL @@ -986,10 +975,10 @@ static void efhd_message_prefix(EMFormat *emf, CamelStream *stream, CamelMimePar camel_stream_printf(stream, "
"); if (comp && comp[0]) { - date = camel_header_decode_date(comp, NULL); - localtime_r(&date, &due_tm); - e_utf8_strftime_fix_am_pm(due_date, sizeof (due_date), _("Completed on %B %d, %Y, %l:%M %p"), &due_tm); - camel_stream_printf(stream, "%s, %s", flag, due_date); + date = camel_header_decode_date (comp, NULL); + due_date_str = e_datetime_format_format ("mail", "header", DTFormatKindDateTime, date); + camel_stream_printf (stream, "%s, %s %s", flag, _("Completed on"), due_date_str ? due_date_str : "???"); + g_free (due_date_str); } else if ((due = camel_folder_get_message_user_tag(emf->folder, emf->uid, "due-by")) != NULL && due[0]) { time_t now; @@ -998,9 +987,9 @@ static void efhd_message_prefix(EMFormat *emf, CamelStream *stream, CamelMimePar if (now > date) camel_stream_printf(stream, "%s ", _("Overdue:")); - localtime_r(&date, &due_tm); - e_utf8_strftime_fix_am_pm(due_date, sizeof (due_date), _("by %B %d, %Y, %l:%M %p"), &due_tm); - camel_stream_printf(stream, "%s %s", flag, due_date); + due_date_str = e_datetime_format_format ("mail", "header", DTFormatKindDateTime, date); + /* To Translators: the "by" is part of the string, like "Follow-up by Tuesday, January 13, 2009" */ + camel_stream_printf (stream, "%s %s %s", flag, _("by"), due_date_str ? due_date_str : "???"); } else { camel_stream_printf(stream, "%s", flag); } diff --git a/mail/em-format-html.c b/mail/em-format-html.c index ec3427c88d..038ca4b61b 100644 --- a/mail/em-format-html.c +++ b/mail/em-format-html.c @@ -43,6 +43,7 @@ #include /* for e_utf8_strftime, what about e_time_format_time? */ #include +#include "e-util/e-datetime-format.h" #include "e-util/e-icon-factory.h" #include "e-util/e-util-private.h" #include "e-util/e-util.h" @@ -1750,14 +1751,16 @@ efh_format_header(EMFormat *emf, CamelStream *stream, CamelMedium *part, struct gint msg_offset, local_tz; time_t msg_date; struct tm local; + gchar *date_str; txt = header->value; while (*txt == ' ' || *txt == '\t') txt++; - /* Show the local timezone equivalent in brackets if the sender is remote */ msg_date = camel_header_decode_date(txt, &msg_offset); - e_localtime_with_offset(msg_date, &local, &local_tz); + e_localtime_with_offset (msg_date, &local, &local_tz); + + date_str = e_datetime_format_format ("mail", "header", DTFormatKindDateTime, msg_date); /* Convert message offset to minutes (e.g. -0400 --> -240) */ msg_offset = ((msg_offset / 100) * 60) + (msg_offset % 100); @@ -1765,25 +1768,18 @@ efh_format_header(EMFormat *emf, CamelStream *stream, CamelMedium *part, struct msg_offset -= local_tz / 60; if (msg_offset) { - gchar buf[256], *html; - - msg_offset += (local.tm_hour * 60) + local.tm_min; - if (msg_offset >= (24 * 60) || msg_offset < 0) { - /* translators: strftime format for local time equivalent in Date header display, with day */ - gchar *msg = g_strdup_printf("%s", _(" (%a, %R %Z)")); - e_utf8_strftime(buf, sizeof(buf), msg, &local); - g_free(msg); - } else { - /* translators: strftime format for local time equivalent in Date header display, without day */ - gchar *msg = g_strdup_printf("%s", _(" (%R %Z)")); - e_utf8_strftime(buf, sizeof(buf), msg, &local); - g_free(msg); - } + gchar *html; + + html = camel_text_to_html (txt, efh->text_html_flags, 0); + txt = value = g_strdup_printf ("%s (%s)", date_str, html); + + g_free (html); + g_free (date_str); - html = camel_text_to_html(txt, efh->text_html_flags, 0); - txt = value = g_strdup_printf("%s %s", html, buf); - g_free(html); flags |= EM_FORMAT_HTML_HEADER_HTML; + } else { + /* date_str will be freed at the end */ + txt = value = date_str; } flags |= EM_FORMAT_HEADER_BOLD; diff --git a/mail/em-mailer-prefs.c b/mail/em-mailer-prefs.c index 057b2d9210..7da40551a5 100644 --- a/mail/em-mailer-prefs.c +++ b/mail/em-mailer-prefs.c @@ -41,6 +41,7 @@ #include "libedataserverui/e-cell-renderer-color.h" +#include "e-util/e-datetime-format.h" #include "e-util/e-util-private.h" #include "e-util/e-util-labels.h" @@ -1147,7 +1148,7 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs) { GSList *header_config_list, *header_add_list, *p; GHashTable *default_header_hash; - GtkWidget *toplevel; + GtkWidget *toplevel, *table; GtkTreeSelection *selection; GtkCellRenderer *renderer; GtkTreeIter iter; @@ -1449,6 +1450,11 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs) g_slist_free (header_add_list); + /* date/time format */ + table = glade_xml_get_widget (gui, "datetime_format_table"); + e_datetime_format_add_setup_widget (table, 0, "mail", "header", DTFormatKindDateTime, _("Date header:")); + e_datetime_format_add_setup_widget (table, 1, "mail", "table", DTFormatKindDateTime, _("Table column:")); + /* Junk prefs */ prefs->check_incoming = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui, "chkCheckIncomingMail")); toggle_button_init (prefs, prefs->check_incoming, FALSE, diff --git a/mail/mail-config.glade b/mail/mail-config.glade index 586dc854b1..a97fbb8ab2 100644 --- a/mail/mail-config.glade +++ b/mail/mail-config.glade @@ -6583,6 +6583,99 @@ b True + + + + True + False + 0 + + + + True + <span weight="bold">Date/Time Format</span> + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + txtHeaders + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + False + 0 + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 6 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 1 + 3 + False + 0 + 0 + + + 0 + True + True + + + + + 0 + True + True + + + + + 0 + False + True + + False diff --git a/mail/message-list.c b/mail/message-list.c index 07143ca0e5..92c740bb9d 100644 --- a/mail/message-list.c +++ b/mail/message-list.c @@ -1728,7 +1728,8 @@ static ECell * create_composite_cell (gint col) images [i] = states_pixmaps [i + 6].pixbuf; cell_attach = e_cell_toggle_new (0, 2, images); - cell_date = e_cell_date_new(NULL, GTK_JUSTIFY_RIGHT); + cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT); + e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail"); g_object_set (G_OBJECT (cell_date), "bold_column", COL_UNREAD, "color_column", COL_COLOUR, @@ -1811,6 +1812,7 @@ message_list_create_extras (void) /* date cell */ cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT); + e_cell_date_set_format_component (E_CELL_DATE (cell), "mail"); g_object_set (G_OBJECT (cell), "bold_column", COL_UNREAD, "color_column", COL_COLOUR, @@ -1844,6 +1846,10 @@ message_list_create_extras (void) cell = create_composite_cell (COL_TO); e_table_extras_add_cell (extras, "render_composite_to", cell); + /* set proper format component for a default 'date' cell renderer */ + cell = e_table_extras_get_cell (extras, "date"); + e_cell_date_set_format_component (E_CELL_DATE (cell), "mail"); + return extras; } diff --git a/po/POTFILES.in b/po/POTFILES.in index 68d2da00b5..f78e5cea14 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -179,6 +179,7 @@ composer/mail-composer.error.xml data/evolution.desktop.in.in data/evolution.keys.in.in e-util/e-categories-config.c +e-util/e-datetime-format.c e-util/e-dialog-utils.c e-util/e-error.c e-util/e-logger.c diff --git a/widgets/table/e-cell-date.c b/widgets/table/e-cell-date.c index 6e95a1f5ea..a3989dcc46 100644 --- a/widgets/table/e-cell-date.c +++ b/widgets/table/e-cell-date.c @@ -29,85 +29,29 @@ #include #include "e-util/e-util.h" +#include "e-util/e-datetime-format.h" #include "misc/e-unicode.h" #include "e-cell-date.h" G_DEFINE_TYPE (ECellDate, e_cell_date, E_CELL_TEXT_TYPE) -#ifdef G_OS_WIN32 -/* The localtime() in Microsoft's C library *is* thread-safe */ -#define localtime_r(timep, result) (localtime (timep) ? memcpy ((result), localtime (timep), sizeof (*(result))) : 0) -#endif - static gchar * ecd_get_text(ECellText *cell, ETableModel *model, gint col, gint row) { time_t date = GPOINTER_TO_INT (e_table_model_value_at(model, col, row)); - time_t nowdate = time(NULL); - time_t yesdate; - struct tm then, now, yesterday; - gchar buf[100]; - gchar *temp; - gboolean done = FALSE; + const gchar *fmt_component, *fmt_part = NULL; if (date == 0) { return g_strdup (_("?")); } - tzset (); - localtime_r (&date, &then); - localtime_r (&nowdate, &now); - - if (nowdate - date < 60 * 60 * 8 && nowdate > date) { - e_utf8_strftime_fix_am_pm (buf, 100, _("%l:%M %p"), &then); - done = TRUE; - } - - if (!done) { - if (then.tm_mday == now.tm_mday && - then.tm_mon == now.tm_mon && - then.tm_year == now.tm_year) { - e_utf8_strftime_fix_am_pm (buf, 100, _("Today %l:%M %p"), &then); - done = TRUE; - } - } - if (!done) { - yesdate = nowdate - 60 * 60 * 24; - localtime_r (&yesdate, &yesterday); - if (then.tm_mday == yesterday.tm_mday && - then.tm_mon == yesterday.tm_mon && - then.tm_year == yesterday.tm_year) { - e_utf8_strftime_fix_am_pm (buf, 100, _("Yesterday %l:%M %p"), &then); - done = TRUE; - } - } - if (!done) { - gint i; - for (i = 2; i < 7; i++) { - yesdate = nowdate - 60 * 60 * 24 * i; - localtime_r (&yesdate, &yesterday); - if (then.tm_mday == yesterday.tm_mday && - then.tm_mon == yesterday.tm_mon && - then.tm_year == yesterday.tm_year) { - e_utf8_strftime_fix_am_pm (buf, 100, _("%a %l:%M %p"), &then); - done = TRUE; - break; - } - } - } - if (!done) { - if (then.tm_year == now.tm_year) { - e_utf8_strftime_fix_am_pm (buf, 100, _("%b %d %l:%M %p"), &then); - } else { - e_utf8_strftime_fix_am_pm (buf, 100, _("%b %d %Y"), &then); - } - } - temp = buf; - while ((temp = strstr (temp, " "))) { - memmove (temp, temp + 1, strlen (temp)); - } - return g_strstrip (g_strdup (buf)); + fmt_component = g_object_get_data ((GObject *) cell, "fmt-component"); + if (!fmt_component || !*fmt_component) + fmt_component = "Default"; + else + fmt_part = "table"; + return e_datetime_format_format (fmt_component, fmt_part, DTFormatKindDateTime, date); } static void @@ -165,3 +109,10 @@ e_cell_date_new (const gchar *fontname, GtkJustification justify) return (ECell *) ecd; } +void +e_cell_date_set_format_component (ECellDate *ecd, const gchar *fmt_component) +{ + g_return_if_fail (ecd != NULL); + + g_object_set_data_full ((GObject *)ecd, "fmt-component", g_strdup (fmt_component), g_free); +} diff --git a/widgets/table/e-cell-date.h b/widgets/table/e-cell-date.h index 968e387198..ec23bd61ad 100644 --- a/widgets/table/e-cell-date.h +++ b/widgets/table/e-cell-date.h @@ -44,6 +44,7 @@ typedef struct { GType e_cell_date_get_type (void); ECell *e_cell_date_new (const gchar *fontname, GtkJustification justify); +void e_cell_date_set_format_component (ECellDate *ecd, const gchar *fmt_component); G_END_DECLS -- cgit