summaryrefslogtreecommitdiff
path: root/applets/clock/calendar-window.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/clock/calendar-window.c')
-rw-r--r--applets/clock/calendar-window.c2125
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");
+}