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.c1126
1 files changed, 1119 insertions, 7 deletions
diff --git a/applets/clock/calendar-window.c b/applets/clock/calendar-window.c
index b4157344..ae9f1d7c 100644
--- a/applets/clock/calendar-window.c
+++ b/applets/clock/calendar-window.c
@@ -38,11 +38,24 @@
#include "clock.h"
#include "clock-utils.h"
#include "clock-typebuiltins.h"
+#ifdef HAVE_EDS
+#include "calendar-client.h"
+#endif
#define KEY_LOCATIONS_EXPANDED "expand-locations"
+#ifdef HAVE_EDS
+#define KEY_SHOW_CALENDAR_EVENTS "show-calendar-events"
+#define KEY_SHOW_TASKS "show-tasks"
+#define SCHEMA_CALENDAR_APP "org.mate.desktop.default-applications.office.calendar"
+#define SCHEMA_TASKS_APP "org.mate.desktop.default-applications.office.tasks"
+#endif
+
enum {
EDIT_LOCATIONS,
+#ifdef HAVE_EDS
+ PERMISSION_READY,
+#endif
LAST_SIGNAL
};
@@ -60,6 +73,33 @@ struct _CalendarWindowPrivate {
GtkWidget *locations_list;
GSettings *settings;
+
+ /* Signal handler IDs for proper cleanup */
+ gulong calendar_month_changed_id;
+ gulong calendar_day_selected_id;
+
+#ifdef HAVE_EDS
+ ClockFormat time_format;
+
+ CalendarClient *client;
+
+ GtkWidget *appointment_list;
+
+ GtkListStore *appointments_model;
+ GtkListStore *tasks_model;
+
+ GtkTreeSelection *previous_selection;
+
+ GtkTreeModelFilter *appointments_filter;
+ GtkTreeModelFilter *tasks_filter;
+
+ GtkWidget *task_list;
+ GtkWidget *task_entry;
+
+ /* EDS-specific signal handler IDs */
+ gulong client_appointments_changed_id;
+ gulong client_tasks_changed_id;
+#endif /* HAVE_EDS */
};
G_DEFINE_TYPE_WITH_PRIVATE (CalendarWindow, calendar_window, GTK_TYPE_WINDOW)
@@ -84,6 +124,63 @@ static GtkWidget * create_hig_frame (CalendarWindow *calwin,
const char *key,
GCallback callback);
+#ifdef HAVE_EDS
+enum {
+ APPOINTMENT_COLUMN_UID,
+ APPOINTMENT_COLUMN_TYPE,
+ APPOINTMENT_COLUMN_SUMMARY,
+ APPOINTMENT_COLUMN_DESCRIPTION,
+ APPOINTMENT_COLUMN_START_TIME,
+ APPOINTMENT_COLUMN_START_TEXT,
+ APPOINTMENT_COLUMN_END_TIME,
+ APPOINTMENT_COLUMN_ALL_DAY,
+ APPOINTMENT_COLUMN_COLOR,
+ N_APPOINTMENT_COLUMNS
+};
+
+enum {
+ TASK_COLUMN_UID,
+ TASK_COLUMN_TYPE,
+ TASK_COLUMN_SUMMARY,
+ TASK_COLUMN_DESCRIPTION,
+ TASK_COLUMN_START_TIME,
+ TASK_COLUMN_START_TEXT,
+ TASK_COLUMN_DUE_TIME,
+ TASK_COLUMN_DUE_TEXT,
+ TASK_COLUMN_PERCENT_COMPLETE,
+ TASK_COLUMN_PERCENT_COMPLETE_TEXT,
+ TASK_COLUMN_COMPLETED,
+ TASK_COLUMN_COMPLETED_TIME,
+ TASK_COLUMN_PRIORITY,
+ TASK_COLUMN_COLOR,
+ N_TASK_COLUMNS
+};
+
+enum {
+ APPOINTMENT_TYPE_APPOINTMENT,
+ TASK_TYPE_TASK
+};
+
+static void calendar_window_pack_pim (CalendarWindow *calwin, GtkWidget *vbox);
+static char *format_time (ClockFormat format, time_t t, gint year, gint month, gint day);
+static void update_frame_visibility (GtkWidget *frame, GtkTreeModel *model);
+static GtkWidget *create_appointment_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window);
+static GtkWidget *create_task_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window);
+static void calendar_window_create_appointments_model (CalendarWindow *calwin);
+static void calendar_window_create_tasks_model (CalendarWindow *calwin);
+static void handle_appointments_changed (CalendarWindow *calwin);
+static void handle_tasks_changed (CalendarWindow *calwin);
+static void mark_day_on_calendar (CalendarClient *client, guint day, CalendarWindow *calwin);
+static gboolean is_for_filter (GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+static gboolean appointment_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
+static gboolean task_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
+static void appointment_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data);
+static void task_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data);
+static void task_completion_toggled_cb (GtkCellRendererToggle *cell, gchar *path_str, CalendarWindow *calwin);
+static gboolean task_entry_key_press_cb (GtkWidget *widget, GdkEventKey *event, CalendarWindow *calwin);
+static void task_entry_activate_cb (GtkEntry *entry, CalendarWindow *calwin);
+#endif /* HAVE_EDS */
+
static void calendar_mark_today(GtkCalendar *calendar)
{
time_t now;
@@ -110,7 +207,22 @@ static gboolean calendar_update(gpointer user_data)
static void calendar_month_changed_cb(GtkCalendar *calendar, gpointer user_data)
{
gtk_calendar_clear_marks(calendar);
- g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, user_data, NULL);
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, calendar, NULL);
+
+#ifdef HAVE_EDS
+ /* Update calendar client when date changes */
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->client) {
+ guint year, month, day;
+ gtk_calendar_get_date (calendar, &year, &month, &day);
+ calendar_client_select_month (calwin->priv->client, month, year);
+ calendar_client_select_day (calwin->priv->client, day);
+
+ /* Refresh appointments and tasks for the new date */
+ handle_appointments_changed (calwin);
+ handle_tasks_changed (calwin);
+ }
+#endif
}
static GtkWidget *
@@ -135,8 +247,10 @@ calendar_window_create_calendar (CalendarWindow *calwin)
gtk_calendar_select_day (GTK_CALENDAR (calendar), (guint) tm1.tm_mday);
calendar_mark_today (GTK_CALENDAR(calendar));
- g_signal_connect(calendar, "month-changed",
- G_CALLBACK(calendar_month_changed_cb), calendar);
+ calwin->priv->calendar_month_changed_id = g_signal_connect(calendar, "month-changed",
+ G_CALLBACK(calendar_month_changed_cb), calwin);
+ calwin->priv->calendar_day_selected_id = g_signal_connect(calendar, "day-selected",
+ G_CALLBACK(calendar_month_changed_cb), calwin);
return calendar;
}
@@ -250,6 +364,52 @@ edit_locations (CalendarWindow *calwin)
g_signal_emit (calwin, signals[EDIT_LOCATIONS], 0);
}
+#ifdef HAVE_EDS
+static gboolean
+hide_task_entry_idle (gpointer user_data)
+{
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->task_entry) {
+ gtk_widget_hide (calwin->priv->task_entry);
+ }
+ return FALSE; /* Remove the idle source */
+}
+
+static gboolean
+focus_task_entry_idle (gpointer user_data)
+{
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->task_entry && gtk_widget_get_visible (calwin->priv->task_entry)) {
+ gtk_widget_grab_focus (calwin->priv->task_entry);
+ }
+ return FALSE; /* Remove the idle source */
+}
+
+static void
+add_task (CalendarWindow *calwin)
+{
+ if (calwin->priv->task_entry) {
+ gtk_widget_show (calwin->priv->task_entry);
+ gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE);
+ gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE);
+
+ /* Make sure parent window is active */
+ gtk_window_present (GTK_WINDOW (calwin));
+
+ /* Ensure widget is realized */
+ if (!gtk_widget_get_realized (calwin->priv->task_entry)) {
+ gtk_widget_realize (calwin->priv->task_entry);
+ }
+
+ /* Try to grab focus immediately */
+ gtk_widget_grab_focus (calwin->priv->task_entry);
+
+ /* Also try to grab focus in idle callback in case immediate focus fails */
+ g_idle_add (focus_task_entry_idle, calwin);
+ }
+}
+#endif
+
static void
calendar_window_pack_locations (CalendarWindow *calwin, GtkWidget *vbox)
{
@@ -287,12 +447,22 @@ calendar_window_fill (CalendarWindow *calwin)
calwin->priv->calendar = calendar_window_create_calendar (calwin);
gtk_widget_show (calwin->priv->calendar);
+#ifdef HAVE_EDS
+ /* Calendar client will be initialized later in calendar_window_pack_pim */
+#endif
+
if (!calwin->priv->invert_order) {
gtk_box_pack_start (GTK_BOX (vbox),
calwin->priv->calendar, TRUE, FALSE, 0);
+#ifdef HAVE_EDS
+ calendar_window_pack_pim (calwin, vbox);
+#endif
calendar_window_pack_locations (calwin, vbox);
} else {
calendar_window_pack_locations (calwin, vbox);
+#ifdef HAVE_EDS
+ calendar_window_pack_pim (calwin, vbox);
+#endif
gtk_box_pack_start (GTK_BOX (vbox),
calwin->priv->calendar, TRUE, FALSE, 0);
}
@@ -405,6 +575,33 @@ calendar_window_dispose (GObject *object)
g_clear_pointer (&calwin->priv->prefs_path, g_free);
+ /* Disconnect calendar signals */
+ if (calwin->priv->calendar && calwin->priv->calendar_month_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_month_changed_id);
+ calwin->priv->calendar_month_changed_id = 0;
+ }
+ if (calwin->priv->calendar && calwin->priv->calendar_day_selected_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_day_selected_id);
+ calwin->priv->calendar_day_selected_id = 0;
+ }
+
+#ifdef HAVE_EDS
+ /* Disconnect client signals */
+ if (calwin->priv->client) {
+ if (calwin->priv->client_appointments_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_appointments_changed_id);
+ calwin->priv->client_appointments_changed_id = 0;
+ }
+ if (calwin->priv->client_tasks_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_tasks_changed_id);
+ calwin->priv->client_tasks_changed_id = 0;
+ }
+ g_signal_handlers_disconnect_by_data (calwin->priv->client, calwin);
+ g_object_unref (calwin->priv->client);
+ calwin->priv->client = NULL;
+ }
+#endif
+
if (calwin->priv->settings)
g_object_unref (calwin->priv->settings);
calwin->priv->settings = NULL;
@@ -475,6 +672,13 @@ calendar_window_init (CalendarWindow *calwin)
calwin->priv = calendar_window_get_instance_private (calwin);
+#ifdef HAVE_EDS
+ /* Initialize signal handler IDs */
+ calwin->priv->calendar_month_changed_id = 0;
+ calwin->priv->calendar_day_selected_id = 0;
+ calwin->priv->client_appointments_changed_id = 0;
+#endif
+
window = GTK_WINDOW (calwin);
gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK);
gtk_window_set_decorated (window, FALSE);
@@ -488,7 +692,8 @@ calendar_window_init (CalendarWindow *calwin)
GtkWidget *
calendar_window_new (time_t *static_current_time,
const char *prefs_path,
- gboolean invert_order)
+ gboolean invert_order,
+ GSettings *settings)
{
CalendarWindow *calwin;
@@ -499,6 +704,13 @@ calendar_window_new (time_t *static_current_time,
"prefs-path", prefs_path,
NULL);
+#ifdef HAVE_EDS
+ /* Store settings for calendar client initialization in init */
+ if (settings) {
+ calwin->priv->settings = g_object_ref (settings);
+ }
+#endif
+
return GTK_WIDGET (calwin);
}
@@ -506,6 +718,16 @@ void
calendar_window_refresh (CalendarWindow *calwin)
{
g_return_if_fail (CALENDAR_IS_WINDOW (calwin));
+
+#ifdef HAVE_EDS
+ if (calwin->priv->appointments_filter && calwin->priv->appointment_list)
+ gtk_tree_model_filter_refilter (calwin->priv->appointments_filter);
+
+ /* Update frame visibility based on model content */
+ if (calwin->priv->appointment_list && calwin->priv->appointments_filter)
+ update_frame_visibility (calwin->priv->appointment_list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+#endif
}
gboolean
@@ -573,7 +795,11 @@ calendar_window_get_time_format (CalendarWindow *calwin)
g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin),
CLOCK_FORMAT_INVALID);
+#ifdef HAVE_EDS
+ return calwin->priv->time_format;
+#else
return CLOCK_FORMAT_INVALID;
+#endif
}
static time_t *
@@ -627,7 +853,893 @@ calendar_window_set_prefs_path (CalendarWindow *calwin,
g_object_notify (G_OBJECT (calwin), "prefs-path");
- if (calwin->priv->settings)
- g_object_unref (calwin->priv->settings);
- calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path);
+ /* Only create new settings if we don't already have shared settings */
+ if (!calwin->priv->settings) {
+ calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path);
+ }
+}
+
+#ifdef HAVE_EDS
+
+static char *
+format_time (ClockFormat format,
+ time_t t,
+ gint year,
+ gint month,
+ gint day)
+{
+ GDateTime *dt;
+ gchar *time;
+
+ if (!t)
+ return NULL;
+
+ /* Evolution timestamps are in UTC but represent local appointment times
+ * Since TZID lookup failed, treat UTC timestamp as local time directly */
+ dt = g_date_time_new_from_unix_utc (t);
+ time = NULL;
+
+ if (!dt)
+ return NULL;
+
+ /* Always show time since we're filtering by selected date */
+ 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 = g_date_time_format (dt, _("%l:%M %p"));
+ } 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 = g_date_time_format (dt, _("%H:%M"));
+ }
+
+ g_date_time_unref (dt);
+ return time;
+}
+
+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);
+}
+
+
+static void
+calendar_window_create_appointments_model (CalendarWindow *calwin)
+{
+ calwin->priv->appointments_model = gtk_list_store_new (N_APPOINTMENT_COLUMNS,
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_UID */
+ G_TYPE_INT, /* APPOINTMENT_COLUMN_TYPE */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_SUMMARY */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_DESCRIPTION */
+ G_TYPE_ULONG, /* APPOINTMENT_COLUMN_START_TIME */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_START_TEXT */
+ G_TYPE_ULONG, /* APPOINTMENT_COLUMN_END_TIME */
+ G_TYPE_BOOLEAN, /* APPOINTMENT_COLUMN_ALL_DAY */
+ G_TYPE_STRING); /* APPOINTMENT_COLUMN_COLOR */
+
+ calwin->priv->appointments_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 (calwin->priv->appointments_filter,
+ (GtkTreeModelFilterVisibleFunc) is_for_filter,
+ GINT_TO_POINTER (APPOINTMENT_TYPE_APPOINTMENT),
+ NULL);
+}
+
+static void
+calendar_window_create_tasks_model (CalendarWindow *calwin)
+{
+ calwin->priv->tasks_model = gtk_list_store_new (N_TASK_COLUMNS,
+ G_TYPE_STRING, /* TASK_COLUMN_UID */
+ G_TYPE_INT, /* TASK_COLUMN_TYPE */
+ G_TYPE_STRING, /* TASK_COLUMN_SUMMARY */
+ G_TYPE_STRING, /* TASK_COLUMN_DESCRIPTION */
+ G_TYPE_ULONG, /* TASK_COLUMN_START_TIME */
+ G_TYPE_STRING, /* TASK_COLUMN_START_TEXT */
+ G_TYPE_ULONG, /* TASK_COLUMN_DUE_TIME */
+ G_TYPE_STRING, /* TASK_COLUMN_DUE_TEXT */
+ G_TYPE_INT, /* TASK_COLUMN_PERCENT_COMPLETE */
+ G_TYPE_STRING, /* TASK_COLUMN_PERCENT_COMPLETE_TEXT */
+ G_TYPE_BOOLEAN, /* TASK_COLUMN_COMPLETED */
+ G_TYPE_ULONG, /* TASK_COLUMN_COMPLETED_TIME */
+ G_TYPE_INT, /* TASK_COLUMN_PRIORITY */
+ G_TYPE_STRING); /* TASK_COLUMN_COLOR */
+
+ 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) is_for_filter,
+ GINT_TO_POINTER (TASK_TYPE_TASK),
+ NULL);
+}
+
+static GtkWidget *
+create_hig_calendar_frame (CalendarWindow *calwin,
+ const char *title,
+ const char *button_label,
+ const char *key,
+ GCallback callback)
+{
+ return create_hig_frame (calwin, title, button_label, key, callback);
+}
+
+
+static GtkWidget *
+create_appointment_list (CalendarWindow *calwin,
+ GtkWidget **tree_view,
+ GtkWidget **scrolled_window)
+{
+ GtkWidget *frame;
+ GtkWidget *list;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ frame = create_hig_calendar_frame (calwin, _("Appointments"), NULL,
+ KEY_SHOW_CALENDAR_EVENTS, NULL);
+
+ list = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list),
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+
+ column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", APPOINTMENT_COLUMN_START_TEXT,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "wrap-width", 200,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", APPOINTMENT_COLUMN_SUMMARY,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+ gtk_widget_set_has_tooltip (list, TRUE);
+ g_signal_connect (list, "query-tooltip", G_CALLBACK (appointment_tooltip_query_cb), calwin);
+ g_signal_connect (list, "row-activated", G_CALLBACK (appointment_row_activated_cb), calwin);
+
+ *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (*scrolled_window, -1, 150);
+ gtk_container_add (GTK_CONTAINER (*scrolled_window), list);
+
+ gtk_container_add (GTK_CONTAINER (frame), *scrolled_window);
+
+ /* Ensure the scrolled window and tree view are visible */
+ gtk_widget_show (*scrolled_window);
+ gtk_widget_show (list);
+
+ /* Appointment list widgets created */
+
+ *tree_view = list;
+ return frame;
+}
+
+static GtkWidget *
+create_task_list (CalendarWindow *calwin,
+ GtkWidget **tree_view,
+ GtkWidget **scrolled_window)
+{
+ GtkWidget *frame;
+ GtkWidget *list;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ frame = create_hig_calendar_frame (calwin, _("Tasks"), _("Add"),
+ KEY_SHOW_TASKS, G_CALLBACK (add_task));
+
+ list = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list),
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+
+ column = gtk_tree_view_column_new ();
+
+ /* Completion checkbox */
+ cell = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "active", TASK_COLUMN_COMPLETED,
+ NULL);
+ g_signal_connect (cell, "toggled", G_CALLBACK (task_completion_toggled_cb), calwin);
+
+
+ /* Task summary */
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "wrap-width", 200,
+ "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,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+ gtk_widget_set_has_tooltip (list, TRUE);
+ g_signal_connect (list, "query-tooltip", G_CALLBACK (task_tooltip_query_cb), calwin);
+ g_signal_connect (list, "row-activated", G_CALLBACK (task_row_activated_cb), calwin);
+
+ *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (*scrolled_window, -1, 150);
+ gtk_container_add (GTK_CONTAINER (*scrolled_window), list);
+
+ gtk_container_add (GTK_CONTAINER (frame), *scrolled_window);
+
+ /* Create task entry field */
+ calwin->priv->task_entry = gtk_entry_new ();
+ gtk_entry_set_placeholder_text (GTK_ENTRY (calwin->priv->task_entry), _("Enter task description..."));
+ gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE);
+ gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE);
+ g_signal_connect (calwin->priv->task_entry, "key-press-event", G_CALLBACK (task_entry_key_press_cb), calwin);
+ g_signal_connect (calwin->priv->task_entry, "activate", G_CALLBACK (task_entry_activate_cb), calwin);
+ gtk_container_add (GTK_CONTAINER (frame), calwin->priv->task_entry);
+
+ /* Ensure the scrolled window and tree view are visible */
+ gtk_widget_show (*scrolled_window);
+ gtk_widget_show (list);
+
+ /* Hide task entry after all show operations are complete */
+ g_idle_add (hide_task_entry_idle, calwin);
+
+ *tree_view = list;
+ return frame;
+}
+
+static void
+calendar_window_pack_pim (CalendarWindow *calwin,
+ GtkWidget *vbox)
+{
+ GtkWidget *list;
+ GtkWidget *tree_view;
+ GtkWidget *scrolled_window;
+ gboolean show_calendar_events;
+ gboolean show_tasks;
+
+ /* Check if calendar events should be shown */
+ show_calendar_events = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_CALENDAR_EVENTS);
+ show_tasks = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_TASKS);
+
+ if (!show_calendar_events && !show_tasks) {
+ return;
+ }
+
+ /* Initialize calendar client if not already done */
+ if (!calwin->priv->client && calwin->priv->settings) {
+ calwin->priv->client = calendar_client_new (calwin->priv->settings);
+
+ if (calwin->priv->client) {
+ if (show_calendar_events) {
+ calwin->priv->client_appointments_changed_id = g_signal_connect_swapped (calwin->priv->client,
+ "appointments-changed",
+ G_CALLBACK (handle_appointments_changed),
+ calwin);
+ }
+ if (show_tasks) {
+ calwin->priv->client_tasks_changed_id = g_signal_connect_swapped (calwin->priv->client,
+ "tasks-changed",
+ G_CALLBACK (handle_tasks_changed),
+ calwin);
+ }
+ }
+ }
+
+ if (!calwin->priv->client) {
+ g_warning ("Failed to create calendar client in calendar_window_pack_pim");
+ return;
+ }
+
+ /* Create and pack appointments list if enabled */
+ if (show_calendar_events) {
+ calendar_window_create_appointments_model (calwin);
+ list = create_appointment_list (calwin, &tree_view, &scrolled_window);
+ update_frame_visibility (list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+ calwin->priv->appointment_list = list;
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ calwin->priv->appointment_list,
+ TRUE, TRUE, 0);
+ }
+
+ /* Create and pack tasks list if enabled */
+ if (show_tasks) {
+ calendar_window_create_tasks_model (calwin);
+ list = create_task_list (calwin, &tree_view, &scrolled_window);
+ update_frame_visibility (list,
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+ calwin->priv->task_list = list;
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ calwin->priv->task_list,
+ TRUE, TRUE, 0);
+ }
+
+ /* Initialize calendar client with current date now that client is ready */
+ if (calwin->priv->client && calwin->priv->calendar) {
+ guint year, month, day;
+ gtk_calendar_get_date (GTK_CALENDAR (calwin->priv->calendar), &year, &month, &day);
+ /* Set a flag to indicate we're initializing to prevent redundant calls */
+ g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (1));
+
+ calendar_client_select_month (calwin->priv->client, month, year);
+ calendar_client_select_day (calwin->priv->client, day);
+
+ /* Clear the initialization flag and trigger initial load */
+ g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (0));
+
+ /* Now trigger the initial appointments and tasks load */
+ if (show_calendar_events) {
+ handle_appointments_changed (calwin);
+ }
+ if (show_tasks) {
+ handle_tasks_changed (calwin);
+ }
+ }
}
+
+static gboolean
+is_for_filter (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gint type;
+ gint expected_type = GPOINTER_TO_INT (data);
+
+ /* Check if this is a task model or appointment model */
+ if (expected_type == TASK_TYPE_TASK) {
+ gtk_tree_model_get (model, iter, TASK_COLUMN_TYPE, &type, -1);
+ } else {
+ gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_TYPE, &type, -1);
+ }
+ return type == expected_type;
+}
+
+static void
+mark_day_on_calendar (CalendarClient *client,
+ guint day,
+ CalendarWindow *calwin)
+{
+ gtk_calendar_mark_day (GTK_CALENDAR (calwin->priv->calendar), day);
+}
+
+
+
+static gint
+compare_appointments_by_time (const CalendarAppointment *a, const CalendarAppointment *b)
+{
+ /* Sort by start time - earlier appointments first */
+ if (a->start_time < b->start_time)
+ return -1;
+ else if (a->start_time > b->start_time)
+ return 1;
+ else
+ return 0;
+}
+
+static gint
+compare_tasks_by_due_time (const CalendarTask *a, const CalendarTask *b)
+{
+ /* Sort by due time - earlier due dates first, then by priority */
+ if (a->due_time && b->due_time) {
+ if (a->due_time < b->due_time)
+ return -1;
+ else if (a->due_time > b->due_time)
+ return 1;
+ } else if (a->due_time && !b->due_time) {
+ return -1; /* Tasks with due dates come first */
+ } else if (!a->due_time && b->due_time) {
+ return 1;
+ }
+
+ /* If due times are equal or both missing, sort by priority (higher priority first) */
+ if (a->priority > b->priority)
+ return -1;
+ else if (a->priority < b->priority)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+handle_appointments_changed (CalendarWindow *calwin)
+{
+ GSList *events, *l;
+ guint year, month, day;
+
+ /* Skip redundant calls during initialization */
+ if (g_object_get_data (G_OBJECT (calwin), "initializing")) {
+ return;
+ }
+
+ 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);
+
+ /* Sort appointments by start time for better display order */
+ events = g_slist_sort (events, (GCompareFunc) compare_appointments_by_time);
+
+ /* Found appointments for current date */
+ 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 (calendar_window_get_time_format (calwin),
+ appointment->start_time,
+ year, month, day);
+
+ gtk_list_store_append (calwin->priv->appointments_model, &iter);
+ /* Appointment added to model */
+ gtk_list_store_set (calwin->priv->appointments_model, &iter,
+ APPOINTMENT_COLUMN_UID, appointment->uid,
+ APPOINTMENT_COLUMN_TYPE, APPOINTMENT_TYPE_APPOINTMENT,
+ 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_COLOR, appointment->color_string,
+ -1);
+
+ g_free (start_text);
+ }
+
+ /* Refresh filter before checking visibility */
+ if (calwin->priv->appointments_filter)
+ gtk_tree_model_filter_refilter (calwin->priv->appointments_filter);
+
+ update_frame_visibility (calwin->priv->appointment_list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+}
+
+static void
+handle_tasks_changed (CalendarWindow *calwin)
+{
+ GSList *events, *l;
+ guint year, month, day;
+
+ /* Skip redundant calls during initialization */
+ if (g_object_get_data (G_OBJECT (calwin), "initializing")) {
+ return;
+ }
+
+ if (!calwin->priv->tasks_model) {
+ return;
+ }
+
+ gtk_list_store_clear (calwin->priv->tasks_model);
+
+ calendar_client_get_date (calwin->priv->client, &year, &month, &day);
+
+ events = calendar_client_get_events (calwin->priv->client,
+ CALENDAR_EVENT_TASK);
+
+ /* Sort tasks by due time for better display order */
+ events = g_slist_sort (events, (GCompareFunc) compare_tasks_by_due_time);
+
+ /* Found tasks for current date */
+ for (l = events; l; l = l->next) {
+ CalendarTask *task = (CalendarTask *) l->data;
+ GtkTreeIter iter;
+ char *start_text = NULL;
+ char *due_text = NULL;
+ char *percent_complete_text = NULL;
+ gboolean completed;
+
+ g_assert (CALENDAR_EVENT (task)->type == CALENDAR_EVENT_TASK);
+
+ if (task->start_time) {
+ start_text = format_time (calendar_window_get_time_format (calwin),
+ task->start_time,
+ year, month, day);
+ } else {
+ start_text = g_strdup ("");
+ }
+
+ if (task->due_time) {
+ due_text = format_time (calendar_window_get_time_format (calwin),
+ task->due_time,
+ year, month, day);
+ } else {
+ due_text = g_strdup ("");
+ }
+
+ /* Format percent complete as text */
+ if (task->percent_complete > 0) {
+ percent_complete_text = g_strdup_printf ("%d%%", task->percent_complete);
+ } else {
+ percent_complete_text = g_strdup ("");
+ }
+
+ completed = (task->percent_complete == 100);
+
+ 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_TYPE, TASK_TYPE_TASK,
+ TASK_COLUMN_SUMMARY, task->summary,
+ TASK_COLUMN_DESCRIPTION, task->description,
+ TASK_COLUMN_START_TIME, (gint64)task->start_time,
+ TASK_COLUMN_START_TEXT, start_text,
+ TASK_COLUMN_DUE_TIME, (gint64)task->due_time,
+ TASK_COLUMN_DUE_TEXT, due_text,
+ TASK_COLUMN_PERCENT_COMPLETE, task->percent_complete,
+ TASK_COLUMN_PERCENT_COMPLETE_TEXT, percent_complete_text,
+ TASK_COLUMN_COMPLETED, completed,
+ TASK_COLUMN_COMPLETED_TIME, (gint64)task->completed_time,
+ TASK_COLUMN_PRIORITY, task->priority,
+ TASK_COLUMN_COLOR, task->color_string,
+ -1);
+
+ g_free (start_text);
+ g_free (due_text);
+ g_free (percent_complete_text);
+ }
+
+ /* Refresh filter before checking visibility */
+ if (calwin->priv->tasks_filter)
+ gtk_tree_model_filter_refilter (calwin->priv->tasks_filter);
+
+ update_frame_visibility (calwin->priv->task_list,
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+}
+
+static void
+appointment_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GAppInfo *app_info;
+ GError *error = NULL;
+
+ /* Launch Evolution calendar */
+ app_info = g_app_info_get_default_for_type ("text/calendar", FALSE);
+ if (!app_info) {
+ /* Try launching evolution directly if no calendar app is set */
+ app_info = g_app_info_create_from_commandline ("evolution -c calendar",
+ "Evolution Calendar",
+ G_APP_INFO_CREATE_NONE,
+ &error);
+ }
+
+ if (app_info) {
+ if (!g_app_info_launch (app_info, NULL, NULL, &error)) {
+ g_warning ("Failed to launch calendar application: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (app_info);
+ } else {
+ g_warning ("No calendar application found");
+ if (error) {
+ g_warning ("Error: %s", error->message);
+ g_error_free (error);
+ }
+ }
+}
+
+static gboolean
+appointment_tooltip_query_cb (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *summary, *description, *start_text;
+ gchar *tooltip_text, *end_text;
+ gboolean all_day;
+ gulong start_time, end_time;
+
+ if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode,
+ &model, &path, &iter)) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ APPOINTMENT_COLUMN_SUMMARY, &summary,
+ APPOINTMENT_COLUMN_DESCRIPTION, &description,
+ APPOINTMENT_COLUMN_START_TEXT, &start_text,
+ APPOINTMENT_COLUMN_START_TIME, &start_time,
+ APPOINTMENT_COLUMN_END_TIME, &end_time,
+ APPOINTMENT_COLUMN_ALL_DAY, &all_day,
+ -1);
+
+ if (!summary) {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ /* Format end time */
+ if (!all_day && end_time > 0) {
+ GDateTime *end_dt = g_date_time_new_from_unix_utc (end_time);
+ if (end_dt) {
+ end_text = g_date_time_format (end_dt, "%H:%M");
+ g_date_time_unref (end_dt);
+ } else {
+ end_text = NULL;
+ }
+ } else {
+ end_text = NULL;
+ }
+
+ if (description && strlen (description) > 0) {
+ if (all_day) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nAll Day", summary, description);
+ } else if (end_text) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s - %s", summary, description, start_text ? start_text : "", end_text);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s", summary, description, start_text ? start_text : "");
+ }
+ } else {
+ if (all_day) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nAll Day", summary);
+ } else if (end_text) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s - %s", summary, start_text ? start_text : "", end_text);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s", summary, start_text ? start_text : "");
+ }
+ }
+
+ gtk_tooltip_set_markup (tooltip, tooltip_text);
+ gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
+
+ g_free (summary);
+ g_free (description);
+ g_free (start_text);
+ g_free (end_text);
+ g_free (tooltip_text);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+static gboolean
+task_tooltip_query_cb (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *summary, *description, *start_text, *due_text;
+ gchar *tooltip_text;
+ gint percent_complete, priority;
+
+ if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode,
+ &model, &path, &iter)) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ TASK_COLUMN_SUMMARY, &summary,
+ TASK_COLUMN_DESCRIPTION, &description,
+ TASK_COLUMN_START_TEXT, &start_text,
+ TASK_COLUMN_DUE_TEXT, &due_text,
+ TASK_COLUMN_PERCENT_COMPLETE, &percent_complete,
+ TASK_COLUMN_PRIORITY, &priority,
+ -1);
+
+ if (!summary) {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ /* Build tooltip with task information */
+ if (description && strlen (description) > 0) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nProgress: %d%%",
+ summary, description, percent_complete);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nProgress: %d%%",
+ summary, percent_complete);
+ }
+
+ /* Add due date if available */
+ if (due_text && strlen (due_text) > 0) {
+ gchar *temp = tooltip_text;
+ tooltip_text = g_markup_printf_escaped ("%s\nDue: %s", temp, due_text);
+ g_free (temp);
+ }
+
+ gtk_tooltip_set_markup (tooltip, tooltip_text);
+ gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
+
+ g_free (summary);
+ g_free (description);
+ g_free (start_text);
+ g_free (due_text);
+ g_free (tooltip_text);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+static void
+task_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GAppInfo *app_info;
+ GError *error = NULL;
+
+ /* Launch Evolution tasks */
+ app_info = g_app_info_get_default_for_uri_scheme ("task");
+ if (!app_info) {
+ /* Fallback to Evolution tasks directly */
+ app_info = g_app_info_create_from_commandline ("evolution --component=tasks",
+ "Evolution Tasks",
+ G_APP_INFO_CREATE_NONE,
+ &error);
+ }
+
+ if (app_info) {
+ g_app_info_launch (app_info, NULL, NULL, &error);
+ g_object_unref (app_info);
+ }
+
+ if (error) {
+ g_warning ("Failed to launch Evolution tasks: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+task_completion_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_str,
+ CalendarWindow *calwin)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *task_uid;
+ gboolean completed;
+ gint percent_complete;
+
+ path = gtk_tree_path_new_from_string (path_str);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter, path)) {
+ gtk_tree_path_free (path);
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter,
+ TASK_COLUMN_UID, &task_uid,
+ TASK_COLUMN_COMPLETED, &completed,
+ TASK_COLUMN_PERCENT_COMPLETE, &percent_complete,
+ -1);
+
+ /* Toggle completion state */
+ completed = !completed;
+ percent_complete = completed ? 100 : 0;
+
+ /* Update the Evolution task */
+ if (calwin->priv->client && task_uid) {
+ calendar_client_set_task_completed (calwin->priv->client,
+ task_uid,
+ completed,
+ percent_complete);
+ }
+
+ g_free (task_uid);
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+task_entry_key_press_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ CalendarWindow *calwin)
+{
+ const gchar *text;
+
+ if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
+ /* Get the text from the entry */
+ text = gtk_entry_get_text (GTK_ENTRY (widget));
+
+ /* Create task if text is not empty */
+ if (text && *text != '\0') {
+ if (calwin->priv->client) {
+ gboolean success = calendar_client_create_task (calwin->priv->client, text);
+ if (success) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (GTK_ENTRY (widget), "");
+ gtk_widget_hide (widget);
+ } else {
+ g_warning ("Failed to create task");
+ }
+ }
+ }
+ return TRUE; /* Event handled */
+ } else if (event->keyval == GDK_KEY_Escape) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (GTK_ENTRY (widget), "");
+ gtk_widget_hide (widget);
+ return TRUE; /* Event handled */
+ }
+
+ return FALSE; /* Let other handlers process the event */
+}
+
+static void
+task_entry_activate_cb (GtkEntry *entry,
+ CalendarWindow *calwin)
+{
+ const gchar *text;
+
+ /* Get the text from the entry */
+ text = gtk_entry_get_text (entry);
+
+ /* Create task if text is not empty */
+ if (text && *text != '\0') {
+ if (calwin->priv->client) {
+ gboolean success = calendar_client_create_task (calwin->priv->client, text);
+ if (success) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (entry, "");
+ gtk_widget_hide (GTK_WIDGET (entry));
+ } else {
+ g_warning ("Failed to create task");
+ }
+ }
+ }
+}
+
+#endif /* HAVE_EDS */