[evolution] Bug 266621 - Add "To Do" bar with events and tasks for Mail view



commit f733f3ad4c4a36f68ed4e1b4cdcb9863c19bdcfd
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jun 14 18:57:23 2017 +0200

    Bug 266621 - Add "To Do" bar with events and tasks for Mail view

 data/org.gnome.evolution.mail.gschema.xml.in |   26 +
 data/ui/evolution-mail.ui                    |    5 +
 data/ui/evolution-shell.ui                   |    1 +
 po/POTFILES.in                               |    1 +
 src/calendar/gui/CMakeLists.txt              |    2 +
 src/calendar/gui/e-cal-ops.c                 |    9 +-
 src/calendar/gui/e-to-do-pane.c              | 2722 ++++++++++++++++++++++++++
 src/calendar/gui/e-to-do-pane.h              |   87 +
 src/modules/mail/CMakeLists.txt              |    1 +
 src/modules/mail/e-mail-shell-content.c      |   67 +-
 src/modules/mail/e-mail-shell-content.h      |    2 +
 src/modules/mail/e-mail-shell-view-actions.c |   35 +
 src/modules/mail/e-mail-shell-view-actions.h |    2 +
 13 files changed, 2954 insertions(+), 6 deletions(-)
---
diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in
index 37106cb..def0bd9 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.</_description>
     </key>
 
+    <key name="show-to-do-bar" type="b">
+      <default>true</default>
+      <_summary>Whether the To Do bar is visible in the main window</_summary>
+      <_description>Stores whether the To Do bar is visible in the main window.</_description>
+    </key>
+    <key name="to-do-bar-width" type="i">
+      <default>9999</default>
+      <_summary>Width of the To Do bar in the main window</_summary>
+      <_description>Holds the width of the To Do bar for the main window.</_description>
+    </key>
+    <key name="show-to-do-bar-sub" type="b">
+      <default>true</default>
+      <_summary>Whether the To Do bar is visible in a sub-window</_summary>
+      <_description>Stores whether the To Do bar is visible in a sub-window.</_description>
+    </key>
+    <key name="to-do-bar-width-sub" type="i">
+      <default>9999</default>
+      <_summary>Width of the To Do bar in a sub-window</_summary>
+      <_description>Holds the width of the To Do bar for a sub-window.</_description>
+    </key>
+    <key name="to-do-bar-show-completed-tasks" type="b">
+      <default>false</default>
+      <_summary>Whether the To Do bar should show also completed tasks</_summary>
+      <_description>Stores whether the To Do bar should show also completed tasks.</_description>
+    </key>
+
     <!-- The following keys are deprecated. -->
 
     <key name="forward-style" type="i">
diff --git a/data/ui/evolution-mail.ui b/data/ui/evolution-mail.ui
index 322c83b..eb455f3 100644
--- a/data/ui/evolution-mail.ui
+++ b/data/ui/evolution-mail.ui
@@ -16,6 +16,11 @@
       </placeholder>
     </menu>
     <menu action='view-menu'>
+      <menu action='layout-menu'>
+        <placeholder name='view-layout-custom-menus'>
+          <menuitem action='mail-to-do-bar'/>
+        </placeholder>
+      </menu>
       <placeholder name='view-custom-menus'>
         <menu action='mail-preview-menu'>
           <menuitem action='mail-preview'/>
diff --git a/data/ui/evolution-shell.ui b/data/ui/evolution-shell.ui
index 9035fb2..6b34107 100644
--- a/data/ui/evolution-shell.ui
+++ b/data/ui/evolution-shell.ui
@@ -47,6 +47,7 @@
         <menuitem action='show-toolbar'/>
         <menuitem action='show-taskbar'/>
         <menuitem action='show-sidebar'/>
+        <placeholder name='view-layout-custom-menus'/>
       </menu>
       <placeholder name='view-custom-menus'/>
       <menu action='switcher-menu'>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4019a48..acc3b3b 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 001e36b..09cf401 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 53edf5c..facb874 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 0000000..8943aed
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-config.h"
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <libedataserverui/libedataserverui.h>
+#include <libecal/libecal.h>
+
+#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, "<b>%s</b>", 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 ("<span size=\"xx-small\">%s</span> %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>%s</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 ("<b>%s</b>", _("Today"));
+                               } else if (ii == 1) {
+                                       markup = g_markup_printf_escaped ("<b>%s</b>", _("Tomorrow"));
+                               } else {
+                                       gchar *date;
+
+                                       date = e_datetime_format_format_tm ("calendar", "table", 
DTFormatKindDate, &tm);
+                                       markup = g_markup_printf_escaped ("<b>%s</b>", 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 0000000..8ce7786
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef E_TO_DO_PANE_H
+#define E_TO_DO_PANE_H
+
+#include <gtk/gtk.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include <shell/e-shell-view.h>
+
+/* 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 ca27f5a..9205b62 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 24d3708..60ac7a3 100644
--- a/src/modules/mail/e-mail-shell-content.c
+++ b/src/modules/mail/e-mail-shell-content.c
@@ -26,6 +26,8 @@
 
 #include <e-util/e-util-private.h>
 
+#include "calendar/gui/e-to-do-pane.h"
+
 #include <mail/e-mail-paned-view.h>
 #include <mail/e-mail-reader.h>
 #include <mail/e-mail-reader-utils.h>
@@ -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 db9ef85..17e7cd7 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 47d31af..1743a31 100644
--- a/src/modules/mail/e-mail-shell-view-actions.c
+++ b/src/modules/mail/e-mail-shell-view-actions.c
@@ -277,6 +277,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,
                                   EActivity *activity)
@@ -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 5459f73..f3fbd32 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) \



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]