diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in index 37106cb3dc..def0bd922c 100644 --- a/data/org.gnome.evolution.mail.gschema.xml.in +++ b/data/org.gnome.evolution.mail.gschema.xml.in @@ -658,6 +658,32 @@ <_description>An Archive folder to use for Messages|Archive... feature when in an On This Computer folder. + + true + <_summary>Whether the To Do bar is visible in the main window + <_description>Stores whether the To Do bar is visible in the main window. + + + 9999 + <_summary>Width of the To Do bar in the main window + <_description>Holds the width of the To Do bar for the main window. + + + true + <_summary>Whether the To Do bar is visible in a sub-window + <_description>Stores whether the To Do bar is visible in a sub-window. + + + 9999 + <_summary>Width of the To Do bar in a sub-window + <_description>Holds the width of the To Do bar for a sub-window. + + + false + <_summary>Whether the To Do bar should show also completed tasks + <_description>Stores whether the To Do bar should show also completed tasks. + + diff --git a/data/ui/evolution-mail.ui b/data/ui/evolution-mail.ui index 322c83bdf1..eb455f3a99 100644 --- a/data/ui/evolution-mail.ui +++ b/data/ui/evolution-mail.ui @@ -16,6 +16,11 @@ + + + + + diff --git a/data/ui/evolution-shell.ui b/data/ui/evolution-shell.ui index 9035fb2b4b..6b341078a3 100644 --- a/data/ui/evolution-shell.ui +++ b/data/ui/evolution-shell.ui @@ -47,6 +47,7 @@ + diff --git a/po/POTFILES.in b/po/POTFILES.in index 4019a48ead..acc3b3b35a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -107,6 +107,7 @@ src/calendar/gui/e-memo-table.etspec src/calendar/gui/e-task-table.c src/calendar/gui/e-task-table.etspec src/calendar/gui/e-timezone-entry.c +src/calendar/gui/e-to-do-pane.c src/calendar/gui/e-week-view.c src/calendar/gui/e-week-view-main-item.c src/calendar/gui/itip-utils.c diff --git a/src/calendar/gui/CMakeLists.txt b/src/calendar/gui/CMakeLists.txt index 001e36bd30..09cf401d6c 100644 --- a/src/calendar/gui/CMakeLists.txt +++ b/src/calendar/gui/CMakeLists.txt @@ -72,6 +72,7 @@ set(SOURCES e-select-names-renderer.c e-send-options-utils.c e-task-table.c + e-to-do-pane.c e-week-view-event-item.c e-week-view-layout.c e-week-view-main-item.c @@ -147,6 +148,7 @@ set(HEADERS e-select-names-renderer.h e-send-options-utils.h e-task-table.h + e-to-do-pane.h e-week-view-event-item.h e-week-view-layout.h e-week-view-main-item.h diff --git a/src/calendar/gui/e-cal-ops.c b/src/calendar/gui/e-cal-ops.c index 53edf5cbbe..facb8741f5 100644 --- a/src/calendar/gui/e-cal-ops.c +++ b/src/calendar/gui/e-cal-ops.c @@ -1822,7 +1822,7 @@ e_cal_ops_new_component_editor_from_model (ECalModel *model, /** * e_cal_ops_open_component_in_editor_sync: - * @model: an #ECalModel instance + * @model: (nullable): an #ECalModel instance * @client: an #ECalClient, to which the component belongs * @icalcomp: an #icalcomponent to open in an editor * @force_attendees: set to TRUE to force to show attendees, FALSE to auto-detect @@ -1842,7 +1842,8 @@ e_cal_ops_open_component_in_editor_sync (ECalModel *model, ECalComponent *comp; ECompEditor *comp_editor; - g_return_if_fail (E_IS_CAL_MODEL (model)); + if (model) + g_return_if_fail (E_IS_CAL_MODEL (model)); g_return_if_fail (E_IS_CAL_CLIENT (client)); g_return_if_fail (icalcomp != NULL); @@ -1857,8 +1858,8 @@ e_cal_ops_open_component_in_editor_sync (ECalModel *model, ncd = g_new0 (NewComponentData, 1); ncd->is_new_component = FALSE; - ncd->shell = g_object_ref (e_cal_model_get_shell (model)); - ncd->model = g_object_ref (model); + ncd->shell = g_object_ref (model ? e_cal_model_get_shell (model) : e_shell_get_default ()); + ncd->model = model ? g_object_ref (model) : NULL; ncd->source_type = e_cal_client_get_source_type (client); ncd->is_assigned = force_attendees; ncd->extension_name = NULL; diff --git a/src/calendar/gui/e-to-do-pane.c b/src/calendar/gui/e-to-do-pane.c new file mode 100644 index 0000000000..8943aedb3b --- /dev/null +++ b/src/calendar/gui/e-to-do-pane.c @@ -0,0 +1,2722 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com) + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "evolution-config.h" + +#include +#include +#include + +#include +#include + +#include "e-cal-data-model.h" +#include "e-cal-data-model-subscriber.h" +#include "e-cal-dialogs.h" +#include "e-cal-ops.h" +#include "misc.h" + +#include "e-to-do-pane.h" + +#define N_ROOTS 7 +#define MAX_TOOLTIP_DESCRIPTION_LEN 128 + +struct _EToDoPanePrivate { + GWeakRef shell_view_weakref; /* EShellView * */ + gboolean highlight_overdue; + GdkRGBA *overdue_color; + gboolean show_completed_tasks; + gboolean use_24hour_format; + + EClientCache *client_cache; + ESourceRegistryWatcher *watcher; + GtkTreeStore *tree_store; + GtkTreeView *tree_view; + ECalDataModel *events_data_model; + ECalDataModel *tasks_data_model; + GHashTable *component_refs; /* ComponentIdent * ~> GSList * { GtkTreeRowRefenrece * } */ + GHashTable *client_colors; /* ESource * ~> GdkRGBA * */ + + GCancellable *cancellable; + + guint time_checker_id; + guint last_today; + time_t nearest_due; + + gulong source_changed_id; + + GtkTreeRowReference *roots[N_ROOTS]; +}; + +enum { + PROP_0, + PROP_HIGHLIGHT_OVERDUE, + PROP_OVERDUE_COLOR, + PROP_SHELL_VIEW, + PROP_SHOW_COMPLETED_TASKS, + PROP_USE_24HOUR_FORMAT +}; + +static void e_to_do_pane_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (EToDoPane, e_to_do_pane, GTK_TYPE_GRID, + G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, e_to_do_pane_cal_data_model_subscriber_init)) + +enum { + COLUMN_BGCOLOR = 0, + COLUMN_FGCOLOR, + COLUMN_HAS_ICON_NAME, + COLUMN_ICON_NAME, + COLUMN_SUMMARY, + COLUMN_TOOLTIP, + COLUMN_SORTKEY, + COLUMN_DATE_MARK, + COLUMN_CAL_CLIENT, + COLUMN_CAL_COMPONENT, + N_COLUMNS +}; + +typedef struct _ComponentIdent { + gconstpointer client; + gchar *uid; + gchar *rid; +} ComponentIdent; + +static ComponentIdent * +component_ident_new (gconstpointer client, + const gchar *uid, + const gchar *rid) +{ + ComponentIdent *ci; + + ci = g_new0 (ComponentIdent, 1); + ci->client = client; + ci->uid = g_strdup (uid); + ci->rid = (rid && *rid) ? g_strdup (rid) : NULL; + + return ci; +} + +static ComponentIdent * +component_ident_copy (const ComponentIdent *src) +{ + if (!src) + return NULL; + + return component_ident_new (src->client, src->uid, src->rid); +} + +static void +component_ident_free (gpointer ptr) +{ + ComponentIdent *ci = ptr; + + if (ci) { + g_free (ci->uid); + g_free (ci->rid); + g_free (ci); + } +} + +static guint +component_ident_hash (gconstpointer ptr) +{ + const ComponentIdent *ci = ptr; + + if (!ci) + return 0; + + return g_direct_hash (ci->client) ^ + (ci->uid ? g_str_hash (ci->uid) : 0) ^ + (ci->rid ? g_str_hash (ci->rid) : 0); +} + +static gboolean +component_ident_equal (gconstpointer ptr1, + gconstpointer ptr2) +{ + const ComponentIdent *ci1 = ptr1, *ci2 = ptr2; + + if (!ci1 || !ci2) + return ci1 == ci2; + + return ci1->client == ci2->client && + g_strcmp0 (ci1->uid, ci2->uid) == 0 && + g_strcmp0 (ci1->rid, ci2->rid) == 0; +} + +static void +etdp_free_component_refs (gpointer ptr) +{ + GSList *roots = ptr; + + g_slist_free_full (roots, (GDestroyNotify) gtk_tree_row_reference_free); +} + +static guint +etdp_create_date_mark (const struct icaltimetype *itt) +{ + if (!itt) + return 0; + + return itt->year * 10000 + itt->month * 100 + itt->day; +} + +static void +etdp_itt_to_zone (struct icaltimetype *itt, + const gchar *itt_tzid, + ECalClient *client, + icaltimezone *default_zone) +{ + icaltimezone *zone = NULL; + + g_return_if_fail (itt != NULL); + + if (itt_tzid) { + e_cal_client_get_timezone_sync (client, itt_tzid, &zone, NULL, NULL); + } else if (itt->is_utc) { + zone = icaltimezone_get_utc_timezone (); + } + + if (zone) + icaltimezone_convert_time (itt, zone, default_zone); +} + +static gchar * +etdp_date_time_to_string (const ECalComponentDateTime *dt, + ECalClient *client, + icaltimezone *default_zone, + guint today_date_mark, + gboolean is_task, + gboolean use_24hour_format, + struct icaltimetype *out_itt) +{ + gboolean is_overdue; + gchar *res; + + g_return_val_if_fail (dt != NULL, NULL); + g_return_val_if_fail (dt->value != NULL, NULL); + g_return_val_if_fail (out_itt != NULL, NULL); + + *out_itt = *dt->value; + + etdp_itt_to_zone (out_itt, dt->tzid, client, default_zone); + + is_overdue = is_task && etdp_create_date_mark (out_itt) < today_date_mark; + + if (out_itt->is_date && !is_overdue) + return NULL; + + if (is_overdue) { + struct tm tm; + + tm = icaltimetype_to_tm (out_itt); + + res = e_datetime_format_format_tm ("calendar", "table", out_itt->is_date ? DTFormatKindDate : DTFormatKindDateTime, &tm); + } else { + if (use_24hour_format) { + res = g_strdup_printf ("%d:%02d", out_itt->hour, out_itt->minute); + } else { + gint hour = out_itt->hour; + const gchar *suffix; + + if (hour < 12) { + /* String to use in 12-hour time format for times in the morning. */ + suffix = _("am"); + } else { + hour -= 12; + /* String to use in 12-hour time format for times in the afternoon. */ + suffix = _("pm"); + } + + if (hour == 0) + hour = 12; + + if (!out_itt->minute) + res = g_strdup_printf ("%d %s", hour, suffix); + else + res = g_strdup_printf ("%d:%02d %s", hour, out_itt->minute, suffix); + } + } + + return res; +} + +static void +etdp_append_to_string_escaped (GString *str, + const gchar *format, + const gchar *value1, + const gchar *value2) +{ + gchar *escaped; + + g_return_if_fail (str != NULL); + g_return_if_fail (format != NULL); + + if (!value1 || !*value1) + return; + + escaped = g_markup_printf_escaped (format, value1, value2); + g_string_append (str, escaped); + g_free (escaped); +} + +static gchar * +etdp_format_date_time (ECalClient *client, + icaltimezone *default_zone, + const struct icaltimetype *in_itt, + const gchar *tzid) +{ + struct icaltimetype itt; + struct tm tm; + + if (!in_itt) + return NULL; + + itt = *in_itt; + + etdp_itt_to_zone (&itt, tzid, client, default_zone); + + tm = icaltimetype_to_tm (&itt); + + return e_datetime_format_format_tm ("calendar", "table", itt.is_date ? DTFormatKindDate : DTFormatKindDateTime, &tm); +} + +static gboolean +etdp_get_component_data (EToDoPane *to_do_pane, + ECalClient *client, + ECalComponent *comp, + icaltimezone *default_zone, + guint today_date_mark, + gchar **out_summary, + gchar **out_tooltip, + gboolean *out_is_task, + gboolean *out_is_completed, + gchar **out_sort_key, + guint *out_date_mark) +{ + icalcomponent *icalcomp; + ECalComponentDateTime dt = { 0 }; + ECalComponentId *id; + struct icaltimetype itt = icaltime_null_time (); + const gchar *prefix, *location, *description; + GString *tooltip; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE); + g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE); + g_return_val_if_fail (out_summary, FALSE); + g_return_val_if_fail (out_tooltip, FALSE); + g_return_val_if_fail (out_is_task, FALSE); + g_return_val_if_fail (out_is_completed, FALSE); + g_return_val_if_fail (out_sort_key, FALSE); + g_return_val_if_fail (out_date_mark, FALSE); + + icalcomp = e_cal_component_get_icalcomponent (comp); + g_return_val_if_fail (icalcomp != NULL, FALSE); + + location = icalcomponent_get_location (icalcomp); + if (location && !*location) + location = NULL; + + tooltip = g_string_sized_new (512); + + etdp_append_to_string_escaped (tooltip, "%s", icalcomponent_get_summary (icalcomp), NULL); + + if (location) { + g_string_append (tooltip, "\n"); + /* Translators: It will display "Location: LocationOfTheAppointment" */ + etdp_append_to_string_escaped (tooltip, _("Location: %s"), location, NULL); + } + + *out_is_task = e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_TODO; + *out_is_completed = FALSE; + + if (*out_is_task) { + struct icaltimetype *completed = NULL; + + /* Tasks after events */ + prefix = "1"; + + e_cal_component_get_due (comp, &dt); + e_cal_component_get_completed (comp, &completed); + + if (dt.value) { + gchar *tmp; + + tmp = etdp_format_date_time (client, default_zone, dt.value, dt.tzid); + + g_string_append (tooltip, "\n"); + /* Translators: It will display "Due: DueDateAndTime" */ + etdp_append_to_string_escaped (tooltip, _("Due: %s"), tmp, NULL); + + g_free (tmp); + } + + if (completed) { + gchar *tmp; + + tmp = etdp_format_date_time (client, default_zone, completed, NULL); + + g_string_append (tooltip, "\n"); + /* Translators: It will display "Completed: DateAndTimeWhenCompleted" */ + etdp_append_to_string_escaped (tooltip, _("Completed: %s"), tmp, NULL); + + g_free (tmp); + + *out_is_completed = TRUE; + e_cal_component_free_icaltimetype (completed); + } + } else { + /* Events first */ + prefix = "0"; + + e_cal_component_get_dtstart (comp, &dt); + + if (dt.value) { + ECalComponentDateTime dtend = { 0 }; + struct icaltimetype ittstart, ittend; + gchar *strstart, *strduration; + + e_cal_component_get_dtend (comp, &dtend); + + ittstart = *dt.value; + if (dtend.value) + ittend = *dtend.value; + else + ittend = ittstart; + + etdp_itt_to_zone (&ittstart, dt.tzid, client, default_zone); + etdp_itt_to_zone (&ittend, dtend.value ? dtend.tzid : dt.tzid, client, default_zone); + + strstart = etdp_format_date_time (client, default_zone, &ittstart, NULL); + strduration = calculate_time (icaltime_as_timet (ittstart), icaltime_as_timet (ittend)); + + g_string_append (tooltip, "\n"); + /* Translators: It will display "Time: StartDateAndTime (Duration)" */ + etdp_append_to_string_escaped (tooltip, _("Time: %s %s"), strstart, strduration); + + g_free (strduration); + g_free (strstart); + + e_cal_component_free_datetime (&dtend); + } + } + + *out_summary = NULL; + + if (dt.value) { + gchar *time_str; + + time_str = etdp_date_time_to_string (&dt, client, default_zone, today_date_mark, *out_is_task, + to_do_pane->priv->use_24hour_format, &itt); + + if (time_str) { + *out_summary = g_markup_printf_escaped ("%s %s%s%s%s", + time_str, icalcomponent_get_summary (icalcomp), location ? " (" : "", + location ? location : "", location ? ")" : ""); + } + + g_free (time_str); + } + + if (!*out_summary) { + *out_summary = g_markup_printf_escaped ("%s%s%s%s", icalcomponent_get_summary (icalcomp), + location ? " (" : "", location ? location : "", location ? ")" : ""); + } + + if (*out_is_completed) { + gchar *tmp = *out_summary; + + /* With leading space, to have proper row height in GtkTreeView */ + *out_summary = g_strdup_printf (" %s", *out_summary); + + g_free (tmp); + } else { + gchar *tmp = *out_summary; + + /* With leading space, to have proper row height in GtkTreeView */ + *out_summary = g_strconcat (" ", *out_summary, NULL); + + g_free (tmp); + } + + e_cal_component_free_datetime (&dt); + + id = e_cal_component_get_id (comp); + + *out_sort_key = g_strdup_printf ("%s-%04d%02d%02d%02d%02d%02d-%s-%s", + prefix, itt.year, itt.month, itt.day, itt.hour, itt.minute, itt.second, + (id && id->uid) ? id->uid : "", (id && id->rid) ? id->rid : ""); + + if (id) + e_cal_component_free_id (id); + + description = icalcomponent_get_description (icalcomp); + if (description && *description && g_utf8_validate (description, -1, NULL)) { + gchar *tmp = NULL; + glong len; + + len = g_utf8_strlen (description, -1); + if (len > MAX_TOOLTIP_DESCRIPTION_LEN) { + GString *str; + const gchar *end; + + end = g_utf8_offset_to_pointer (description, MAX_TOOLTIP_DESCRIPTION_LEN); + str = g_string_new_len (description, end - description); + g_string_append (str, "…"); + + tmp = g_string_free (str, FALSE); + } + + g_string_append (tooltip, "\n\n"); + etdp_append_to_string_escaped (tooltip, "%s", tmp ? tmp : description, NULL); + + g_free (tmp); + } + + *out_date_mark = etdp_create_date_mark (&itt); + *out_tooltip = g_string_free (tooltip, FALSE); + + return TRUE; +} + +static GdkRGBA +etdp_get_fgcolor_for_bgcolor (const GdkRGBA *bgcolor) +{ + GdkRGBA fgcolor = { 1.0, 1.0, 1.0, 1.0 }; + + if (bgcolor) { + if ((bgcolor->red > 0.7) || (bgcolor->green > 0.7) || (bgcolor->blue > 0.7)) { + fgcolor.red = 0.0; + fgcolor.green = 0.0; + fgcolor.blue = 0.0; + } else { + fgcolor.red = 1.0; + fgcolor.green = 1.0; + fgcolor.blue = 1.0; + } + } + + return fgcolor; +} + +static GSList * /* GtkTreePath * */ +etdp_get_component_root_paths (EToDoPane *to_do_pane, + ECalClient *client, + ECalComponent *comp, + icaltimezone *default_zone) +{ + ECalComponentDateTime dt; + struct icaltimetype itt; + GtkTreePath *first_root_path = NULL; + GtkTreeModel *model; + GSList *roots = NULL; + guint start_date_mark, end_date_mark, prev_date_mark = 0; + gint ii; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), NULL); + g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL); + g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); + + if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_TODO) { + e_cal_component_get_due (comp, &dt); + + if (dt.value) { + itt = *dt.value; + + etdp_itt_to_zone (&itt, dt.tzid, client, default_zone); + start_date_mark = etdp_create_date_mark (&itt); + } else { + start_date_mark = 0; + } + + end_date_mark = start_date_mark; + + e_cal_component_free_datetime (&dt); + } else { + e_cal_component_get_dtstart (comp, &dt); + + if (dt.value) { + itt = *dt.value; + + etdp_itt_to_zone (&itt, dt.tzid, client, default_zone); + start_date_mark = etdp_create_date_mark (&itt); + } else { + start_date_mark = 0; + } + + e_cal_component_free_datetime (&dt); + + e_cal_component_get_dtend (comp, &dt); + + if (dt.value) { + itt = *dt.value; + + etdp_itt_to_zone (&itt, dt.tzid, client, default_zone); + end_date_mark = etdp_create_date_mark (&itt); + } else { + end_date_mark = start_date_mark; + } + + e_cal_component_free_datetime (&dt); + } + + model = GTK_TREE_MODEL (to_do_pane->priv->tree_store); + + for (ii = 0; ii < N_ROOTS; ii++) { + if (gtk_tree_row_reference_valid (to_do_pane->priv->roots[ii])) { + GtkTreePath *root_path; + GtkTreeIter root_iter; + guint root_date_mark = 0; + + root_path = gtk_tree_row_reference_get_path (to_do_pane->priv->roots[ii]); + if (root_path && gtk_tree_model_get_iter (model, &root_iter, root_path)) { + gtk_tree_model_get (model, &root_iter, COLUMN_DATE_MARK, &root_date_mark, -1); + + if (start_date_mark < root_date_mark && (end_date_mark > prev_date_mark || + (start_date_mark == end_date_mark && end_date_mark >= prev_date_mark))) { + roots = g_slist_prepend (roots, gtk_tree_path_copy (root_path)); + } else if (!first_root_path) { + first_root_path = gtk_tree_path_copy (root_path); + } + + prev_date_mark = root_date_mark; + } + + gtk_tree_path_free (root_path); + } + } + + if (!roots && first_root_path) + roots = g_slist_prepend (roots, first_root_path); + else + gtk_tree_path_free (first_root_path); + + return g_slist_reverse (roots); +} + +static GSList * /* GtkTreeRowReference * */ +etdp_merge_with_root_paths (EToDoPane *to_do_pane, + GtkTreeModel *model, + const GSList *new_root_paths, /* GtkTreePath * */ + const GSList *current_refs) /* GtkTreeRowReference * */ +{ + GSList *new_references = NULL; + const GSList *paths_link, *refs_link; + GtkTreeIter iter, parent; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), NULL); + g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL); + g_return_val_if_fail (new_root_paths != NULL, NULL); + + refs_link = current_refs; + for (paths_link = new_root_paths; paths_link; paths_link = g_slist_next (paths_link)) { + GtkTreePath *root_path = paths_link->data; + gboolean found = FALSE; + + while (refs_link && !found) { + GtkTreeRowReference *reference = refs_link->data; + GtkTreePath *ref_path; + + ref_path = gtk_tree_row_reference_get_path (reference); + if (ref_path && + gtk_tree_model_get_iter (model, &iter, ref_path) && + gtk_tree_model_iter_parent (model, &parent, &iter)) { + GtkTreePath *parent_path; + gint cmp; + + parent_path = gtk_tree_model_get_path (model, &parent); + cmp = gtk_tree_path_compare (parent_path, root_path); + gtk_tree_path_free (parent_path); + + if (cmp == 0) { + found = TRUE; + new_references = g_slist_prepend (new_references, gtk_tree_row_reference_copy (reference)); + } else if (cmp > 0) { + gtk_tree_path_free (ref_path); + break; + } else { + gtk_tree_store_remove (to_do_pane->priv->tree_store, &iter); + } + } + gtk_tree_path_free (ref_path); + + refs_link = g_slist_next (refs_link); + } + + if (!found) { + GtkTreeIter parent; + GtkTreePath *path; + + g_warn_if_fail (gtk_tree_model_get_iter (model, &parent, root_path)); + + gtk_tree_store_append (to_do_pane->priv->tree_store, &iter, &parent); + path = gtk_tree_model_get_path (model, &iter); + + if (gtk_tree_model_iter_n_children (model, &parent) == 1) { + gtk_tree_view_expand_row (to_do_pane->priv->tree_view, root_path, TRUE); + } + + new_references = g_slist_prepend (new_references, gtk_tree_row_reference_new (model, path)); + + gtk_tree_path_free (path); + } + } + + while (refs_link) { + GtkTreeRowReference *reference = refs_link->data; + GtkTreePath *ref_path; + + ref_path = gtk_tree_row_reference_get_path (reference); + if (ref_path && + gtk_tree_model_get_iter (model, &iter, ref_path)) { + gtk_tree_store_remove (to_do_pane->priv->tree_store, &iter); + } + + gtk_tree_path_free (ref_path); + + refs_link = g_slist_next (refs_link); + } + + g_warn_if_fail (g_slist_length (new_references) == g_slist_length ((GSList *) new_root_paths)); + + return g_slist_reverse (new_references); +} + +static void +etdp_get_comp_colors (EToDoPane *to_do_pane, + ECalClient *client, + ECalComponent *comp, + GdkRGBA *out_bgcolor, + gboolean *out_bgcolor_set, + GdkRGBA *out_fgcolor, + gboolean *out_fgcolor_set, + time_t *out_nearest_due) +{ + GdkRGBA *bgcolor, fgcolor; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + g_return_if_fail (out_bgcolor); + g_return_if_fail (out_bgcolor_set); + g_return_if_fail (out_fgcolor); + g_return_if_fail (out_fgcolor_set); + + *out_bgcolor_set = FALSE; + *out_fgcolor_set = FALSE; + + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (E_IS_CAL_COMPONENT (comp)); + + bgcolor = g_hash_table_lookup (to_do_pane->priv->client_colors, e_client_get_source (E_CLIENT (client))); + + if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_TODO && + to_do_pane->priv->highlight_overdue && + to_do_pane->priv->overdue_color) { + ECalComponentDateTime dt = { 0 }; + + e_cal_component_get_due (comp, &dt); + + if (dt.value) { + icaltimezone *default_zone; + struct icaltimetype itt, now; + + default_zone = e_cal_data_model_get_timezone (to_do_pane->priv->events_data_model); + + itt = *dt.value; + etdp_itt_to_zone (&itt, dt.tzid, client, default_zone); + + now = icaltime_current_time_with_zone (default_zone); + + if (icaltime_compare (itt, now) <= 0) { + bgcolor = to_do_pane->priv->overdue_color; + } else if (out_nearest_due) { + time_t due_tt; + + due_tt = icaltime_as_timet_with_zone (itt, default_zone); + if (*out_nearest_due == (time_t) -1 || + *out_nearest_due > due_tt) + *out_nearest_due = due_tt; + } + } + + e_cal_component_free_datetime (&dt); + } + + fgcolor = etdp_get_fgcolor_for_bgcolor (bgcolor); + + *out_bgcolor_set = bgcolor != NULL; + if (bgcolor) + *out_bgcolor = *bgcolor; + + *out_fgcolor_set = *out_bgcolor_set; + *out_fgcolor = fgcolor; +} + +static void +etdp_add_component (EToDoPane *to_do_pane, + ECalClient *client, + ECalComponent *comp) +{ + ECalComponentId *id; + ComponentIdent *ident; + icaltimezone *default_zone; + GSList *new_root_paths, *new_references, *link; + GtkTreeModel *model; + GtkTreeIter iter = { 0 }; + GdkRGBA bgcolor, fgcolor; + gboolean bgcolor_set = FALSE, fgcolor_set = FALSE; + gchar *summary = NULL, *tooltip = NULL, *sort_key = NULL; + gboolean is_task = FALSE, is_completed = FALSE; + const gchar *icon_name; + guint date_mark = 0; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (E_IS_CAL_COMPONENT (comp)); + + id = e_cal_component_get_id (comp); + g_return_if_fail (id != NULL); + + default_zone = e_cal_data_model_get_timezone (to_do_pane->priv->events_data_model); + + if (!etdp_get_component_data (to_do_pane, client, comp, default_zone, to_do_pane->priv->last_today, + &summary, &tooltip, &is_task, &is_completed, &sort_key, &date_mark)) { + e_cal_component_free_id (id); + return; + } + + model = GTK_TREE_MODEL (to_do_pane->priv->tree_store); + ident = component_ident_new (client, id->uid, id->rid); + + new_root_paths = etdp_get_component_root_paths (to_do_pane, client, comp, default_zone); + + new_references = etdp_merge_with_root_paths (to_do_pane, model, new_root_paths, + g_hash_table_lookup (to_do_pane->priv->component_refs, ident)); + + g_slist_free_full (new_root_paths, (GDestroyNotify) gtk_tree_path_free); + + if (e_cal_component_has_attendees (comp)) { + if (is_task) + icon_name = "stock_task-assigned"; + else + icon_name = "stock_people"; + } else { + if (is_task) + icon_name = "stock_task"; + else + icon_name = "appointment-new"; + } + + etdp_get_comp_colors (to_do_pane, client, comp, &bgcolor, &bgcolor_set, &fgcolor, &fgcolor_set, + &to_do_pane->priv->nearest_due); + + for (link = new_references; link; link = g_slist_next (link)) { + GtkTreeRowReference *reference = link->data; + + if (gtk_tree_row_reference_valid (reference)) { + GtkTreePath *path; + + path = gtk_tree_row_reference_get_path (reference); + if (path && gtk_tree_model_get_iter (model, &iter, path)) { + gtk_tree_store_set (to_do_pane->priv->tree_store, &iter, + COLUMN_BGCOLOR, bgcolor_set ? &bgcolor : NULL, + COLUMN_FGCOLOR, fgcolor_set ? &fgcolor : NULL, + COLUMN_HAS_ICON_NAME, TRUE, + COLUMN_ICON_NAME, icon_name, + COLUMN_SUMMARY, summary, + COLUMN_TOOLTIP, tooltip, + COLUMN_SORTKEY, sort_key, + COLUMN_DATE_MARK, date_mark, + COLUMN_CAL_CLIENT, client, + COLUMN_CAL_COMPONENT, comp, + -1); + } + + gtk_tree_path_free (path); + } + } + + g_hash_table_insert (to_do_pane->priv->component_refs, component_ident_copy (ident), new_references); + + component_ident_free (ident); + e_cal_component_free_id (id); + g_free (summary); + g_free (tooltip); + g_free (sort_key); +} + +static void +etdp_got_client_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + EClient *client; + GError *error = NULL; + + client = e_client_cache_get_client_finish (E_CLIENT_CACHE (source_object), result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (client && gtk_widget_get_visible (GTK_WIDGET (to_do_pane))) { + ECalClient *cal_client = E_CAL_CLIENT (client); + ESource *source; + ESourceSelectable *selectable = NULL; + ECalDataModel *data_model = NULL; + + g_warn_if_fail (cal_client != NULL); + + source = e_client_get_source (client); + + if (e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) { + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); + data_model = to_do_pane->priv->events_data_model; + } else if (e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_TASKS) { + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST); + data_model = to_do_pane->priv->tasks_data_model; + } + + if (data_model) { + g_hash_table_remove (to_do_pane->priv->client_colors, source); + if (selectable) { + GdkRGBA rgba; + gchar *color_spec; + + color_spec = e_source_selectable_dup_color (selectable); + if (color_spec && gdk_rgba_parse (&rgba, color_spec)) { + g_hash_table_insert (to_do_pane->priv->client_colors, source, gdk_rgba_copy (&rgba)); + } + + g_free (color_spec); + } + + e_cal_data_model_add_client (data_model, cal_client); + } + } else if (!client) { + /* Ignore errors */ + } + + g_clear_object (&client); + g_clear_error (&error); +} + +static gboolean +e_to_do_pane_watcher_filter_cb (ESourceRegistryWatcher *watcher, + ESource *source, + gpointer user_data) +{ + ESourceSelectable *selectable = NULL; + + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST); + + return selectable && e_source_selectable_get_selected (selectable); +} + +static void +e_to_do_pane_watcher_appeared_cb (ESourceRegistryWatcher *watcher, + ESource *source, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + const gchar *extension_name = NULL; + + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (!gtk_widget_get_visible (GTK_WIDGET (to_do_pane))) + return; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + extension_name = E_SOURCE_EXTENSION_CALENDAR; + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + + g_return_if_fail (extension_name != NULL); + + e_client_cache_get_client (to_do_pane->priv->client_cache, source, extension_name, + (guint32) -1, to_do_pane->priv->cancellable, etdp_got_client_cb, to_do_pane); +} + +static void +e_to_do_pane_watcher_disappeared_cb (ESourceRegistryWatcher *watcher, + ESource *source, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + g_hash_table_remove (to_do_pane->priv->client_colors, source); + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + e_cal_data_model_remove_client (to_do_pane->priv->events_data_model, e_source_get_uid (source)); + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + e_cal_data_model_remove_client (to_do_pane->priv->tasks_data_model, e_source_get_uid (source)); +} + +static void +etdp_data_subscriber_component_added (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp) +{ + g_return_if_fail (E_IS_TO_DO_PANE (subscriber)); + + etdp_add_component (E_TO_DO_PANE (subscriber), client, comp); +} + +static void +etdp_data_subscriber_component_modified (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp) +{ + g_return_if_fail (E_IS_TO_DO_PANE (subscriber)); + + etdp_add_component (E_TO_DO_PANE (subscriber), client, comp); +} + +static void +etdp_data_subscriber_component_removed (ECalDataModelSubscriber *subscriber, + ECalClient *client, + const gchar *uid, + const gchar *rid) +{ + EToDoPane *to_do_pane; + ComponentIdent ident; + GSList *link; + + g_return_if_fail (E_IS_TO_DO_PANE (subscriber)); + + to_do_pane = E_TO_DO_PANE (subscriber); + + ident.client = client; + ident.uid = (gchar *) uid; + ident.rid = (gchar *) (rid && *rid ? rid : NULL); + + for (link = g_hash_table_lookup (to_do_pane->priv->component_refs, &ident); link; link = g_slist_next (link)) { + GtkTreeRowReference *reference = link->data; + + if (reference && gtk_tree_row_reference_valid (reference)) { + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_row_reference_get_path (reference); + + if (path && gtk_tree_model_get_iter (gtk_tree_row_reference_get_model (reference), &iter, path)) { + gtk_tree_store_remove (to_do_pane->priv->tree_store, &iter); + } + + gtk_tree_path_free (path); + } + } + + g_hash_table_remove (to_do_pane->priv->component_refs, &ident); +} + +static void +etdp_data_subscriber_freeze (ECalDataModelSubscriber *subscriber) +{ + g_return_if_fail (E_IS_TO_DO_PANE (subscriber)); +} + +static void +etdp_data_subscriber_thaw (ECalDataModelSubscriber *subscriber) +{ + g_return_if_fail (E_IS_TO_DO_PANE (subscriber)); +} + +static GCancellable * +e_to_do_pane_submit_thread_job (GObject *responder, + const gchar *description, + const gchar *alert_ident, + const gchar *alert_arg_0, + EAlertSinkThreadJobFunc func, + gpointer user_data, + GDestroyNotify free_user_data) +{ + EShellView *shell_view; + EActivity *activity; + GCancellable *cancellable = NULL; + + g_return_val_if_fail (E_IS_TO_DO_PANE (responder), NULL); + + shell_view = e_to_do_pane_ref_shell_view (E_TO_DO_PANE (responder)); + if (!shell_view) + return NULL; + + activity = e_shell_view_submit_thread_job (shell_view, description, + alert_ident, alert_arg_0, func, user_data, free_user_data); + + if (activity) { + cancellable = e_activity_get_cancellable (activity); + if (cancellable) + g_object_ref (cancellable); + g_object_unref (activity); + } + + g_clear_object (&shell_view); + + return cancellable; +} + +static void +etdp_update_all (EToDoPane *to_do_pane) +{ + GtkTreeModel *model; + GtkTreeIter iter, next; + gint level = 0; + gboolean done = FALSE; + GHashTable *comps_by_client; /* ECalClient ~> GHashTable { ECalComponent *, NULL } */ + GHashTableIter htiter; + gpointer key, value; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + to_do_pane->priv->nearest_due = (time_t) -1; + + model = GTK_TREE_MODEL (to_do_pane->priv->tree_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return; + + comps_by_client = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, (GDestroyNotify) g_hash_table_unref); + + while (!done) { + if (level != 0) { + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + gtk_tree_model_get (model, &iter, + COLUMN_CAL_CLIENT, &client, + COLUMN_CAL_COMPONENT, &comp, + -1); + + if (client && comp) { + GHashTable *comps; + + comps = g_hash_table_lookup (comps_by_client, client); + if (comps) { + g_hash_table_ref (comps); + } else { + comps = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); + } + + g_hash_table_insert (comps, g_object_ref (comp), NULL); + g_hash_table_insert (comps_by_client, g_object_ref (client), comps); + } + + g_clear_object (&client); + g_clear_object (&comp); + } + + done = !gtk_tree_model_iter_children (model, &next, &iter); + + if (done) { + next = iter; + done = !gtk_tree_model_iter_next (model, &next); + } else { + level++; + } + + if (done) { + while (done = !gtk_tree_model_iter_parent (model, &next, &iter), !done) { + level--; + + iter = next; + done = !gtk_tree_model_iter_next (model, &next); + + if (!done) + break; + } + } + + iter = next; + } + + g_hash_table_iter_init (&htiter, comps_by_client); + while (g_hash_table_iter_next (&htiter, &key, &value)) { + ECalClient *client = key; + GHashTable *comps = value; + GHashTableIter citer; + + g_hash_table_iter_init (&citer, comps); + while (g_hash_table_iter_next (&citer, &key, NULL)) { + ECalComponent *comp = key; + + etdp_add_component (to_do_pane, client, comp); + } + } + + g_hash_table_destroy (comps_by_client); +} + +static void +etdp_update_colors (EToDoPane *to_do_pane, + gboolean only_overdue) +{ + GtkTreeModel *model; + GtkTreeIter iter, next; + gint level = 0; + time_t nearest_due = (time_t) -1; + gboolean done = FALSE; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + model = GTK_TREE_MODEL (to_do_pane->priv->tree_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return; + + while (!done) { + if (level != 0) { + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + gtk_tree_model_get (model, &iter, + COLUMN_CAL_CLIENT, &client, + COLUMN_CAL_COMPONENT, &comp, + -1); + + if (client && comp) { + GdkRGBA bgcolor, fgcolor; + gboolean bgcolor_set = FALSE, fgcolor_set = FALSE; + + etdp_get_comp_colors (to_do_pane, client, comp, &bgcolor, &bgcolor_set, &fgcolor, &fgcolor_set, &nearest_due); + + gtk_tree_store_set (to_do_pane->priv->tree_store, &iter, + COLUMN_BGCOLOR, bgcolor_set ? &bgcolor : NULL, + COLUMN_FGCOLOR, fgcolor_set ? &fgcolor : NULL, + -1); + } + + g_clear_object (&client); + g_clear_object (&comp); + } + + done = !gtk_tree_model_iter_children (model, &next, &iter); + + if (done) { + next = iter; + done = !gtk_tree_model_iter_next (model, &next); + + /* Overdue can be only those 'Today', thus under the first child. */ + if (only_overdue && !level) + break; + } else { + level++; + } + + if (done) { + while (done = !gtk_tree_model_iter_parent (model, &next, &iter), !done) { + level--; + + iter = next; + done = !gtk_tree_model_iter_next (model, &next); + + /* Overdue can be only those 'Today', thus under the first child. */ + if (only_overdue && !level) + done = TRUE; + + if (!done) + break; + } + } + + iter = next; + } + + to_do_pane->priv->nearest_due = nearest_due; +} + +static void +etdp_check_time_changed (EToDoPane *to_do_pane, + gboolean force_update) +{ + icaltimetype itt; + icaltimezone *zone; + guint new_today; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + zone = e_cal_data_model_get_timezone (to_do_pane->priv->events_data_model); + itt = icaltime_current_time_with_zone (zone); + new_today = etdp_create_date_mark (&itt); + + if (force_update || new_today != to_do_pane->priv->last_today) { + gchar *tasks_filter; + time_t tt_begin, tt_end; + gchar *iso_begin_all, *iso_begin, *iso_end; + gint ii; + + to_do_pane->priv->last_today = new_today; + + tt_begin = icaltime_as_timet_with_zone (itt, zone); + tt_begin = time_day_begin_with_zone (tt_begin, zone); + tt_end = time_add_week_with_zone (tt_begin, 1, zone); + + iso_begin_all = isodate_from_time_t (0); + iso_begin = isodate_from_time_t (tt_begin); + iso_end = isodate_from_time_t (tt_end); + if (to_do_pane->priv->show_completed_tasks) { + tasks_filter = g_strdup_printf ( + "(or" + " (and" + " (not (is-completed?))" + " (due-in-time-range? (make-time \"%s\") (make-time \"%s\"))" + ")" + " (and" + " (due-in-time-range? (make-time \"%s\") (make-time \"%s\"))" + ")" + ")", + iso_begin_all, iso_begin, iso_begin, iso_end); + } else { + tasks_filter = g_strdup_printf ( + "(and" + " (not (is-completed?))" + " (due-in-time-range? (make-time \"%s\") (make-time \"%s\"))" + ")", + iso_begin_all, iso_end); + } + + /* Re-label the roots */ + for (ii = 0; ii < N_ROOTS; ii++) { + GtkTreePath *path; + GtkTreeIter iter; + + if (!gtk_tree_row_reference_valid (to_do_pane->priv->roots[ii])) + continue; + + path = gtk_tree_row_reference_get_path (to_do_pane->priv->roots[ii]); + + if (gtk_tree_model_get_iter (gtk_tree_row_reference_get_model (to_do_pane->priv->roots[ii]), &iter, path)) { + struct tm tm; + gchar *markup; + guint date_mark; + + tm = icaltimetype_to_tm (&itt); + + icaltime_adjust (&itt, 1, 0, 0, 0); + + date_mark = etdp_create_date_mark (&itt); + + if (ii == 0) { + markup = g_markup_printf_escaped ("%s", _("Today")); + } else if (ii == 1) { + markup = g_markup_printf_escaped ("%s", _("Tomorrow")); + } else { + gchar *date; + + date = e_datetime_format_format_tm ("calendar", "table", DTFormatKindDate, &tm); + markup = g_markup_printf_escaped ("%s", date); + g_free (date); + } + + gtk_tree_store_set (to_do_pane->priv->tree_store, &iter, + COLUMN_SUMMARY, markup, + COLUMN_DATE_MARK, date_mark, + -1); + + g_free (markup); + } else { + icaltime_adjust (&itt, 1, 0, 0, 0); + } + + gtk_tree_path_free (path); + } + + /* Update data-model-s */ + e_cal_data_model_subscribe (to_do_pane->priv->events_data_model, + E_CAL_DATA_MODEL_SUBSCRIBER (to_do_pane), tt_begin, tt_end); + + e_cal_data_model_set_filter (to_do_pane->priv->tasks_data_model, tasks_filter); + + e_cal_data_model_subscribe (to_do_pane->priv->tasks_data_model, + E_CAL_DATA_MODEL_SUBSCRIBER (to_do_pane), 0, 0); + + g_free (tasks_filter); + g_free (iso_begin_all); + g_free (iso_begin); + g_free (iso_end); + + etdp_update_all (to_do_pane); + } else { + time_t now_tt = icaltime_as_timet_with_zone (itt, zone); + + if (to_do_pane->priv->nearest_due != (time_t) -1 && + to_do_pane->priv->nearest_due <= now_tt) + etdp_update_colors (to_do_pane, TRUE); + } +} + +static gboolean +etdp_check_time_cb (gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + etdp_check_time_changed (to_do_pane, FALSE); + + return TRUE; +} + +static void +etdp_update_queries (EToDoPane *to_do_pane) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_check_time_changed (to_do_pane, TRUE); +} + +static void +etdp_timezone_changed_cb (ECalDataModel *data_model, + GParamSpec *param, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_check_time_changed (to_do_pane, TRUE); +} + +static gboolean +etdp_settings_map_string_to_icaltimezone (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GSettings *settings; + const gchar *location = NULL; + icaltimezone *timezone = NULL; + + settings = e_util_ref_settings ("org.gnome.evolution.calendar"); + + if (g_settings_get_boolean (settings, "use-system-timezone")) + timezone = e_cal_util_get_system_timezone (); + else + location = g_variant_get_string (variant, NULL); + + if (location != NULL && *location != '\0') + timezone = icaltimezone_get_builtin_timezone (location); + + if (timezone == NULL) + timezone = icaltimezone_get_utc_timezone (); + + g_value_set_pointer (value, timezone); + + g_object_unref (settings); + + return TRUE; +} + +static gboolean +etdp_settings_map_string_to_rgba (GValue *value, + GVariant *variant, + gpointer user_data) +{ + GdkRGBA rgba; + const gchar *color_str; + + color_str = g_variant_get_string (variant, NULL); + + if (color_str && gdk_rgba_parse (&rgba, color_str)) + g_value_set_boxed (value, &rgba); + else + g_value_set_boxed (value, NULL); + + return TRUE; +} + +static void +etdp_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + model = gtk_tree_view_get_model (tree_view); + + if (gtk_tree_model_get_iter (model, &iter, path)) { + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + gtk_tree_model_get (model, &iter, + COLUMN_CAL_CLIENT, &client, + COLUMN_CAL_COMPONENT, &comp, + -1); + + if (client && comp) { + e_cal_ops_open_component_in_editor_sync (NULL, client, + e_cal_component_get_icalcomponent (comp), FALSE); + } + + g_clear_object (&client); + g_clear_object (&comp); + } +} + +static void +etdp_source_changed_cb (ESourceRegistry *registry, + ESource *source, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (g_hash_table_contains (to_do_pane->priv->client_colors, source)) { + ESourceSelectable *selectable = NULL; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST); + + if (selectable) { + GdkRGBA rgba; + gchar *color_spec; + + color_spec = e_source_selectable_dup_color (selectable); + if (color_spec && gdk_rgba_parse (&rgba, color_spec)) { + GdkRGBA *current_rgba; + + current_rgba = g_hash_table_lookup (to_do_pane->priv->client_colors, source); + if (!gdk_rgba_equal (current_rgba, &rgba)) { + g_hash_table_insert (to_do_pane->priv->client_colors, source, gdk_rgba_copy (&rgba)); + etdp_update_colors (to_do_pane, FALSE); + } + } + + g_free (color_spec); + } + } +} + +static gboolean +etdp_get_tree_view_selected_one (EToDoPane *to_do_pane, + ECalClient **out_client, + ECalComponent **out_comp) +{ + GtkTreeSelection *selection; + GList *rows; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + gboolean had_any = FALSE; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + if (out_client) + *out_client = NULL; + + if (out_comp) + *out_comp = NULL; + + selection = gtk_tree_view_get_selection (to_do_pane->priv->tree_view); + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + if (rows && gtk_tree_model_get_iter (model, &iter, rows->data)) { + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + gtk_tree_model_get (model, &iter, + COLUMN_CAL_CLIENT, &client, + COLUMN_CAL_COMPONENT, &comp, + -1); + + if (out_client && client) + *out_client = g_object_ref (client); + + if (out_comp && comp) + *out_comp = g_object_ref (comp); + + had_any = client || comp; + + g_clear_object (&client); + g_clear_object (&comp); + } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return had_any; +} + +static void +etdp_new_common (EToDoPane *to_do_pane, + ECalClientSourceType source_type, + gboolean is_assigned) +{ + ECalClient *client = NULL; + EShellView *shell_view; + gchar *client_source_uid = NULL; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (etdp_get_tree_view_selected_one (to_do_pane, &client, NULL) && client) { + ESource *source; + + source = e_client_get_source (E_CLIENT (client)); + if (source) { + const gchar *extension_name = NULL; + + switch (source_type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + default: + break; + } + + /* Cannot ask to create an event in a task list or vice versa. */ + if (!extension_name || !e_source_has_extension (source, extension_name)) + source = NULL; + } + + if (source) + client_source_uid = e_source_dup_uid (source); + } + + g_clear_object (&client); + + shell_view = e_to_do_pane_ref_shell_view (to_do_pane); + + e_cal_ops_new_component_editor (shell_view ? e_shell_view_get_shell_window (shell_view) : NULL, + source_type, client_source_uid, is_assigned); + + g_clear_object (&shell_view); + g_free (client_source_uid); +} + +static void +etdp_new_appointment_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_new_common (to_do_pane, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE); +} + +static void +etdp_new_meeting_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_new_common (to_do_pane, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE); +} + +static void +etdp_new_task_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_new_common (to_do_pane, E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE); +} + +static void +etdp_new_assigned_task_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_new_common (to_do_pane, E_CAL_CLIENT_SOURCE_TYPE_TASKS, TRUE); +} + +static void +etdp_open_selected_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (etdp_get_tree_view_selected_one (to_do_pane, &client, &comp) && client && comp) { + e_cal_ops_open_component_in_editor_sync (NULL, client, + e_cal_component_get_icalcomponent (comp), FALSE); + } + + g_clear_object (&client); + g_clear_object (&comp); +} + +typedef struct _RemoveOperationData { + ECalClient *client; + gchar *uid; + gchar *rid; + ECalObjModType mod; +} RemoveOperationData; + +static void +remove_operation_data_free (gpointer ptr) +{ + RemoveOperationData *rod = ptr; + + if (rod) { + g_clear_object (&rod->client); + g_free (rod->uid); + g_free (rod->rid); + g_free (rod); + } +} + +static void +etdp_remove_component_thread (EAlertSinkThreadJobData *job_data, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + RemoveOperationData *rod = user_data; + + g_return_if_fail (rod != NULL); + + e_cal_client_remove_object_sync (rod->client, rod->uid, rod->rid, rod->mod, cancellable, error); +} + +static void +etdp_delete_common (EToDoPane *to_do_pane, + ECalObjModType mod) +{ + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (etdp_get_tree_view_selected_one (to_do_pane, &client, &comp) && client && comp) { + const gchar *description; + const gchar *alert_ident; + gchar *display_name; + GCancellable *cancellable; + ESource *source; + RemoveOperationData *rod; + ECalComponentId *id; + + id = e_cal_component_get_id (comp); + g_return_if_fail (id != NULL); + + if (!e_cal_dialogs_delete_component (comp, FALSE, 1, e_cal_component_get_vtype (comp), GTK_WIDGET (to_do_pane))) { + e_cal_component_free_id (id); + g_clear_object (&client); + g_clear_object (&comp); + return; + } + + switch (e_cal_client_get_source_type (client)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + description = _("Removing an event"); + alert_ident = "calendar:failed-remove-event"; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + description = _("Removing a memo"); + alert_ident = "calendar:failed-remove-memo"; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + description = _("Removing a task"); + alert_ident = "calendar:failed-remove-task"; + break; + default: + g_warn_if_reached (); + return; + } + + if (!e_cal_component_is_instance (comp)) + mod = E_CAL_OBJ_MOD_ALL; + + rod = g_new0 (RemoveOperationData,1); + rod->client = g_object_ref (client); + rod->uid = g_strdup (id->uid); + rod->rid = g_strdup (id->rid); + rod->mod = mod; + + source = e_client_get_source (E_CLIENT (client)); + display_name = e_util_get_source_full_name (e_source_registry_watcher_get_registry (to_do_pane->priv->watcher), source); + + /* It doesn't matter which data-model is picked, because it's used + only for thread creation and manipulation, not for its content. */ + cancellable = e_cal_data_model_submit_thread_job (to_do_pane->priv->events_data_model, description, alert_ident, + display_name, etdp_remove_component_thread, rod, remove_operation_data_free); + + e_cal_component_free_id (id); + g_clear_object (&cancellable); + g_free (display_name); + } + + g_clear_object (&client); + g_clear_object (&comp); +} + +static void +etdp_delete_selected_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_delete_common (to_do_pane, E_CAL_OBJ_MOD_THIS); +} + +static void +etdp_delete_series_cb (GtkMenuItem *item, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + etdp_delete_common (to_do_pane, E_CAL_OBJ_MOD_ALL); +} + +static void +etdp_fill_popup_menu (EToDoPane *to_do_pane, + GtkMenu *menu) +{ + GtkWidget *item; + GtkMenuShell *menu_shell; + ECalClient *client = NULL; + ECalComponent *comp = NULL; + + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + g_return_if_fail (GTK_IS_MENU (menu)); + + etdp_get_tree_view_selected_one (to_do_pane, &client, &comp); + + menu_shell = GTK_MENU_SHELL (menu); + + item = gtk_image_menu_item_new_with_mnemonic (_("New _Appointment...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("appointment-new", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_new_appointment_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_image_menu_item_new_with_mnemonic (_("New _Meeting...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("stock_people", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_new_meeting_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_image_menu_item_new_with_mnemonic (_("New _Task...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("stock_task", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_new_task_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_New Assigned Task...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("stock_task-assigned", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_new_assigned_task_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + if (client && comp) { + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Open...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("document-open", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_open_selected_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_EVENT && + e_cal_component_is_instance (comp)) { + item = gtk_image_menu_item_new_with_mnemonic (_("_Delete This Instance...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("edit-delete", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_delete_selected_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + + item = gtk_image_menu_item_new_with_mnemonic (_("D_elete All Instances...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("edit-delete", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_delete_series_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + } else { + item = gtk_image_menu_item_new_with_mnemonic (_("_Delete...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ("edit-delete", GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (etdp_delete_series_cb), to_do_pane); + gtk_widget_show (item); + gtk_menu_shell_append (menu_shell, item); + } + } + + g_clear_object (&client); + g_clear_object (&comp); +} + +static gboolean +etdp_destroy_menu_idle_cb (gpointer user_data) +{ + GtkWidget *widget = user_data; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + gtk_widget_destroy (widget); + + return FALSE; +} + +static void +etdp_menu_deactivate_cb (GtkWidget *widget) +{ + g_idle_add (etdp_destroy_menu_idle_cb, widget); +} + +static void +etdp_popup_menu (EToDoPane *to_do_pane, + GdkEvent *event) +{ + GtkMenu *menu; + guint button, event_time; + + menu = GTK_MENU (gtk_menu_new ()); + + g_signal_connect (menu, "deactivate", + G_CALLBACK (etdp_menu_deactivate_cb), NULL); + + etdp_fill_popup_menu (to_do_pane, menu); + + if (event) { + if (!gdk_event_get_button (event, &button)) + button = 0; + + event_time = gdk_event_get_time (event); + } else { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_attach_to_widget (menu, GTK_WIDGET (to_do_pane->priv->tree_view), NULL); + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, button, event_time); +} + +static gboolean +etdp_button_press_event_cb (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + if (event->type == GDK_BUTTON_PRESS && + gdk_event_triggers_context_menu (event)) { + GtkTreeSelection *selection; + GtkTreePath *path; + + selection = gtk_tree_view_get_selection (to_do_pane->priv->tree_view); + if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE) + gtk_tree_selection_unselect_all (selection); + + if (gtk_tree_view_get_path_at_pos (to_do_pane->priv->tree_view, event->button.x, event->button.y, &path, NULL, NULL, NULL)) { + gtk_tree_selection_select_path (selection, path); + gtk_tree_view_set_cursor (to_do_pane->priv->tree_view, path, NULL, FALSE); + + gtk_tree_path_free (path); + } + + etdp_popup_menu (to_do_pane, event); + + return TRUE; + } + + return FALSE; +} + +static gboolean +etdp_popup_menu_cb (GtkWidget *widget, + gpointer user_data) +{ + EToDoPane *to_do_pane = user_data; + + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + etdp_popup_menu (to_do_pane, NULL); + + return TRUE; +} + +static void +etcp_notify_visible_cb (EToDoPane *to_do_pane, + GParamSpec *param, + gpointer user_data) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (gtk_widget_get_visible (GTK_WIDGET (to_do_pane))) { + e_source_registry_watcher_reclaim (to_do_pane->priv->watcher); + } else { + GList *clients, *link; + + clients = e_cal_data_model_get_clients (to_do_pane->priv->events_data_model); + for (link = clients; link; link = g_list_next (link)) { + ECalClient *client = link->data; + ESource *source = e_client_get_source (E_CLIENT (client)); + + e_cal_data_model_remove_client (to_do_pane->priv->events_data_model, e_source_get_uid (source)); + } + g_list_free_full (clients, g_object_unref); + + clients = e_cal_data_model_get_clients (to_do_pane->priv->tasks_data_model); + for (link = clients; link; link = g_list_next (link)) { + ECalClient *client = link->data; + ESource *source = e_client_get_source (E_CLIENT (client)); + + e_cal_data_model_remove_client (to_do_pane->priv->tasks_data_model, e_source_get_uid (source)); + } + g_list_free_full (clients, g_object_unref); + } +} + +static void +e_to_do_pane_set_shell_view (EToDoPane *to_do_pane, + EShellView *shell_view) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); + + g_weak_ref_set (&to_do_pane->priv->shell_view_weakref, shell_view); +} + +static void +e_to_do_pane_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_HIGHLIGHT_OVERDUE: + e_to_do_pane_set_highlight_overdue ( + E_TO_DO_PANE (object), + g_value_get_boolean (value)); + return; + + case PROP_OVERDUE_COLOR: + e_to_do_pane_set_overdue_color ( + E_TO_DO_PANE (object), + g_value_get_boxed (value)); + return; + + case PROP_SHELL_VIEW: + e_to_do_pane_set_shell_view ( + E_TO_DO_PANE (object), + g_value_get_object (value)); + return; + + case PROP_SHOW_COMPLETED_TASKS: + e_to_do_pane_set_show_completed_tasks ( + E_TO_DO_PANE (object), + g_value_get_boolean (value)); + return; + + case PROP_USE_24HOUR_FORMAT: + e_to_do_pane_set_use_24hour_format ( + E_TO_DO_PANE (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_to_do_pane_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_HIGHLIGHT_OVERDUE: + g_value_set_boolean ( + value, + e_to_do_pane_get_highlight_overdue ( + E_TO_DO_PANE (object))); + return; + + case PROP_OVERDUE_COLOR: + g_value_set_boxed ( + value, + e_to_do_pane_get_overdue_color ( + E_TO_DO_PANE (object))); + return; + + case PROP_SHELL_VIEW: + g_value_set_object ( + value, + e_to_do_pane_ref_shell_view ( + E_TO_DO_PANE (object))); + return; + + case PROP_SHOW_COMPLETED_TASKS: + g_value_set_boolean ( + value, + e_to_do_pane_get_show_completed_tasks ( + E_TO_DO_PANE (object))); + return; + + case PROP_USE_24HOUR_FORMAT: + g_value_set_boolean ( + value, + e_to_do_pane_get_use_24hour_format ( + E_TO_DO_PANE (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_to_do_pane_constructed (GObject *object) +{ + EToDoPane *to_do_pane = E_TO_DO_PANE (object); + EShellView *shell_view; + EShell *shell; + GtkGrid *grid; + GtkWidget *widget; + GtkCellRenderer *renderer; + GtkTreeView *tree_view; + GtkTreeViewColumn *column; + GtkTreeModel *model, *sort_model; + GtkTreeIter iter; + GSettings *settings; + PangoAttrList *bold; + gint ii; + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_to_do_pane_parent_class)->constructed (object); + + shell_view = e_to_do_pane_ref_shell_view (to_do_pane); + shell = e_shell_backend_get_shell (e_shell_view_get_shell_backend (shell_view)); + + to_do_pane->priv->client_cache = g_object_ref (e_shell_get_client_cache (shell)); + to_do_pane->priv->watcher = e_source_registry_watcher_new (e_shell_get_registry (shell), NULL); + to_do_pane->priv->source_changed_id = + g_signal_connect (e_source_registry_watcher_get_registry (to_do_pane->priv->watcher), "source-changed", + G_CALLBACK (etdp_source_changed_cb), to_do_pane); + + g_signal_connect (to_do_pane->priv->watcher, "filter", + G_CALLBACK (e_to_do_pane_watcher_filter_cb), NULL); + + g_signal_connect (to_do_pane->priv->watcher, "appeared", + G_CALLBACK (e_to_do_pane_watcher_appeared_cb), to_do_pane); + + g_signal_connect (to_do_pane->priv->watcher, "disappeared", + G_CALLBACK (e_to_do_pane_watcher_disappeared_cb), to_do_pane); + + to_do_pane->priv->tree_store = GTK_TREE_STORE (gtk_tree_store_new (N_COLUMNS, + GDK_TYPE_RGBA, /* COLUMN_BGCOLOR */ + GDK_TYPE_RGBA, /* COLUMN_FGCOLOR */ + G_TYPE_BOOLEAN, /* COLUMN_HAS_ICON_NAME */ + G_TYPE_STRING, /* COLUMN_ICON_NAME */ + G_TYPE_STRING, /* COLUMN_SUMMARY */ + G_TYPE_STRING, /* COLUMN_TOOLTIP */ + G_TYPE_STRING, /* COLUMN_SORTKEY */ + G_TYPE_UINT, /* COLUMN_DATE_MARK */ + E_TYPE_CAL_CLIENT, /* COLUMN_CAL_CLIENT */ + E_TYPE_CAL_COMPONENT)); /* COLUMN_CAL_COMPONENT */ + + grid = GTK_GRID (to_do_pane); + + bold = pango_attr_list_new (); + pango_attr_list_insert (bold, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + + widget = gtk_label_new (_("To Do")); + g_object_set (G_OBJECT (widget), + "halign", GTK_ALIGN_CENTER, + "hexpand", TRUE, + "valign", GTK_ALIGN_START, + "vexpand", FALSE, + "attributes", bold, + NULL); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + pango_attr_list_unref (bold); + + model = GTK_TREE_MODEL (to_do_pane->priv->tree_store); + + sort_model = gtk_tree_model_sort_new_with_model (model); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model), COLUMN_SORTKEY, GTK_SORT_ASCENDING); + + widget = gtk_tree_view_new_with_model (sort_model); + + g_object_set (G_OBJECT (widget), + "halign", GTK_ALIGN_FILL, + "hexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "vexpand", TRUE, + NULL); + + tree_view = GTK_TREE_VIEW (widget); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (tree_view)); + + g_object_set (G_OBJECT (widget), + "halign", GTK_ALIGN_FILL, + "hexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "vexpand", TRUE, + NULL); + + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + renderer = gtk_cell_renderer_pixbuf_new (); + + column = gtk_tree_view_column_new_with_attributes ("Text", renderer, + "icon-name", COLUMN_ICON_NAME, + "visible", COLUMN_HAS_ICON_NAME, + NULL); + + renderer = gtk_cell_renderer_text_new (); + + g_object_set (G_OBJECT (renderer), + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + gtk_tree_view_column_pack_start (column, renderer, TRUE); + + gtk_tree_view_column_set_attributes (column, renderer, + "markup", COLUMN_SUMMARY, + "background-rgba", COLUMN_BGCOLOR, + "foreground-rgba", COLUMN_FGCOLOR, + NULL); + + gtk_tree_view_append_column (tree_view, column); + gtk_tree_view_set_expander_column (tree_view, column); + + for (ii = 0; ii < N_ROOTS; ii++) { + GtkTreePath *path; + gchar *sort_key; + + sort_key = g_strdup_printf ("%c", 'A' + ii); + + gtk_tree_store_append (to_do_pane->priv->tree_store, &iter, NULL); + gtk_tree_store_set (to_do_pane->priv->tree_store, &iter, + COLUMN_SORTKEY, sort_key, + COLUMN_HAS_ICON_NAME, FALSE, + -1); + + g_free (sort_key); + + path = gtk_tree_model_get_path (model, &iter); + + to_do_pane->priv->roots[ii] = gtk_tree_row_reference_new (model, path); + g_warn_if_fail (to_do_pane->priv->roots[ii] != NULL); + + gtk_tree_path_free (path); + } + + gtk_tree_view_set_headers_visible (tree_view, FALSE); + gtk_tree_view_set_tooltip_column (tree_view, COLUMN_TOOLTIP); + + gtk_widget_show_all (GTK_WIDGET (grid)); + + to_do_pane->priv->events_data_model = e_cal_data_model_new (e_to_do_pane_submit_thread_job, G_OBJECT (to_do_pane)); + to_do_pane->priv->tasks_data_model = e_cal_data_model_new (e_to_do_pane_submit_thread_job, G_OBJECT (to_do_pane)); + to_do_pane->priv->time_checker_id = g_timeout_add_seconds (60, etdp_check_time_cb, to_do_pane); + + e_cal_data_model_set_expand_recurrences (to_do_pane->priv->events_data_model, TRUE); + e_cal_data_model_set_expand_recurrences (to_do_pane->priv->tasks_data_model, FALSE); + + settings = e_util_ref_settings ("org.gnome.evolution.calendar"); + + g_settings_bind_with_mapping ( + settings, "timezone", + to_do_pane->priv->events_data_model, "timezone", + G_SETTINGS_BIND_GET, + etdp_settings_map_string_to_icaltimezone, + NULL, /* one-way binding */ + NULL, NULL); + + g_settings_bind_with_mapping ( + settings, "timezone", + to_do_pane->priv->tasks_data_model, "timezone", + G_SETTINGS_BIND_GET, + etdp_settings_map_string_to_icaltimezone, + NULL, /* one-way binding */ + NULL, NULL); + + g_settings_bind ( + settings, "task-overdue-highlight", + to_do_pane, "highlight-overdue", + G_SETTINGS_BIND_GET); + + g_settings_bind_with_mapping ( + settings, "task-overdue-color", + to_do_pane, "overdue-color", + G_SETTINGS_BIND_GET, + etdp_settings_map_string_to_rgba, + NULL, /* one-way binding */ + NULL, NULL); + + g_settings_bind ( + settings, "use-24hour-format", + to_do_pane, "use-24hour-format", + G_SETTINGS_BIND_GET); + + g_object_unref (settings); + + g_signal_connect (to_do_pane->priv->events_data_model, "notify::timezone", + G_CALLBACK (etdp_timezone_changed_cb), to_do_pane); + + g_signal_connect (tree_view, "row-activated", + G_CALLBACK (etdp_row_activated_cb), to_do_pane); + + g_signal_connect (tree_view, "button-press-event", + G_CALLBACK (etdp_button_press_event_cb), to_do_pane); + + g_signal_connect (tree_view, "popup-menu", + G_CALLBACK (etdp_popup_menu_cb), to_do_pane); + + to_do_pane->priv->tree_view = tree_view; + + etdp_check_time_changed (to_do_pane, TRUE); + + g_clear_object (&shell_view); + g_clear_object (&sort_model); + + g_signal_connect (to_do_pane, "notify::visible", + G_CALLBACK (etcp_notify_visible_cb), NULL); + + if (gtk_widget_get_visible (GTK_WIDGET (to_do_pane))) + e_source_registry_watcher_reclaim (to_do_pane->priv->watcher); +} + +static void +e_to_do_pane_dispose (GObject *object) +{ + EToDoPane *to_do_pane = E_TO_DO_PANE (object); + gint ii; + + if (to_do_pane->priv->cancellable) { + g_cancellable_cancel (to_do_pane->priv->cancellable); + g_clear_object (&to_do_pane->priv->cancellable); + } + + if (to_do_pane->priv->time_checker_id) { + g_source_remove (to_do_pane->priv->time_checker_id); + to_do_pane->priv->time_checker_id = 0; + } + + if (to_do_pane->priv->source_changed_id) { + g_signal_handler_disconnect (e_source_registry_watcher_get_registry (to_do_pane->priv->watcher), + to_do_pane->priv->source_changed_id); + to_do_pane->priv->source_changed_id = 0; + } + + for (ii = 0; ii < N_ROOTS; ii++) { + gtk_tree_row_reference_free (to_do_pane->priv->roots[ii]); + to_do_pane->priv->roots[ii] = NULL; + } + + g_hash_table_remove_all (to_do_pane->priv->component_refs); + g_hash_table_remove_all (to_do_pane->priv->client_colors); + + g_clear_object (&to_do_pane->priv->client_cache); + g_clear_object (&to_do_pane->priv->watcher); + g_clear_object (&to_do_pane->priv->tree_store); + g_clear_object (&to_do_pane->priv->events_data_model); + g_clear_object (&to_do_pane->priv->tasks_data_model); + + g_weak_ref_set (&to_do_pane->priv->shell_view_weakref, NULL); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_to_do_pane_parent_class)->dispose (object); +} + +static void +e_to_do_pane_finalize (GObject *object) +{ + EToDoPane *to_do_pane = E_TO_DO_PANE (object); + + g_weak_ref_clear (&to_do_pane->priv->shell_view_weakref); + + g_hash_table_destroy (to_do_pane->priv->component_refs); + g_hash_table_destroy (to_do_pane->priv->client_colors); + + if (to_do_pane->priv->overdue_color) + gdk_rgba_free (to_do_pane->priv->overdue_color); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_to_do_pane_parent_class)->finalize (object); +} + +static void +e_to_do_pane_init (EToDoPane *to_do_pane) +{ + to_do_pane->priv = G_TYPE_INSTANCE_GET_PRIVATE (to_do_pane, E_TYPE_TO_DO_PANE, EToDoPanePrivate); + to_do_pane->priv->cancellable = g_cancellable_new (); + + to_do_pane->priv->component_refs = g_hash_table_new_full (component_ident_hash, component_ident_equal, + component_ident_free, etdp_free_component_refs); + + to_do_pane->priv->client_colors = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) gdk_rgba_free); + + to_do_pane->priv->nearest_due = (time_t) -1; + + g_weak_ref_init (&to_do_pane->priv->shell_view_weakref, NULL); +} + +static void +e_to_do_pane_class_init (EToDoPaneClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (EToDoPanePrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = e_to_do_pane_set_property; + object_class->get_property = e_to_do_pane_get_property; + object_class->constructed = e_to_do_pane_constructed; + object_class->dispose = e_to_do_pane_dispose; + object_class->finalize = e_to_do_pane_finalize; + + g_object_class_install_property ( + object_class, + PROP_HIGHLIGHT_OVERDUE, + g_param_spec_boolean ( + "highlight-overdue", + "Highlight Overdue Tasks", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_OVERDUE_COLOR, + g_param_spec_boxed ( + "overdue-color", + "Overdue Color", + NULL, + GDK_TYPE_RGBA, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_SHELL_VIEW, + g_param_spec_object ( + "shell-view", + "EShellView", + NULL, + E_TYPE_SHELL_VIEW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_SHOW_COMPLETED_TASKS, + g_param_spec_boolean ( + "show-completed-tasks", + "Show Completed Tasks", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_USE_24HOUR_FORMAT, + g_param_spec_boolean ( + "use-24hour-format", + "Use 24hour Format", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +e_to_do_pane_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface) +{ + iface->component_added = etdp_data_subscriber_component_added; + iface->component_modified = etdp_data_subscriber_component_modified; + iface->component_removed = etdp_data_subscriber_component_removed; + iface->freeze = etdp_data_subscriber_freeze; + iface->thaw = etdp_data_subscriber_thaw; +} + +/** + * e_to_do_pane_new: + * @shell_view: an #EShellView + * + * Creates a new #EToDoPane. + * + * Returns: (transfer full): A new #EToDoPane. + * + * Since: 3.26 + **/ +GtkWidget * +e_to_do_pane_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new (E_TYPE_TO_DO_PANE, + "shell-view", shell_view, + NULL); +} + +/** + * e_to_do_pane_ref_shell_view: + * @to_do_pane: an #EToDoPane + * + * Returns: (transfer full): an #EShellView used to create the @to_do_pane with added reference. + * Free it with g_object_unref() when no longer needed. + * + * Since: 3.26 + **/ +EShellView * +e_to_do_pane_ref_shell_view (EToDoPane *to_do_pane) +{ + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), NULL); + + return g_weak_ref_get (&to_do_pane->priv->shell_view_weakref); +} + +/** + * e_to_do_pane_get_highlight_overdue: + * @to_do_pane: an #EToDoPane + * + * Returns: Whether highlights overdue tasks with overdue-color. + * + * Since: 3.26 + **/ +gboolean +e_to_do_pane_get_highlight_overdue (EToDoPane *to_do_pane) +{ + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + return to_do_pane->priv->highlight_overdue; +} + +/** + * e_to_do_pane_set_highlight_overdue: + * @to_do_pane: an #EToDoPane + * @highlight_overdue: a value to set + * + * Sets whether should highlight overdue tasks with overdue-color. + * + * Since: 3.26 + **/ +void +e_to_do_pane_set_highlight_overdue (EToDoPane *to_do_pane, + gboolean highlight_overdue) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if ((to_do_pane->priv->highlight_overdue ? 1 : 0) == (highlight_overdue ? 1 : 0)) + return; + + to_do_pane->priv->highlight_overdue = highlight_overdue; + + if (to_do_pane->priv->overdue_color) + etdp_update_colors (to_do_pane, TRUE); + + g_object_notify (G_OBJECT (to_do_pane), "highlight-overdue"); +} + +/** + * e_to_do_pane_get_overdue_color: + * @to_do_pane: an #EToDoPane + * + * Returns: (transfer none) (nullable): Currently set color to use for overdue tasks. + * + * Since: 3.26 + **/ +const GdkRGBA * +e_to_do_pane_get_overdue_color (EToDoPane *to_do_pane) +{ + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), NULL); + + return to_do_pane->priv->overdue_color; +} + +/** + * e_to_do_pane_set_overdue_color: + * @to_do_pane: an #EToDoPane + * @overdue_color: (nullable): a color to set, or %NULL + * + * Sets a color to use for overdue tasks, or unsets the previous, + * when it's %NULL. + * + * Since: 3.26 + **/ +void +e_to_do_pane_set_overdue_color (EToDoPane *to_do_pane, + const GdkRGBA *overdue_color) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if (to_do_pane->priv->overdue_color == overdue_color || + (to_do_pane->priv->overdue_color && overdue_color && + gdk_rgba_equal (to_do_pane->priv->overdue_color, overdue_color))) + return; + + if (to_do_pane->priv->overdue_color) { + gdk_rgba_free (to_do_pane->priv->overdue_color); + to_do_pane->priv->overdue_color = NULL; + } + + if (overdue_color) + to_do_pane->priv->overdue_color = gdk_rgba_copy (overdue_color); + + if (to_do_pane->priv->highlight_overdue) + etdp_update_colors (to_do_pane, TRUE); + + g_object_notify (G_OBJECT (to_do_pane), "overdue-color"); +} + +/** + * e_to_do_pane_get_show_completed_tasks: + * @to_do_pane: an #EToDoPane + * + * Returns: Whether completed tasks should be shown in the view. + * + * Since: 3.26 + **/ +gboolean +e_to_do_pane_get_show_completed_tasks (EToDoPane *to_do_pane) +{ + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + return to_do_pane->priv->show_completed_tasks; +} + +/** + * e_to_do_pane_set_show_completed_tasks: + * @to_do_pane: an #EToDoPane + * @show_completed_tasks: a value to set + * + * Sets whether completed tasks should be shown in the view. + * + * Since: 3.26 + **/ +void +e_to_do_pane_set_show_completed_tasks (EToDoPane *to_do_pane, + gboolean show_completed_tasks) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if ((to_do_pane->priv->show_completed_tasks ? 1 : 0) == (show_completed_tasks ? 1 : 0)) + return; + + to_do_pane->priv->show_completed_tasks = show_completed_tasks; + + etdp_update_queries (to_do_pane); + + g_object_notify (G_OBJECT (to_do_pane), "show-completed-tasks"); +} + +/** + * e_to_do_pane_get_use_24hour_format: + * @to_do_pane: an #EToDoPane + * + * Returns: Whether uses 24-hour format for time display. + * + * Since: 3.26 + **/ +gboolean +e_to_do_pane_get_use_24hour_format (EToDoPane *to_do_pane) +{ + g_return_val_if_fail (E_IS_TO_DO_PANE (to_do_pane), FALSE); + + return to_do_pane->priv->use_24hour_format; +} + +/** + * e_to_do_pane_set_use_24hour_format: + * @to_do_pane: an #EToDoPane + * @use_24hour_format: a value to set + * + * Sets whether to use 24-hour format for time display. + * + * Since: 3.26 + **/ +void +e_to_do_pane_set_use_24hour_format (EToDoPane *to_do_pane, + gboolean use_24hour_format) +{ + g_return_if_fail (E_IS_TO_DO_PANE (to_do_pane)); + + if ((to_do_pane->priv->use_24hour_format ? 1 : 0) == (use_24hour_format ? 1 : 0)) + return; + + to_do_pane->priv->use_24hour_format = use_24hour_format; + + etdp_update_all (to_do_pane); + + g_object_notify (G_OBJECT (to_do_pane), "use-24hour-format"); +} diff --git a/src/calendar/gui/e-to-do-pane.h b/src/calendar/gui/e-to-do-pane.h new file mode 100644 index 0000000000..8ce778647a --- /dev/null +++ b/src/calendar/gui/e-to-do-pane.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com) + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef E_TO_DO_PANE_H +#define E_TO_DO_PANE_H + +#include + +#include + +#include + +/* Standard GObject macros */ + +#define E_TYPE_TO_DO_PANE \ + (e_to_do_pane_get_type ()) +#define E_TO_DO_PANE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_TO_DO_PANE, EToDoPane)) +#define E_TO_DO_PANE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_TO_DO_PANE, EToDoPaneClass)) +#define E_IS_TO_DO_PANE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_TO_DO_PANE)) +#define E_IS_TO_DO_PANE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_TO_DO_PANE)) +#define E_TO_DO_PANE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_TO_DO_PANE, EToDoPaneClass)) + +G_BEGIN_DECLS + +typedef struct _EToDoPane EToDoPane; +typedef struct _EToDoPaneClass EToDoPaneClass; +typedef struct _EToDoPanePrivate EToDoPanePrivate; + +struct _EToDoPane { + GtkGrid parent; + + EToDoPanePrivate *priv; +}; + +struct _EToDoPaneClass { + GtkGridClass parent_class; +}; + +GType e_to_do_pane_get_type (void) G_GNUC_CONST; +GtkWidget * e_to_do_pane_new (EShellView *shell_view); +EShellView * e_to_do_pane_ref_shell_view (EToDoPane *to_do_pane); +gboolean e_to_do_pane_get_highlight_overdue + (EToDoPane *to_do_pane); +void e_to_do_pane_set_highlight_overdue + (EToDoPane *to_do_pane, + gboolean highlight_overdue); +const GdkRGBA * e_to_do_pane_get_overdue_color (EToDoPane *to_do_pane); +void e_to_do_pane_set_overdue_color (EToDoPane *to_do_pane, + const GdkRGBA *overdue_color); +gboolean e_to_do_pane_get_show_completed_tasks + (EToDoPane *to_do_pane); +void e_to_do_pane_set_show_completed_tasks + (EToDoPane *to_do_pane, + gboolean show_completed_tasks); +gboolean e_to_do_pane_get_use_24hour_format + (EToDoPane *to_do_pane); +void e_to_do_pane_set_use_24hour_format + (EToDoPane *to_do_pane, + gboolean use_24hour_format); + +G_END_DECLS + +#endif /* E_TO_DO_PANE_H */ diff --git a/src/modules/mail/CMakeLists.txt b/src/modules/mail/CMakeLists.txt index ca27f5ae2e..9205b62a4e 100644 --- a/src/modules/mail/CMakeLists.txt +++ b/src/modules/mail/CMakeLists.txt @@ -1,5 +1,6 @@ set(extra_deps email-engine + evolution-calendar evolution-mail evolution-mail-composer evolution-mail-formatter diff --git a/src/modules/mail/e-mail-shell-content.c b/src/modules/mail/e-mail-shell-content.c index 24d3708e5a..60ac7a361d 100644 --- a/src/modules/mail/e-mail-shell-content.c +++ b/src/modules/mail/e-mail-shell-content.c @@ -26,6 +26,8 @@ #include +#include "calendar/gui/e-to-do-pane.h" + #include #include #include @@ -41,6 +43,7 @@ struct _EMailShellContentPrivate { EMailView *mail_view; + GtkWidget *to_do_pane; /* not referenced */ }; enum { @@ -49,7 +52,8 @@ enum { PROP_GROUP_BY_THREADS, PROP_MAIL_VIEW, PROP_REPLY_STYLE, - PROP_MARK_SEEN_ALWAYS + PROP_MARK_SEEN_ALWAYS, + PROP_TO_DO_PANE }; /* Forward Declarations */ @@ -171,6 +175,12 @@ mail_shell_content_get_property (GObject *object, value, e_mail_reader_get_mark_seen_always ( E_MAIL_READER (object))); return; + + case PROP_TO_DO_PANE: + g_value_set_object ( + value, e_mail_shell_content_get_to_do_pane ( + E_MAIL_SHELL_CONTENT (object))); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -200,9 +210,11 @@ mail_shell_content_constructed (GObject *object) EShellView *shell_view; EAttachmentStore *attachment_store; EMailDisplay *display; + GtkPaned *paned; GtkWindow *window; GtkWidget *widget; GtkBox *vbox; + GSettings *settings; priv = E_MAIL_SHELL_CONTENT_GET_PRIVATE (object); @@ -214,10 +226,16 @@ mail_shell_content_constructed (GObject *object) /* Build content widgets. */ - widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + widget = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_add (GTK_CONTAINER (shell_content), widget); gtk_widget_show (widget); + paned = GTK_PANED (widget); + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_paned_pack1 (paned, widget, TRUE, FALSE); + gtk_widget_show (widget); + vbox = GTK_BOX (widget); widget = e_mail_paned_view_new (shell_view); @@ -244,6 +262,33 @@ mail_shell_content_constructed (GObject *object) mail_shell_content_transform_num_attachments_to_visible_boolean_with_settings, NULL, NULL, NULL); + widget = e_to_do_pane_new (shell_view); + gtk_paned_pack2 (paned, widget, FALSE, FALSE); + gtk_widget_show (widget); + + priv->to_do_pane = widget; + + settings = e_util_ref_settings ("org.gnome.evolution.mail"); + + if (e_shell_window_is_main_instance (e_shell_view_get_shell_window (shell_view))) { + g_settings_bind ( + settings, "to-do-bar-width", + paned, "position", + G_SETTINGS_BIND_DEFAULT); + } else { + g_settings_bind ( + settings, "to-do-bar-width-sub", + paned, "position", + G_SETTINGS_BIND_DEFAULT); + } + + g_settings_bind ( + settings, "to-do-bar-show-completed-tasks", + priv->to_do_pane, "show-completed-tasks", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); + window = e_mail_reader_get_window (E_MAIL_READER (object)); widget = e_mail_reader_get_message_list (E_MAIL_READER (object)); @@ -489,6 +534,16 @@ e_mail_shell_content_class_init (EMailShellContentClass *class) object_class, PROP_MARK_SEEN_ALWAYS, "mark-seen-always"); + + g_object_class_install_property ( + object_class, + PROP_TO_DO_PANE, + g_param_spec_object ( + "to-do-pane", + "To Do Pane", + NULL, + E_TYPE_TO_DO_PANE, + G_PARAM_READABLE)); } static void @@ -564,3 +619,11 @@ e_mail_shell_content_get_searchbar (EMailShellContent *mail_shell_content) return E_SHELL_SEARCHBAR (searchbar); } + +GtkWidget * +e_mail_shell_content_get_to_do_pane (EMailShellContent *mail_shell_content) +{ + g_return_val_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content), NULL); + + return mail_shell_content->priv->to_do_pane; +} diff --git a/src/modules/mail/e-mail-shell-content.h b/src/modules/mail/e-mail-shell-content.h index db9ef85c78..17e7cd73a5 100644 --- a/src/modules/mail/e-mail-shell-content.h +++ b/src/modules/mail/e-mail-shell-content.h @@ -70,6 +70,8 @@ EMailView * e_mail_shell_content_get_mail_view EShellSearchbar * e_mail_shell_content_get_searchbar (EMailShellContent *mail_shell_content); +GtkWidget * e_mail_shell_content_get_to_do_pane + (EMailShellContent *mail_shell_content); G_END_DECLS diff --git a/src/modules/mail/e-mail-shell-view-actions.c b/src/modules/mail/e-mail-shell-view-actions.c index 47d31aff83..1743a31a39 100644 --- a/src/modules/mail/e-mail-shell-view-actions.c +++ b/src/modules/mail/e-mail-shell-view-actions.c @@ -276,6 +276,21 @@ action_mail_attachment_bar_cb (GtkAction *action, } } +static void +action_mail_to_do_bar_cb (GtkAction *action, + EShellView *shell_view) +{ + EShellContent *shell_content; + GtkWidget *to_do_pane; + + g_return_if_fail (E_IS_MAIL_SHELL_VIEW (shell_view)); + + shell_content = e_shell_view_get_shell_content (shell_view); + to_do_pane = e_mail_shell_content_get_to_do_pane (E_MAIL_SHELL_CONTENT (shell_content)); + + gtk_widget_set_visible (to_do_pane, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))); +} + static void action_mail_download_finished_cb (CamelStore *store, GAsyncResult *result, @@ -1969,6 +1984,14 @@ static GtkToggleActionEntry mail_toggle_entries[] = { NULL, /* Handled by property bindings */ FALSE }, + { "mail-to-do-bar", + NULL, + N_("Show To _Do Bar"), + NULL, + N_("Show To Do bar with appointments and tasks"), + G_CALLBACK (action_mail_to_do_bar_cb), + TRUE }, + { "mail-vfolder-unmatched-enable", NULL, N_("_Unmatched Folder Enabled"), @@ -2259,6 +2282,18 @@ e_mail_shell_view_actions_init (EMailShellView *mail_shell_view) ACTION (MAIL_ATTACHMENT_BAR), "active", G_SETTINGS_BIND_DEFAULT); + if (e_shell_window_is_main_instance (shell_window)) { + g_settings_bind ( + settings, "show-to-do-bar", + ACTION (MAIL_TO_DO_BAR), "active", + G_SETTINGS_BIND_DEFAULT); + } else { + g_settings_bind ( + settings, "show-to-do-bar-sub", + ACTION (MAIL_TO_DO_BAR), "active", + G_SETTINGS_BIND_DEFAULT); + } + g_object_unref (settings); /* Fine tuning. */ diff --git a/src/modules/mail/e-mail-shell-view-actions.h b/src/modules/mail/e-mail-shell-view-actions.h index 5459f735c7..f3fbd3279c 100644 --- a/src/modules/mail/e-mail-shell-view-actions.h +++ b/src/modules/mail/e-mail-shell-view-actions.h @@ -208,6 +208,8 @@ E_SHELL_WINDOW_ACTION ((window), "mail-tools-search-folders") #define E_SHELL_WINDOW_ACTION_MAIL_TOOLS_SUBSCRIPTIONS(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-tools-subscriptions") +#define E_SHELL_WINDOW_ACTION_MAIL_TO_DO_BAR(window) \ + E_SHELL_WINDOW_ACTION ((window), "mail-to-do-bar") #define E_SHELL_WINDOW_ACTION_MAIL_UNDELETE(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-undelete") #define E_SHELL_WINDOW_ACTION_MAIL_VFOLDER_UNMATCHED_ENABLE(window) \