diff options
Diffstat (limited to 'applets/clock/calendar-window.c')
-rw-r--r-- | applets/clock/calendar-window.c | 2125 |
1 files changed, 2125 insertions, 0 deletions
diff --git a/applets/clock/calendar-window.c b/applets/clock/calendar-window.c new file mode 100644 index 00000000..d675f492 --- /dev/null +++ b/applets/clock/calendar-window.c @@ -0,0 +1,2125 @@ +/* + * calendar-window.c: toplevel window containing a calendar and + * tasks/appointments + * + * Copyright (C) 2007 Vincent Untz <[email protected]> + * + * 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; either version 2 of the + * License, or (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: + * Vincent Untz <[email protected]> + * + * Most of the original code comes from clock.c + */ + + /* + * Evolution calendar integration TODO: + * + Fix treeview scrolling and sizing + * + Tooltips for tasks/appointments + * + Do everything backwards if the clock is on the bottom + * + Double clicking appointments/tasks should open them in evo + * + Consider using different colours for different sources + * + Consider doing a GtkMenu tearoff type thing + */ + +#include <config.h> + +#include <string.h> + +#include <glib/gi18n.h> +#include <mateconf/mateconf-client.h> + +#define MATE_DESKTOP_USE_UNSTABLE_API +#include <libmate/mate-desktop-utils.h> + +#include "calendar-window.h" + +#include "clock.h" +#include "clock-utils.h" +#include "clock-typebuiltins.h" +#ifdef HAVE_LIBECAL +#include "calendar-client.h" +#endif + +#define CALENDAR_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_WINDOW, CalendarWindowPrivate)) + +#define KEY_LOCATIONS_EXPANDED "expand_locations" +#ifdef HAVE_LIBECAL +/* For the following value, take into account the KEY_* that are not inside this #ifdef! */ +# define N_CALENDAR_WINDOW_MATECONF_PREFS 5 +# define KEY_APPOINTMENTS_EXPANDED "expand_appointments" +# define KEY_BIRTHDAYS_EXPANDED "expand_birthdays" +# define KEY_TASKS_EXPANDED "expand_tasks" +# define KEY_WEATHER_EXPANDED "expand_weather" + +# define KEY_CALENDAR_APP "/desktop/mate/applications/calendar" +# define KEY_TASKS_APP "/desktop/mate/applications/tasks" +#else +# define N_CALENDAR_WINDOW_MATECONF_PREFS 1 +#endif + +enum { + EDIT_LOCATIONS, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +struct _CalendarWindowPrivate { + GtkWidget *calendar; + + char *prefs_dir; + + gboolean invert_order; + gboolean show_weeks; + time_t *current_time; + + GtkWidget *locations_list; + +#ifdef HAVE_LIBECAL + ClockFormat time_format; + + CalendarClient *client; + + GtkWidget *appointment_list; + GtkWidget *birthday_list; + GtkWidget *weather_list; + GtkWidget *task_list; + + GtkListStore *appointments_model; + GtkListStore *tasks_model; + + GtkTreeSelection *previous_selection; + + GtkTreeModelFilter *appointments_filter; + GtkTreeModelFilter *birthdays_filter; + GtkTreeModelFilter *tasks_filter; + GtkTreeModelFilter *weather_filter; + + MateConfClient *mateconfclient; +#endif /* HAVE_LIBECAL */ +}; + +G_DEFINE_TYPE (CalendarWindow, calendar_window, GTK_TYPE_WINDOW) + +enum { + PROP_0, + PROP_INVERTORDER, + PROP_SHOWWEEKS, +#ifdef HAVE_LIBECAL + PROP_TIMEFORMAT, +#endif + PROP_CURRENTTIMEP, + PROP_PREFSDIR +}; + +static time_t *calendar_window_get_current_time_p (CalendarWindow *calwin); +static void calendar_window_set_current_time_p (CalendarWindow *calwin, + time_t *current_time); +static const char *calendar_window_get_prefs_dir (CalendarWindow *calwin); +static void calendar_window_set_prefs_dir (CalendarWindow *calwin, + const char *prefs_dir); +static GtkWidget * create_hig_frame (CalendarWindow *calwin, + const char *title, + const char *button_label, + const char *key, + GCallback callback); + +#ifdef HAVE_LIBECAL + +static void +clock_launch_calendar_tasks_app (CalendarWindow *calwin, + const char *key_program, + const char *argument) +{ + char **argv; + int argc; + char *key; + char *program; + gboolean terminal; + char *command_line; + GdkScreen *screen; + GError *error; + gboolean result; + + key = g_strdup_printf ("%s%s", key_program, "/exec"); + program = mateconf_client_get_string (calwin->priv->mateconfclient, + key, NULL); + g_free (key); + + key = g_strdup_printf ("%s%s", key_program, "/needs_term"); + terminal = mateconf_client_get_bool (calwin->priv->mateconfclient, + key, NULL); + g_free (key); + + if (program == NULL) { + g_printerr ("Cannot launch calendar/tasks application: key not set\n"); + return; + } + + command_line = g_find_program_in_path (program); + if (command_line == NULL) { + g_printerr ("Cannot launch calendar/tasks application: %s in path\n", program); + g_free (program); + return; + } + g_free (command_line); + + if (argument) { + argc = 2; + argv = g_new0 (char *, 3); + argv[1] = g_strdup (argument); + } else { + argc = 1; + argv = g_new0 (char *, 2); + } + argv[0] = program; /* no strdup, and it will get freed for free */ + + screen = gtk_widget_get_screen (calwin->priv->calendar); + error = NULL; + + if (terminal) + mate_desktop_prepend_terminal_to_vector (&argc, &argv); + + result = gdk_spawn_on_screen (screen, + NULL, /* working directory */ + argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH, + NULL, /* child setup func */ + NULL, /* user data */ + NULL, /* child pid */ + &error); + + if (!result) { + g_printerr ("Cannot launch calendar/tasks application: %s\n", + error->message); + g_error_free (error); + } + + g_strfreev (argv); +} + +static void +clock_launch_calendar_app (CalendarWindow *calwin, + const char *argument) +{ + clock_launch_calendar_tasks_app (calwin, KEY_CALENDAR_APP, argument); +} + +static void +clock_launch_tasks_app (CalendarWindow *calwin, + const char *argument) +{ + clock_launch_calendar_tasks_app (calwin, KEY_TASKS_APP, argument); +} + +static void +update_frame_visibility (GtkWidget *frame, + GtkTreeModel *model) +{ + GtkTreeIter iter; + gboolean model_empty; + + if (!frame) + return; + + model_empty = !gtk_tree_model_get_iter_first (model, &iter); + + if (model_empty) + gtk_widget_hide (frame); + else + gtk_widget_show (frame); +} + +enum { + APPOINTMENT_COLUMN_UID, + APPOINTMENT_COLUMN_URI, + APPOINTMENT_COLUMN_SUMMARY, + APPOINTMENT_COLUMN_DESCRIPTION, + APPOINTMENT_COLUMN_START_TIME, + APPOINTMENT_COLUMN_START_TEXT, + APPOINTMENT_COLUMN_END_TIME, + APPOINTMENT_COLUMN_ALL_DAY, + APPOINTMENT_COLUMN_PIXBUF, + N_APPOINTMENT_COLUMNS +}; + +enum { + TASK_COLUMN_UID, + TASK_COLUMN_SUMMARY, + TASK_COLUMN_DESCRIPTION, + TASK_COLUMN_START_TIME, + TASK_COLUMN_DUE_TIME, + TASK_COLUMN_PERCENT_COMPLETE, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, + TASK_COLUMN_COMPLETED, + TASK_COLUMN_COMPLETED_TIME, + TASK_COLUMN_OVERDUE_ATTR, + TASK_COLUMN_COLOR, + TASK_COLUMN_PRIORITY, + N_TASK_COLUMNS +}; + +static char * +format_time (ClockFormat format, + time_t t, + guint year, + guint month, + guint day) +{ + struct tm *tm; + char *time_format; + char result [256] = { 0, }; + + if (!t) + return NULL; + + tm = localtime (&t); + if (!tm) + return NULL; + + if (year == (tm->tm_year + 1900) && + month == tm->tm_mon && + day == tm->tm_mday) { + if (format == CLOCK_FORMAT_12) + /* Translators: This is a strftime format string. + * It is used to display the time in 12-hours format + * (eg, like in the US: 8:10 am). The %p expands to + * am/pm. */ + time_format = g_locale_from_utf8 (_("%l:%M %p"), -1, NULL, NULL, NULL); + else + /* Translators: This is a strftime format string. + * It is used to display the time in 24-hours format + * (eg, like in France: 20:10). */ + time_format = g_locale_from_utf8 (_("%H:%M"), -1, NULL, NULL, NULL); + } + else { + /* Translators: This is a strftime format string. + * It is used to display the start date of an appointment, in + * the most abbreviated way possible. */ + time_format = g_locale_from_utf8 (_("%b %d"), -1, NULL, NULL, NULL); + } + + strftime (result, sizeof (result), time_format, tm); + g_free (time_format); + + return g_locale_to_utf8 (result, -1, NULL, NULL, NULL); +} + +static void +handle_tasks_changed (CalendarWindow *calwin) +{ + GSList *events, *l; + + gtk_list_store_clear (calwin->priv->tasks_model); + + events = calendar_client_get_events (calwin->priv->client, + CALENDAR_EVENT_TASK); + for (l = events; l; l = l->next) { + CalendarTask *task = l->data; + GtkTreeIter iter; + char *percent_complete_text; + + g_assert (CALENDAR_EVENT (task)->type == CALENDAR_EVENT_TASK); + + /* FIXME: should this format be locale specific ? */ + percent_complete_text = g_strdup_printf ("%d%%", task->percent_complete); + + gtk_list_store_append (calwin->priv->tasks_model, &iter); + gtk_list_store_set (calwin->priv->tasks_model, &iter, + TASK_COLUMN_UID, task->uid, + TASK_COLUMN_SUMMARY, task->summary, + TASK_COLUMN_DESCRIPTION, task->description, + TASK_COLUMN_START_TIME, (gint64)task->start_time, + TASK_COLUMN_DUE_TIME, (gint64)task->due_time, + TASK_COLUMN_PERCENT_COMPLETE, task->percent_complete, + TASK_COLUMN_PERCENT_COMPLETE_TEXT, percent_complete_text, + TASK_COLUMN_COMPLETED, task->percent_complete == 100, + TASK_COLUMN_COMPLETED_TIME, (gint64)task->completed_time, + TASK_COLUMN_COLOR, task->color_string, + TASK_COLUMN_PRIORITY, task->priority, + -1); + + g_free (percent_complete_text); + calendar_event_free (CALENDAR_EVENT (task)); + } + g_slist_free (events); + + update_frame_visibility (calwin->priv->task_list, + GTK_TREE_MODEL (calwin->priv->tasks_filter)); +} + +static void +handle_task_completed_toggled (CalendarWindow *calwin, + const char *path_str, + GtkCellRendererToggle *cell) +{ + GtkTreePath *child_path, *path; + GtkTreeIter iter; + char *uid; + gboolean task_completed; + guint percent_complete; + + path = gtk_tree_path_new_from_string (path_str); + child_path = gtk_tree_model_filter_convert_path_to_child_path (calwin->priv->tasks_filter, path); + gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, child_path); + gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, + TASK_COLUMN_UID, &uid, + TASK_COLUMN_COMPLETED, &task_completed, + TASK_COLUMN_PERCENT_COMPLETE, &percent_complete, + -1); + + task_completed = !task_completed; + percent_complete = task_completed ? 100 : 0; + + calendar_client_set_task_completed (calwin->priv->client, + uid, + task_completed, + percent_complete); + + g_free (uid); + gtk_tree_path_free (path); + gtk_tree_path_free (child_path); +} + +static void +handle_task_percent_complete_edited (CalendarWindow *calwin, + const char *path_str, + const char *text, + GtkCellRendererText *cell) +{ + GtkTreePath *child_path, *path; + GtkTreeIter iter; + char *uid; + int percent_complete; + char *error = NULL, *text_copy; + + path = gtk_tree_path_new_from_string (path_str); + child_path = gtk_tree_model_filter_convert_path_to_child_path (calwin->priv->tasks_filter, path); + gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, child_path); + gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, TASK_COLUMN_UID, &uid, + -1); + + text_copy = g_strdup (text); + text_copy = g_strdelimit (text_copy, "%", ' '); + text_copy = g_strstrip (text_copy); + percent_complete = (int) g_strtod (text_copy, &error); + if (!error || !error [0]) { + gboolean task_completed; + + percent_complete = CLAMP (percent_complete, 0, 100); + task_completed = (percent_complete == 100); + + calendar_client_set_task_completed (calwin->priv->client, + uid, + task_completed, + percent_complete); + } + + g_free (uid); + g_free (text_copy); + gtk_tree_path_free (path); + gtk_tree_path_free (child_path); +} + +static gboolean +is_appointment (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *uri; + + gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_URI, &uri, -1); + if (uri) + return (g_ascii_strcasecmp (uri, "file") == 0 || + g_ascii_strcasecmp (uri, "webcal") == 0 || + g_ascii_strcasecmp (uri, "caldav") == 0 || + g_ascii_strcasecmp (uri, "exchange") == 0 || + g_ascii_strcasecmp (uri, "groupwise") == 0 || + g_ascii_strcasecmp (uri, "google") == 0); + return FALSE; +} + +static gboolean +is_birthday (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *uri; + + gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_URI, &uri, -1); + if (uri) + return (g_ascii_strcasecmp (uri, "contacts") == 0); + return FALSE; +} + +static gboolean +is_weather (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *uri; + + gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_URI, &uri, -1); + if (uri) + return (g_ascii_strcasecmp (uri, "weather") == 0); + return FALSE; +} + +static gboolean +filter_out_tasks (GtkTreeModel *model, + GtkTreeIter *iter, + CalendarWindow *calwin) +{ + gint64 start_time64; + gint64 completed_time64; + time_t start_time; + time_t completed_time; + time_t one_day_ago; + gboolean visible; + + gtk_tree_model_get (model, iter, + TASK_COLUMN_START_TIME, &start_time64, + TASK_COLUMN_COMPLETED_TIME, &completed_time64, + -1); + start_time = start_time64; + completed_time = completed_time64; + + one_day_ago = *(calwin->priv->current_time) - (24 * 60 * 60); + + visible = !start_time || start_time <= *(calwin->priv->current_time); + if (visible) + visible = !completed_time || completed_time >= one_day_ago; + + return visible; +} + +static void +modify_task_text_attributes (GtkTreeModel *model, + GtkTreeIter *iter, + GValue *value, + gint column, + CalendarWindow *calwin) +{ + gint64 due_time64; + time_t due_time; + PangoAttrList *attr_list; + PangoAttribute *attr; + GtkTreeIter child_iter; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), + &child_iter, + iter); + + if (column != TASK_COLUMN_OVERDUE_ATTR) { + memset (value, 0, sizeof (GValue)); + gtk_tree_model_get_value (GTK_TREE_MODEL (calwin->priv->tasks_model), + &child_iter, column, value); + + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_model), + &child_iter, TASK_COLUMN_DUE_TIME, &due_time64, + -1); + due_time = due_time64; + + if (due_time && due_time > *(calwin->priv->current_time)) + return; + + attr_list = pango_attr_list_new (); + + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + attr->start_index = 0; + attr->end_index = G_MAXINT; + pango_attr_list_insert (attr_list, attr); + + g_value_take_boxed (value, attr_list); +} + +static gboolean +task_activated_cb (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + CalendarWindow *calwin) +{ + GtkTreeIter iter; + GtkTreePath *child_path; + char *uid; + char *argument; + + child_path = gtk_tree_model_filter_convert_path_to_child_path (calwin->priv->tasks_filter, + path); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, child_path); + gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_model), + &iter, TASK_COLUMN_UID, &uid, -1); + + argument = g_strdup_printf ("task:%s", uid); + + clock_launch_tasks_app (calwin, argument); + + g_free (argument); + g_free (uid); + gtk_tree_path_free (child_path); + + return TRUE; +} + +static void +set_renderer_pixbuf_color_by_column (GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gint column_number) +{ + char *color_string; + GdkPixbuf *pixbuf = NULL; + GdkColor color; + + gtk_tree_model_get (model, iter, column_number, &color_string, -1); + + if (color_string && gdk_color_parse (color_string, &color)) { + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 16, 16); + /* GdkColor has 16 bits per color, gdk_pixbuf only uses 8 bits + * per color. So just drop the least significant parts */ + gdk_pixbuf_fill (pixbuf, + (color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00)); + + g_object_set (renderer, "visible", pixbuf != NULL, "pixbuf", pixbuf, NULL); + + if (pixbuf) + g_object_unref (pixbuf); + + g_free (color_string); + } +} + +static void +set_renderer_pixbuf_pixmap (GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + const char *iconpath) +{ + GdkPixbuf *pixbuf = NULL; + GError *error = NULL; + + if (!g_file_test (iconpath, G_FILE_TEST_IS_REGULAR)) { + g_printerr ("File '%s' does not exist.\n", iconpath); + return; + } + + pixbuf = gdk_pixbuf_new_from_file (iconpath, &error); + if (error) { + g_printerr ("Cannot load '%s': %s\n", + iconpath, error->message); + g_error_free (error); + return; + } + + g_object_set (renderer, + "visible", pixbuf != NULL, + "pixbuf", pixbuf, + NULL); + + if (pixbuf) + g_object_unref (pixbuf); +} + +static void +set_renderer_pixbuf_pixmap_for_bday (GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gint data_column) +{ + const gchar *path = NULL; + gchar *type = NULL; + + gtk_tree_model_get (model, iter, data_column, &type, -1); + if (!type) + return; + + /* type should be in format like this: + * pas-id-4121A93E00000001-anniversary + * pas-id-41112AF900000003-birthday + * ... + */ + if (g_strrstr (type, "birthday") != NULL) + path = CLOCK_EDS_ICONDIR G_DIR_SEPARATOR_S "category_birthday_16.png"; + else if (g_strrstr (type, "anniversary") != NULL) + path = CLOCK_EDS_ICONDIR G_DIR_SEPARATOR_S "category_gifts_16.png"; + else + path = CLOCK_EDS_ICONDIR G_DIR_SEPARATOR_S "category_miscellaneous_16.png"; + + g_free (type); + + set_renderer_pixbuf_pixmap (renderer, model, iter, path); +} + +static void +set_renderer_pixbuf_pixmap_for_weather (GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + set_renderer_pixbuf_pixmap (renderer, model, iter, + CLOCK_EDS_ICONDIR G_DIR_SEPARATOR_S "category_holiday_16.png"); +} + +static void +task_pixbuf_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + set_renderer_pixbuf_color_by_column (renderer, + model, + iter, + TASK_COLUMN_COLOR); +} + +static void +appointment_pixbuf_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + set_renderer_pixbuf_color_by_column (renderer, + model, + iter, + APPOINTMENT_COLUMN_PIXBUF); +} +static void +birthday_pixbuf_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + + /* APPOINTMENT_COLUMN_UID contains data to select between + * anniversary or birthday + */ + set_renderer_pixbuf_pixmap_for_bday (renderer, + model, + iter, + APPOINTMENT_COLUMN_UID); +} +static void +weather_pixbuf_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + + set_renderer_pixbuf_pixmap_for_weather (renderer, + model, + iter); +} + +static int +compare_tasks (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gboolean done_a, done_b; + int priority_a, priority_b; + + gtk_tree_model_get (model, a, + TASK_COLUMN_COMPLETED, &done_a, + TASK_COLUMN_PRIORITY, &priority_a, + -1); + gtk_tree_model_get (model, b, + TASK_COLUMN_COMPLETED, &done_b, + TASK_COLUMN_PRIORITY, &priority_b, + -1); + + /* Always sort completed tasks last */ + if (done_a != done_b) + return done_a ? -1 : 1; + + /* We change undefined priorities so they appear as "Normal" */ + if (priority_a <= 0) + priority_a = 5; + if (priority_b <= 0) + priority_b = 5; + + /* We'll just use the ordering of the priority values. */ + if (priority_a < priority_b) + return -1; + else if (priority_a > priority_b) + return 1; + else { + gint64 due_time_a64, due_time_b64; + time_t due_time_a, due_time_b; + + gtk_tree_model_get (model, a, + TASK_COLUMN_DUE_TIME, &due_time_a64, -1); + gtk_tree_model_get (model, b, + TASK_COLUMN_DUE_TIME, &due_time_b64, -1); + due_time_a = due_time_a64; + due_time_b = due_time_b64; + + if (due_time_a < due_time_b) + return -1; + else if (due_time_a > due_time_b) + return 1; + else { + char *summary_a, *summary_b; + int res; + + gtk_tree_model_get (model, a, TASK_COLUMN_SUMMARY, &summary_a, -1); + gtk_tree_model_get (model, b, TASK_COLUMN_SUMMARY, &summary_b, -1); + + res = g_utf8_collate (summary_a ? summary_a: "", + summary_b ? summary_b: ""); + + g_free (summary_a); + g_free (summary_b); + + return res; + } + } +} + +static void +calendar_window_tree_selection_changed (GtkTreeSelection *selection, + CalendarWindow *calwin) +{ + if (selection == calwin->priv->previous_selection) + return; + + if (calwin->priv->previous_selection) { + g_signal_handlers_block_by_func (calwin->priv->previous_selection, + calendar_window_tree_selection_changed, + calwin); + gtk_tree_selection_unselect_all (calwin->priv->previous_selection); + g_signal_handlers_unblock_by_func (calwin->priv->previous_selection, + calendar_window_tree_selection_changed, + calwin); + } + + calwin->priv->previous_selection = selection; +} + +static void +edit_tasks (CalendarWindow *calwin) +{ + clock_launch_tasks_app (calwin, NULL); +} + +static GtkWidget * +create_task_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + GtkWidget *list; + GtkWidget *view; + GtkWidget *scrolled; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + list = create_hig_frame (calwin, + _("Tasks"), _("Edit"), + KEY_TASKS_EXPANDED, + G_CALLBACK (edit_tasks)); + + *scrolled_window = scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + /* we show the widget before adding to the container, since adding to + * the container changes the visibility depending on the state of the + * expander */ + gtk_widget_show (scrolled); + gtk_container_add (GTK_CONTAINER (list), scrolled); + + g_assert (calwin->priv->tasks_model != NULL); + + *tree_view = view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (calwin->priv->tasks_filter)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + g_signal_connect (view, "row-activated", + G_CALLBACK (task_activated_cb), calwin); + + /* Source color */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (column, cell, + (GtkTreeCellDataFunc) task_pixbuf_cell_data_func, + NULL, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Completed toggle */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_toggle_new (); + g_object_set (cell, + "activatable", TRUE, + NULL); + g_signal_connect_swapped (cell, "toggled", + G_CALLBACK (handle_task_completed_toggled), + calwin); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "active", TASK_COLUMN_COMPLETED); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Percent complete */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "editable", TRUE, + NULL); + g_signal_connect_swapped (cell, "edited", + G_CALLBACK (handle_task_percent_complete_edited), + calwin); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", TASK_COLUMN_PERCENT_COMPLETE_TEXT); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + /* Summary */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", TASK_COLUMN_SUMMARY, + "strikethrough", TASK_COLUMN_COMPLETED, + "attributes", TASK_COLUMN_OVERDUE_ATTR, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + g_signal_connect (selection, "changed", + G_CALLBACK (calendar_window_tree_selection_changed), + calwin); + + gtk_container_add (GTK_CONTAINER (scrolled), view); + + gtk_widget_show (view); + + return list; +} + +static void +mark_day_on_calendar (CalendarClient *client, + guint day, + CalendarWindow *calwin) +{ + gtk_calendar_mark_day (GTK_CALENDAR (calwin->priv->calendar), day); +} + +static void +handle_appointments_changed (CalendarWindow *calwin) +{ + GSList *events, *l; + guint year, month, day; + + if (calwin->priv->calendar) { + gtk_calendar_clear_marks (GTK_CALENDAR (calwin->priv->calendar)); + + calendar_client_foreach_appointment_day (calwin->priv->client, + (CalendarDayIter) mark_day_on_calendar, + calwin); + } + + gtk_list_store_clear (calwin->priv->appointments_model); + + calendar_client_get_date (calwin->priv->client, &year, &month, &day); + + events = calendar_client_get_events (calwin->priv->client, + CALENDAR_EVENT_APPOINTMENT); + for (l = events; l; l = l->next) { + CalendarAppointment *appointment = l->data; + GtkTreeIter iter; + char *start_text; + + g_assert (CALENDAR_EVENT (appointment)->type == CALENDAR_EVENT_APPOINTMENT); + + if (appointment->is_all_day) + start_text = g_strdup (_("All Day")); + else + start_text = format_time (calwin->priv->time_format, + appointment->start_time, + year, month, day); + + + gtk_list_store_append (calwin->priv->appointments_model, + &iter); + gtk_list_store_set (calwin->priv->appointments_model, &iter, + APPOINTMENT_COLUMN_UID, appointment->uid, + APPOINTMENT_COLUMN_URI, appointment->uri, + APPOINTMENT_COLUMN_SUMMARY, appointment->summary, + APPOINTMENT_COLUMN_DESCRIPTION, appointment->description, + APPOINTMENT_COLUMN_START_TIME, (gint64)appointment->start_time, + APPOINTMENT_COLUMN_START_TEXT, start_text, + APPOINTMENT_COLUMN_END_TIME, (gint64)appointment->end_time, + APPOINTMENT_COLUMN_ALL_DAY, appointment->is_all_day, + APPOINTMENT_COLUMN_PIXBUF, appointment->color_string, + -1); + + g_free (start_text); + calendar_event_free (CALENDAR_EVENT (appointment)); + } + g_slist_free (events); + + update_frame_visibility (calwin->priv->appointment_list, + GTK_TREE_MODEL (calwin->priv->appointments_filter)); + update_frame_visibility (calwin->priv->birthday_list, + GTK_TREE_MODEL (calwin->priv->birthdays_filter)); + update_frame_visibility (calwin->priv->weather_list, + GTK_TREE_MODEL (calwin->priv->weather_filter)); +} + +static GtkWidget * +create_list_for_appointment_model (CalendarWindow *calwin, + const char *label, + GtkTreeModelFilter **filter, + GtkTreeModelFilterVisibleFunc is_for_filter, + GtkTreeCellDataFunc set_pixbuf_cell, + gboolean show_start, + GtkWidget **tree_view, + GtkWidget **scrolled_window, + const char *key, + GCallback callback) +{ + GtkWidget *list; + GtkWidget *view; + GtkWidget *scrolled; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + + list = create_hig_frame (calwin, label, _("Edit"), key, callback); + + *scrolled_window = scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + /* we show the widget before adding to the container, since adding to + * the container changes the visibility depending on the state of the + * expander */ + gtk_widget_show (scrolled); + gtk_container_add (GTK_CONTAINER (list), scrolled); + + g_assert (calwin->priv->appointments_model != NULL); + + if (!*filter) { + *filter = + GTK_TREE_MODEL_FILTER ( + gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->appointments_model), + NULL)); + gtk_tree_model_filter_set_visible_func ( + *filter, + (GtkTreeModelFilterVisibleFunc) is_for_filter, + calwin, + NULL); + } + + *tree_view = view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (*filter)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + /* Icon */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (column, cell, + (GtkTreeCellDataFunc) set_pixbuf_cell, + NULL, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + if (show_start) { + /* Start time */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", APPOINTMENT_COLUMN_START_TEXT); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + } + + /* Summary */ + column = gtk_tree_view_column_new (); + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", APPOINTMENT_COLUMN_SUMMARY); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + g_signal_connect (selection, "changed", + G_CALLBACK (calendar_window_tree_selection_changed), + calwin); + + gtk_container_add (GTK_CONTAINER (scrolled), view); + + gtk_widget_show (view); + + return list; +} + +static void +edit_appointments (CalendarWindow *calwin) +{ + clock_launch_calendar_app (calwin, NULL); +} + +static GtkWidget * +create_appointment_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + return create_list_for_appointment_model ( + calwin, + _("Appointments"), + &calwin->priv->appointments_filter, + is_appointment, + appointment_pixbuf_cell_data_func, + TRUE, + tree_view, + scrolled_window, + KEY_APPOINTMENTS_EXPANDED, + G_CALLBACK (edit_appointments)); +} + +static void +edit_birthdays (CalendarWindow *calwin) +{ + clock_launch_calendar_app (calwin, NULL); +} + +static GtkWidget * +create_birthday_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + /* FIXME: Figure out how to get rid of useless localized message in front of the summary */ + return create_list_for_appointment_model ( + calwin, + _("Birthdays and Anniversaries"), + &calwin->priv->birthdays_filter, + is_birthday, + birthday_pixbuf_cell_data_func, + FALSE, + tree_view, + scrolled_window, + KEY_BIRTHDAYS_EXPANDED, + G_CALLBACK (edit_birthdays)); +} + +static void +edit_weather (CalendarWindow *calwin) +{ + clock_launch_calendar_app (calwin, NULL); +} + + +static GtkWidget * +create_weather_list (CalendarWindow *calwin, + GtkWidget **tree_view, + GtkWidget **scrolled_window) +{ + return create_list_for_appointment_model ( + calwin, + _("Weather Information"), + &calwin->priv->weather_filter, + is_weather, + weather_pixbuf_cell_data_func, + FALSE, + tree_view, + scrolled_window, + KEY_WEATHER_EXPANDED, + G_CALLBACK (edit_weather)); +} + +static void +calendar_window_create_tasks_model (CalendarWindow *calwin) +{ + GType column_types [N_TASK_COLUMNS] = { + G_TYPE_STRING, /* uid */ + G_TYPE_STRING, /* summary */ + G_TYPE_STRING, /* description */ + G_TYPE_INT64, /* start time */ + G_TYPE_INT64, /* due time */ + G_TYPE_UINT, /* percent complete */ + G_TYPE_STRING, /* percent complete text */ + G_TYPE_BOOLEAN, /* completed */ + G_TYPE_INT64, /* completed time */ + PANGO_TYPE_ATTR_LIST, /* summary text attributes */ + G_TYPE_STRING, /* color */ + G_TYPE_INT /* priority */ + }; + + calwin->priv->tasks_model = gtk_list_store_newv (N_TASK_COLUMNS, + column_types); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (calwin->priv->tasks_model), + TASK_COLUMN_PRIORITY, + compare_tasks, + NULL, NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (calwin->priv->tasks_model), + TASK_COLUMN_PRIORITY, + GTK_SORT_ASCENDING); + + calwin->priv->tasks_filter = GTK_TREE_MODEL_FILTER ( + gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->tasks_model), + NULL)); + gtk_tree_model_filter_set_visible_func ( + calwin->priv->tasks_filter, + (GtkTreeModelFilterVisibleFunc) filter_out_tasks, + calwin, + NULL); + gtk_tree_model_filter_set_modify_func ( + calwin->priv->tasks_filter, + N_TASK_COLUMNS, + column_types, + (GtkTreeModelFilterModifyFunc) modify_task_text_attributes, + calwin, + NULL); +} + +static void +calendar_window_create_appointments_model (CalendarWindow *calwin) +{ + calwin->priv->appointments_model = + gtk_list_store_new (N_APPOINTMENT_COLUMNS, + G_TYPE_STRING, /* uid */ + G_TYPE_STRING, /* uri */ + G_TYPE_STRING, /* summary */ + G_TYPE_STRING, /* description */ + G_TYPE_INT64, /* start time */ + G_TYPE_STRING, /* start time text */ + G_TYPE_INT64, /* end time */ + G_TYPE_BOOLEAN, /* all day */ + G_TYPE_STRING); /* color */ + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (calwin->priv->appointments_model), + APPOINTMENT_COLUMN_START_TIME, + GTK_SORT_ASCENDING); +} + +static void +calendar_day_activated (GtkCalendar *calendar, + CalendarWindow *calwin) +{ + unsigned int day; + unsigned int month; + unsigned int year; + time_t date; + struct tm utc_date_tm; + struct tm local_date_tm = { 0, }; + char *argument; + + gtk_calendar_get_date (calendar, &year, &month, &day); + + local_date_tm.tm_mday = (int) day; + local_date_tm.tm_mon = (int) month; + local_date_tm.tm_year = (int) year - 1900; + local_date_tm.tm_isdst = -1; + + /* convert the local date picked in the calendar to broken-down UTC */ + date = mktime (&local_date_tm); + gmtime_r (&date, &utc_date_tm); + + /* FIXME: once bug 409200 is fixed, we'll have to make this hh:mm:ss + * instead of hhmmss */ + argument = g_strdup_printf ("calendar:///?startdate=" + "%.4d%.2d%.2dT%.2d%.2d%.2dZ", + utc_date_tm.tm_year + 1900, + utc_date_tm.tm_mon + 1, + utc_date_tm.tm_mday, + utc_date_tm.tm_hour, + utc_date_tm.tm_min, + 0); + + clock_launch_calendar_app (calwin, argument); + + g_free (argument); +} + +static void +calendar_day_selected (GtkCalendar *calendar, + CalendarWindow *calwin) +{ + guint day; + + gtk_calendar_get_date (calendar, NULL, NULL, &day); + + calendar_client_select_day (calwin->priv->client, day); + + handle_appointments_changed (calwin); + handle_tasks_changed (calwin); +} + +static void +calendar_month_selected (GtkCalendar *calendar, + CalendarWindow *calwin) +{ + guint year, month; + + gtk_calendar_get_date (calendar, &year, &month, NULL); + + calendar_client_select_month (calwin->priv->client, month, year); + + handle_appointments_changed (calwin); + handle_tasks_changed (calwin); +} + +typedef struct +{ + GtkWidget *calendar; + GtkWidget *tree; +} ConstraintData; + +static void +constrain_list_size (GtkWidget *widget, + GtkRequisition *requisition, + ConstraintData *constraint) +{ + GtkRequisition req; + int screen_h; + int max_height; + + /* constrain width to the calendar width */ + gtk_widget_size_request (constraint->calendar, &req); + requisition->width = MIN (requisition->width, req.width); + + screen_h = gdk_screen_get_height (gtk_widget_get_screen (widget)); + /* constrain height to be the tree height up to a max */ + max_height = (screen_h - req.height) / 3; + gtk_widget_size_request (constraint->tree, &req); + + requisition->height = MIN (req.height, max_height); + requisition->height += 2 * gtk_widget_get_style (widget)->ythickness; +} + +static void +setup_list_size_constraint (GtkWidget *widget, + GtkWidget *calendar, + GtkWidget *tree) +{ + ConstraintData *constraint; + + constraint = g_new0 (ConstraintData, 1); + constraint->calendar = calendar; + constraint->tree = tree; + + g_signal_connect_data (widget, "size-request", + G_CALLBACK (constrain_list_size), constraint, + (GClosureNotify) g_free, 0); +} + +static void +expander_activated (GtkExpander *expander, + CalendarWindow *calwin) +{ + const char *key; + + key = (const gchar*)g_object_get_data (G_OBJECT (expander), "mateconf-key"); + + if (mateconf_client_key_is_writable (calwin->priv->mateconfclient, + key, NULL)) { + mateconf_client_set_bool (calwin->priv->mateconfclient, key, + gtk_expander_get_expanded (expander), + NULL); + } +} + +static void +expanded_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + GtkExpander *expander) +{ + gboolean value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_BOOL) + return; + + value = mateconf_value_get_bool (entry->value); + + gtk_expander_set_expanded (expander, value); +} + +static void +remove_listener (gpointer data) +{ + MateConfClient *client; + + client = mateconf_client_get_default (); + mateconf_client_notify_remove (client, GPOINTER_TO_UINT (data)); + g_object_unref (client); +} + +static void +connect_expander_with_mateconf (CalendarWindow *calwin, + GtkWidget *expander, + const char *relative_key) +{ + char *key; + gboolean expanded; + guint listener; + + key = g_strdup_printf ("%s/%s", + calwin->priv->prefs_dir, relative_key); + + g_object_set_data_full (G_OBJECT (expander), "mateconf-key", (gpointer)key, g_free); + + expanded = mateconf_client_get_bool (calwin->priv->mateconfclient, key, + NULL); + gtk_expander_set_expanded (GTK_EXPANDER (expander), expanded); + + g_signal_connect_after (expander, "activate", + G_CALLBACK (expander_activated), + calwin); + + listener = mateconf_client_notify_add ( + calwin->priv->mateconfclient, key, + (MateConfClientNotifyFunc) expanded_changed, + expander, NULL, NULL); + + g_object_set_data_full (G_OBJECT (expander), "listener-id", + GUINT_TO_POINTER (listener), remove_listener); +} +#endif /* HAVE_LIBECAL */ + +static void +calendar_window_pack_pim (CalendarWindow *calwin, + GtkWidget *vbox) +{ +#ifdef HAVE_LIBECAL + GtkWidget *list; + GtkWidget *tree_view; + GtkWidget *scrolled_window; + guint year, month, day; + + calendar_window_create_tasks_model (calwin); + calendar_window_create_appointments_model (calwin); + + list = create_task_list (calwin, &tree_view, &scrolled_window); + setup_list_size_constraint (scrolled_window, + calwin->priv->calendar, tree_view); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->tasks_model)); + calwin->priv->task_list = list; + + list = create_birthday_list (calwin, &tree_view, &scrolled_window); + setup_list_size_constraint (scrolled_window, + calwin->priv->calendar, tree_view); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->birthdays_filter)); + calwin->priv->birthday_list = list; + + list = create_weather_list (calwin, &tree_view, &scrolled_window); + setup_list_size_constraint (scrolled_window, + calwin->priv->calendar, tree_view); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->weather_filter)); + calwin->priv->weather_list = list; + + list = create_appointment_list (calwin, &tree_view, &scrolled_window); + setup_list_size_constraint (scrolled_window, + calwin->priv->calendar, tree_view); + update_frame_visibility (list, + GTK_TREE_MODEL (calwin->priv->appointments_filter)); + calwin->priv->appointment_list = list; + + if (!calwin->priv->invert_order) { + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->task_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->appointment_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->birthday_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->weather_list, + TRUE, TRUE, 0); + } else { + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->weather_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->birthday_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->appointment_list, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->task_list, + TRUE, TRUE, 0); + } + + if (!calwin->priv->client) { + calwin->priv->client = calendar_client_new (); + + g_signal_connect_swapped (calwin->priv->client, + "tasks-changed", + G_CALLBACK (handle_tasks_changed), + calwin); + g_signal_connect_swapped (calwin->priv->client, + "appointments-changed", + G_CALLBACK (handle_appointments_changed), + calwin); + } + + gtk_calendar_get_date (GTK_CALENDAR (calwin->priv->calendar), + &year, &month, &day); + + calendar_client_select_day (calwin->priv->client, day); + calendar_client_select_month (calwin->priv->client, month, year); + + handle_tasks_changed (calwin); + handle_appointments_changed (calwin); + + g_signal_connect (calwin->priv->calendar, + "day-selected-double-click", + G_CALLBACK (calendar_day_activated), + calwin); + g_signal_connect (calwin->priv->calendar, + "day-selected", + G_CALLBACK (calendar_day_selected), + calwin); + g_signal_connect (calwin->priv->calendar, + "month-changed", + G_CALLBACK (calendar_month_selected), + calwin); +#endif /* HAVE_LIBECAL */ +} + +static GtkWidget * +calendar_window_create_calendar (CalendarWindow *calwin) +{ + GtkWidget *calendar; + GtkCalendarDisplayOptions options; + struct tm *tm; + + calendar = gtk_calendar_new (); + options = gtk_calendar_get_display_options (GTK_CALENDAR (calendar)); + if (calwin->priv->show_weeks) + options |= GTK_CALENDAR_SHOW_WEEK_NUMBERS; + else + options &= ~(GTK_CALENDAR_SHOW_WEEK_NUMBERS); + gtk_calendar_set_display_options (GTK_CALENDAR (calendar), options); + + tm = localtime (calwin->priv->current_time); + + gtk_calendar_select_month (GTK_CALENDAR (calendar), + tm->tm_mon, + tm->tm_year + 1900); + gtk_calendar_select_day (GTK_CALENDAR (calendar), tm->tm_mday); + + return calendar; +} + +static void +expand_collapse_child (GtkWidget *child, + gpointer data) +{ + gboolean expanded; + + if (data == child || gtk_widget_is_ancestor (data, child)) + return; + + expanded = gtk_expander_get_expanded (GTK_EXPANDER (data)); + g_object_set (child, "visible", expanded, NULL); +} + +static void +expand_collapse (GtkWidget *expander, + GParamSpec *pspec, + gpointer data) +{ + GtkWidget *box = data; + + gtk_container_foreach (GTK_CONTAINER (box), + (GtkCallback)expand_collapse_child, + expander); +} + +static void add_child (GtkContainer *container, + GtkWidget *child, + GtkExpander *expander) +{ + expand_collapse_child (child, expander); +} + +static GtkWidget * +create_hig_frame (CalendarWindow *calwin, + const char *title, + const char *button_label, + const char *key, + GCallback callback) +{ + GtkWidget *vbox; + GtkWidget *alignment; + GtkWidget *label; + GtkWidget *hbox; + GtkWidget *button; + char *bold_title; + char *text; + GtkWidget *expander; + + vbox = gtk_vbox_new (FALSE, 6); + + bold_title = g_strdup_printf ("<b>%s</b>", title); + expander = gtk_expander_new (bold_title); + g_free (bold_title); + gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), expander, FALSE, FALSE, 0); + gtk_widget_show_all (vbox); + + g_signal_connect (expander, "notify::expanded", + G_CALLBACK (expand_collapse), hbox); + g_signal_connect (expander, "notify::expanded", + G_CALLBACK (expand_collapse), vbox); + + /* FIXME: this doesn't really work, since "add" does not + * get emitted for e.g. gtk_box_pack_start + */ + g_signal_connect (vbox, "add", G_CALLBACK (add_child), expander); + g_signal_connect (hbox, "add", G_CALLBACK (add_child), expander); + + if (button_label) { + button = gtk_button_new (); + text = g_markup_printf_escaped ("<small>%s</small>", button_label); + label = gtk_label_new (text); + g_free (text); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_container_add (GTK_CONTAINER (button), label); + + alignment = gtk_alignment_new (1, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), button); + gtk_widget_show_all (alignment); + + gtk_container_add (GTK_CONTAINER (hbox), alignment); + + g_signal_connect_swapped (button, "clicked", callback, calwin); + } + +#ifdef HAVE_LIBECAL + connect_expander_with_mateconf (calwin, expander, key); +#endif + + return vbox; +} + +static void +edit_locations (CalendarWindow *calwin) +{ + g_signal_emit (calwin, signals[EDIT_LOCATIONS], 0); +} + +static void +calendar_window_pack_locations (CalendarWindow *calwin, GtkWidget *vbox) +{ + calwin->priv->locations_list = create_hig_frame (calwin, + _("Locations"), _("Edit"), + KEY_LOCATIONS_EXPANDED, + G_CALLBACK (edit_locations)); + + /* we show the widget before adding to the container, since adding to + * the container changes the visibility depending on the state of the + * expander */ + gtk_widget_show (calwin->priv->locations_list); + gtk_container_add (GTK_CONTAINER (vbox), calwin->priv->locations_list); + + //gtk_box_pack_start (GTK_BOX (vbox), calwin->priv->locations_list, TRUE, FALSE, 0); +} + +static void +calendar_window_fill (CalendarWindow *calwin) +{ + GtkWidget *frame; + GtkWidget *vbox; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (calwin), frame); + gtk_widget_show (frame); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + calwin->priv->calendar = calendar_window_create_calendar (calwin); + gtk_widget_show (calwin->priv->calendar); + + if (!calwin->priv->invert_order) { + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->calendar, TRUE, FALSE, 0); + calendar_window_pack_pim (calwin, vbox); + calendar_window_pack_locations (calwin, vbox); + } else { + calendar_window_pack_locations (calwin, vbox); + calendar_window_pack_pim (calwin, vbox); + gtk_box_pack_start (GTK_BOX (vbox), + calwin->priv->calendar, TRUE, FALSE, 0); + } +} + +GtkWidget * +calendar_window_get_locations_box (CalendarWindow *calwin) +{ + return calwin->priv->locations_list; +} + +static GObject * +calendar_window_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + CalendarWindow *calwin; + + obj = G_OBJECT_CLASS (calendar_window_parent_class)->constructor (type, + n_construct_properties, + construct_properties); + + calwin = CALENDAR_WINDOW (obj); + + g_assert (calwin->priv->current_time != NULL); + g_assert (calwin->priv->prefs_dir != NULL); + + calendar_window_fill (calwin); + + return obj; +} + +static void +calendar_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CalendarWindow *calwin; + + g_return_if_fail (CALENDAR_IS_WINDOW (object)); + + calwin = CALENDAR_WINDOW (object); + + switch (prop_id) { + case PROP_INVERTORDER: + g_value_set_boolean (value, + calendar_window_get_invert_order (calwin)); + break; + case PROP_SHOWWEEKS: + g_value_set_boolean (value, + calendar_window_get_show_weeks (calwin)); + break; +#ifdef HAVE_LIBECAL + case PROP_TIMEFORMAT: + g_value_set_enum (value, + calendar_window_get_time_format (calwin)); + break; +#endif + case PROP_CURRENTTIMEP: + g_value_set_pointer (value, + calendar_window_get_current_time_p (calwin)); + break; + case PROP_PREFSDIR: + g_value_set_string (value, + calendar_window_get_prefs_dir (calwin)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +calendar_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CalendarWindow *calwin; + + g_return_if_fail (CALENDAR_IS_WINDOW (object)); + + calwin = CALENDAR_WINDOW (object); + + switch (prop_id) { + case PROP_INVERTORDER: + calendar_window_set_invert_order (calwin, + g_value_get_boolean (value)); + break; + case PROP_SHOWWEEKS: + calendar_window_set_show_weeks (calwin, + g_value_get_boolean (value)); + break; +#ifdef HAVE_LIBECAL + case PROP_TIMEFORMAT: + calendar_window_set_time_format (calwin, + g_value_get_enum (value)); + break; +#endif + case PROP_CURRENTTIMEP: + calendar_window_set_current_time_p (calwin, + g_value_get_pointer (value)); + break; + case PROP_PREFSDIR: + calendar_window_set_prefs_dir (calwin, + g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +calendar_window_destroy (GtkObject *object) +{ +#ifdef HAVE_LIBECAL + CalendarWindow *calwin; + + calwin = CALENDAR_WINDOW (object); + + if (calwin->priv->client) + g_object_unref (calwin->priv->client); + calwin->priv->client = NULL; + + if (calwin->priv->appointments_model) + g_object_unref (calwin->priv->appointments_model); + calwin->priv->appointments_model = NULL; + + if (calwin->priv->tasks_model) + g_object_unref (calwin->priv->tasks_model); + calwin->priv->tasks_model = NULL; + + if (calwin->priv->appointments_filter) + g_object_unref (calwin->priv->appointments_filter); + calwin->priv->appointments_filter = NULL; + + if (calwin->priv->birthdays_filter) + g_object_unref (calwin->priv->birthdays_filter); + calwin->priv->birthdays_filter = NULL; + + if (calwin->priv->tasks_filter) + g_object_unref (calwin->priv->tasks_filter); + calwin->priv->tasks_filter = NULL; + + if (calwin->priv->weather_filter) + g_object_unref (calwin->priv->weather_filter); + calwin->priv->weather_filter = NULL; + + if (calwin->priv->mateconfclient) + g_object_unref (calwin->priv->mateconfclient); + calwin->priv->mateconfclient = NULL; +#endif /* HAVE_LIBECAL */ + + GTK_OBJECT_CLASS (calendar_window_parent_class)->destroy (object); +} + +static void +calendar_window_class_init (CalendarWindowClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass); + + gobject_class->constructor = calendar_window_constructor; + gobject_class->get_property = calendar_window_get_property; + gobject_class->set_property = calendar_window_set_property; + + gtkobject_class->destroy = calendar_window_destroy; + + g_type_class_add_private (klass, sizeof (CalendarWindowPrivate)); + + signals[EDIT_LOCATIONS] = g_signal_new ("edit-locations", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (CalendarWindowClass, edit_locations), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property ( + gobject_class, + PROP_INVERTORDER, + g_param_spec_boolean ("invert-order", + "Invert Order", + "Invert order of the calendar and tree views", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + gobject_class, + PROP_SHOWWEEKS, + g_param_spec_boolean ("show-weeks", + "Show Weeks", + "Show weeks in the calendar", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + +#ifdef HAVE_LIBECAL + g_object_class_install_property ( + gobject_class, + PROP_TIMEFORMAT, + g_param_spec_enum ("time-format", + "Time Format", + "Time format used to display time", + CLOCK_TYPE_FORMAT, + clock_locale_format (), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +#endif + + g_object_class_install_property ( + gobject_class, + PROP_CURRENTTIMEP, + g_param_spec_pointer ("current-time", + "Current Time", + "Pointer to a variable containing the current time", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property ( + gobject_class, + PROP_PREFSDIR, + g_param_spec_string ("prefs-dir", + "Preferences Directory", + "Preferences directory in MateConf", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +calendar_window_init (CalendarWindow *calwin) +{ + GtkWindow *window; + + calwin->priv = CALENDAR_WINDOW_GET_PRIVATE (calwin); + + window = GTK_WINDOW (calwin); + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK); + gtk_window_set_decorated (window, FALSE); + gtk_window_set_resizable (window, FALSE); + gtk_window_stick (window); + gtk_window_set_title (window, _("Calendar")); + gtk_window_set_icon_name (window, CLOCK_ICON); + +#ifdef HAVE_LIBECAL + calwin->priv->previous_selection = NULL; + calwin->priv->mateconfclient = mateconf_client_get_default (); +#endif +} + +GtkWidget * +calendar_window_new (time_t *static_current_time, + const char *prefs_dir, + gboolean invert_order) +{ + CalendarWindow *calwin; + + calwin = g_object_new (CALENDAR_TYPE_WINDOW, + "type", GTK_WINDOW_TOPLEVEL, + "current-time", static_current_time, + "invert-order", invert_order, + "prefs-dir", prefs_dir, + NULL); + + return GTK_WIDGET (calwin); +} + +void +calendar_window_refresh (CalendarWindow *calwin) +{ + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + +#ifdef HAVE_LIBECAL + if (calwin->priv->appointments_filter && calwin->priv->appointment_list) + gtk_tree_model_filter_refilter (calwin->priv->appointments_filter); + if (calwin->priv->birthdays_filter && calwin->priv->birthday_list) + gtk_tree_model_filter_refilter (calwin->priv->birthdays_filter); + if (calwin->priv->tasks_filter && calwin->priv->task_list) + gtk_tree_model_filter_refilter (calwin->priv->tasks_filter); + if (calwin->priv->weather_filter && calwin->priv->weather_list) + gtk_tree_model_filter_refilter (calwin->priv->weather_filter); +#endif +} + +gboolean +calendar_window_get_invert_order (CalendarWindow *calwin) +{ + g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), FALSE); + + return calwin->priv->invert_order; +} + +void +calendar_window_set_invert_order (CalendarWindow *calwin, + gboolean invert_order) +{ + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + + if (invert_order == calwin->priv->invert_order) + return; + + calwin->priv->invert_order = invert_order; + //FIXME: update the order of the content of the window + + g_object_notify (G_OBJECT (calwin), "invert-order"); +} + +gboolean +calendar_window_get_show_weeks (CalendarWindow *calwin) +{ + g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), FALSE); + + return calwin->priv->show_weeks; +} + +void +calendar_window_set_show_weeks (CalendarWindow *calwin, + gboolean show_weeks) +{ + GtkCalendarDisplayOptions options; + + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + + if (show_weeks == calwin->priv->show_weeks) + return; + + calwin->priv->show_weeks = show_weeks; + + if (calwin->priv->calendar) { + options = gtk_calendar_get_display_options (GTK_CALENDAR (calwin->priv->calendar)); + + if (show_weeks) + options |= GTK_CALENDAR_SHOW_WEEK_NUMBERS; + else + options &= ~(GTK_CALENDAR_SHOW_WEEK_NUMBERS); + + gtk_calendar_set_display_options (GTK_CALENDAR (calwin->priv->calendar), + options); + } + + g_object_notify (G_OBJECT (calwin), "show-weeks"); +} + +ClockFormat +calendar_window_get_time_format (CalendarWindow *calwin) +{ + g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), + CLOCK_FORMAT_INVALID); + +#ifdef HAVE_LIBECAL + return calwin->priv->time_format; +#else + return CLOCK_FORMAT_INVALID; +#endif +} + +void +calendar_window_set_time_format (CalendarWindow *calwin, + ClockFormat time_format) +{ + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + +#ifdef HAVE_LIBECAL + if (time_format != CLOCK_FORMAT_12 && time_format != CLOCK_FORMAT_24) + time_format = clock_locale_format (); + + if (time_format == calwin->priv->time_format) + return; + + calwin->priv->time_format = time_format; + /* Time to display for appointments has changed */ + if (calwin->priv->appointments_model) + handle_appointments_changed (calwin); + + g_object_notify (G_OBJECT (calwin), "time-format"); +#endif +} + +static time_t * +calendar_window_get_current_time_p (CalendarWindow *calwin) +{ + g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), NULL); + + return calwin->priv->current_time; +} + +static void +calendar_window_set_current_time_p (CalendarWindow *calwin, + time_t *current_time) +{ + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + + if (current_time == calwin->priv->current_time) + return; + + calwin->priv->current_time = current_time; + + g_object_notify (G_OBJECT (calwin), "current-time"); +} + +static const char * +calendar_window_get_prefs_dir (CalendarWindow *calwin) +{ + g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin), NULL); + + return calwin->priv->prefs_dir; +} + +static void +calendar_window_set_prefs_dir (CalendarWindow *calwin, + const char *prefs_dir) +{ + g_return_if_fail (CALENDAR_IS_WINDOW (calwin)); + + if (!calwin->priv->prefs_dir && (!prefs_dir || !prefs_dir [0])) + return; + + if (calwin->priv->prefs_dir && prefs_dir && prefs_dir [0] && + !strcmp (calwin->priv->prefs_dir, prefs_dir)) + return; + + if (calwin->priv->prefs_dir) + g_free (calwin->priv->prefs_dir); + calwin->priv->prefs_dir = NULL; + + if (prefs_dir && prefs_dir [0]) + calwin->priv->prefs_dir = g_strdup (prefs_dir); + + g_object_notify (G_OBJECT (calwin), "prefs-dir"); +} |