From 49787d04feef8ff699326e54d5991a36d8ef74bb Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Wed, 11 Apr 2012 16:58:05 +0200 Subject: Bug #584143 - Global Search Function withing Calendar --- modules/calendar/e-cal-shell-settings.c | 5 + modules/calendar/e-cal-shell-view-actions.c | 42 +++ modules/calendar/e-cal-shell-view-actions.h | 6 + modules/calendar/e-cal-shell-view-private.c | 462 ++++++++++++++++++++++++++++ modules/calendar/e-cal-shell-view-private.h | 14 + modules/calendar/e-cal-shell-view.c | 24 +- 6 files changed, 552 insertions(+), 1 deletion(-) (limited to 'modules/calendar') diff --git a/modules/calendar/e-cal-shell-settings.c b/modules/calendar/e-cal-shell-settings.c index 392bb841cb..045ca28f3b 100644 --- a/modules/calendar/e-cal-shell-settings.c +++ b/modules/calendar/e-cal-shell-settings.c @@ -623,6 +623,11 @@ e_cal_shell_backend_init_settings (EShell *shell) CALENDAR_SCHEMA, "recur-events-italic"); + e_shell_settings_install_property_for_key ( + "cal-search-range-years", + CALENDAR_SCHEMA, + "search-range-years"); + e_shell_settings_install_property_for_key ( "cal-show-event-end-times", CALENDAR_SCHEMA, diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c index 956766571a..d73feef25e 100644 --- a/modules/calendar/e-cal-shell-view-actions.c +++ b/modules/calendar/e-cal-shell-view-actions.c @@ -420,6 +420,27 @@ action_calendar_rename_cb (GtkAction *action, e_source_selector_edit_primary_selection (selector); } +static void +action_calendar_search_next_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_events (cal_shell_view, TRUE); +} + +static void +action_calendar_search_prev_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_events (cal_shell_view, FALSE); +} + +static void +action_calendar_search_stop_cb (GtkAction *action, + ECalShellView *cal_shell_view) +{ + e_cal_shell_view_search_stop (cal_shell_view); +} + static void action_calendar_select_one_cb (GtkAction *action, ECalShellView *cal_shell_view) @@ -1407,6 +1428,27 @@ static GtkActionEntry calendar_entries[] = { N_("Rename the selected calendar"), G_CALLBACK (action_calendar_rename_cb) }, + { "calendar-search-next", + GTK_STOCK_GO_FORWARD, + N_("Find _next"), + "n", + N_("Find next occurrence of the current search string"), + G_CALLBACK (action_calendar_search_next_cb) }, + + { "calendar-search-prev", + GTK_STOCK_GO_BACK, + N_("Find _previous"), + "p", + N_("Find previous occurrence of the current search string"), + G_CALLBACK (action_calendar_search_prev_cb) }, + + { "calendar-search-stop", + GTK_STOCK_STOP, + N_("Stop _running search"), + NULL, + N_("Stop currently running search"), + G_CALLBACK (action_calendar_search_stop_cb) }, + { "calendar-select-one", "stock_check-filled", N_("Show _Only This Calendar"), diff --git a/modules/calendar/e-cal-shell-view-actions.h b/modules/calendar/e-cal-shell-view-actions.h index fcc77ea011..ef2c520d74 100644 --- a/modules/calendar/e-cal-shell-view-actions.h +++ b/modules/calendar/e-cal-shell-view-actions.h @@ -51,6 +51,12 @@ E_SHELL_WINDOW_ACTION ((window), "calendar-refresh") #define E_SHELL_WINDOW_ACTION_CALENDAR_RENAME(window) \ E_SHELL_WINDOW_ACTION ((window), "calendar-rename") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_PREV(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-prev") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_NEXT(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-next") +#define E_SHELL_WINDOW_ACTION_CALENDAR_SEARCH_STOP(window) \ + E_SHELL_WINDOW_ACTION ((window), "calendar-search-stop") #define E_SHELL_WINDOW_ACTION_CALENDAR_SELECT_ONE(window) \ E_SHELL_WINDOW_ACTION ((window), "calendar-select-one") #define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_DAY(window) \ diff --git a/modules/calendar/e-cal-shell-view-private.c b/modules/calendar/e-cal-shell-view-private.c index 818ee3047e..fc94a83a21 100644 --- a/modules/calendar/e-cal-shell-view-private.c +++ b/modules/calendar/e-cal-shell-view-private.c @@ -687,6 +687,8 @@ e_cal_shell_view_private_dispose (ECalShellView *cal_shell_view) ECalShellViewPrivate *priv = cal_shell_view->priv; gint i; + e_cal_shell_view_search_stop (cal_shell_view); + /* Calling calendar's save state from here, * because it is too late in its dispose. */ if (priv->cal_shell_content != NULL) @@ -1159,3 +1161,463 @@ e_cal_shell_view_update_timezone (ECalShellView *cal_shell_view) g_list_free (clients); } + +static gint +cal_searching_get_search_range_years (ECalShellView *cal_shell_view) +{ + EShellBackend *backend; + EShellSettings *shell_settings; + gint value; + + backend = e_shell_view_get_shell_backend (E_SHELL_VIEW (cal_shell_view)); + shell_settings = e_shell_get_shell_settings (e_shell_backend_get_shell (backend)); + + value = e_shell_settings_get_int (shell_settings, "cal-search-range-years"); + if (value <= 0) + value = 10; + + return value; +} + +static gint +cal_time_t_ptr_compare (gconstpointer a, + gconstpointer b) +{ + const time_t *ta = a, *tb = b; + + return (ta ? *ta : 0) - (tb ? *tb : 0); +} + +static void cal_iterate_searching (ECalShellView *cal_shell_view); + +struct GenerateInstancesData +{ + ECalClient *client; + ECalShellView *cal_shell_view; + GCancellable *cancellable; +}; + +static void +cal_searching_instances_done_cb (gpointer user_data) +{ + struct GenerateInstancesData *gid = user_data; + + g_return_if_fail (gid != NULL); + g_return_if_fail (gid->cal_shell_view != NULL); + + if (!g_cancellable_is_cancelled (gid->cancellable)) { + gid->cal_shell_view->priv->search_pending_count--; + if (!gid->cal_shell_view->priv->search_pending_count) { + gid->cal_shell_view->priv->search_hit_cache = + g_slist_sort (gid->cal_shell_view->priv->search_hit_cache, cal_time_t_ptr_compare); + cal_iterate_searching (gid->cal_shell_view); + } + } + + g_object_unref (gid->cancellable); + g_free (gid); +} + +static gboolean +cal_searching_got_instance_cb (ECalComponent *comp, + time_t instance_start, + time_t instance_end, + gpointer user_data) +{ + struct GenerateInstancesData *gid = user_data; + ECalShellViewPrivate *priv; + ECalComponentDateTime dt; + time_t *value; + + g_return_val_if_fail (gid != NULL, FALSE); + + if (g_cancellable_is_cancelled (gid->cancellable)) + return FALSE; + + g_return_val_if_fail (gid->cal_shell_view != NULL, FALSE); + g_return_val_if_fail (gid->cal_shell_view->priv != NULL, FALSE); + + e_cal_component_get_dtstart (comp, &dt); + + if (dt.tzid && dt.value) { + icaltimezone *zone = NULL; + if (!e_cal_client_get_timezone_sync (gid->client, dt.tzid, &zone, gid->cancellable, NULL)) { + zone = NULL; + } + + if (g_cancellable_is_cancelled (gid->cancellable)) + return FALSE; + + if (zone) + instance_start = icaltime_as_timet_with_zone (*dt.value, zone); + } + + e_cal_component_free_datetime (&dt); + + priv = gid->cal_shell_view->priv; + value = g_new (time_t, 1); + *value = instance_start; + if (!g_slist_find_custom (priv->search_hit_cache, value, cal_time_t_ptr_compare)) + priv->search_hit_cache = g_slist_append (priv->search_hit_cache, value); + else + g_free (value); + + return TRUE; +} + +static void +cal_search_get_object_list_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + ECalClient *client = E_CAL_CLIENT (source); + ECalShellView *cal_shell_view = user_data; + GSList *icalcomps = NULL; + GError *error = NULL; + + g_return_if_fail (client != NULL); + g_return_if_fail (result != NULL); + g_return_if_fail (cal_shell_view != NULL); + + if (!e_cal_client_get_object_list_finish (client, result, &icalcomps, &error) || !icalcomps) { + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_clear_error (&error); + return; + } + + g_clear_error (&error); + cal_shell_view->priv->search_pending_count--; + if (!cal_shell_view->priv->search_pending_count) { + cal_shell_view->priv->search_hit_cache = + g_slist_sort (cal_shell_view->priv->search_hit_cache, cal_time_t_ptr_compare); + cal_iterate_searching (cal_shell_view); + } + } else { + GSList *iter; + GCancellable *cancellable; + time_t start, end; + + cancellable = e_activity_get_cancellable (cal_shell_view->priv->searching_activity); + start = time_add_day (cal_shell_view->priv->search_time, (-1) * cal_shell_view->priv->search_direction); + end = cal_shell_view->priv->search_time; + if (start > end) { + time_t tmp = start; + start = end; + end = tmp; + } + + for (iter = icalcomps; iter; iter = iter->next) { + icalcomponent *icalcomp = iter->data; + struct GenerateInstancesData *gid = g_new0 (struct GenerateInstancesData, 1); + + gid->client = client; + gid->cal_shell_view = cal_shell_view; + gid->cancellable = g_object_ref (cancellable); + + e_cal_client_generate_instances_for_object (client, icalcomp, start, end, cancellable, + cal_searching_got_instance_cb, gid, cal_searching_instances_done_cb); + } + + e_cal_client_free_icalcomp_slist (icalcomps); + } +} + +static gboolean +cal_searching_check_candidates (ECalShellView *cal_shell_view) +{ + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + GSList *iter; + time_t value, candidate = -1; + + g_return_val_if_fail (cal_shell_view != NULL, FALSE); + g_return_val_if_fail (cal_shell_view->priv != NULL, FALSE); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + if (!e_calendar_view_get_selected_time_range (calendar_view, &value, NULL)) + return FALSE; + + if (cal_shell_view->priv->search_direction > 0 && (view_type == GNOME_CAL_WEEK_VIEW || view_type == GNOME_CAL_MONTH_VIEW)) + value = time_add_day (value, 1); + + for (iter = cal_shell_view->priv->search_hit_cache; iter; iter = iter->next) { + time_t cache = *((time_t *) iter->data); + + /* list is sorted, once the search iteration is complete */ + if (cache > value) { + if (cal_shell_view->priv->search_direction > 0) + candidate = cache; + break; + } else if (cal_shell_view->priv->search_direction < 0 && cache != value) + candidate = cache; + } + + if (candidate > 0) { + gnome_calendar_goto (calendar, candidate); + return TRUE; + } + + return FALSE; +} + +static void +cal_searching_update_alert (ECalShellView *cal_shell_view, + const gchar *message) +{ + ECalShellViewPrivate *priv; + EShellContent *shell_content; + EAlert *alert; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + + if (priv->search_alert) { + e_alert_response (priv->search_alert, e_alert_get_default_response (priv->search_alert)); + priv->search_alert = NULL; + } + + if (!message) + return; + + alert = e_alert_new ("calendar:search-error-generic", message, NULL); + g_return_if_fail (alert != NULL); + + priv->search_alert = alert; + g_object_add_weak_pointer (G_OBJECT (alert), &priv->search_alert); + e_alert_start_timer (priv->search_alert, 5); + + shell_content = e_shell_view_get_shell_content (E_SHELL_VIEW (cal_shell_view)); + e_alert_sink_submit_alert (E_ALERT_SINK (shell_content), priv->search_alert); + g_object_unref (priv->search_alert); +} + +static void +cal_iterate_searching (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv; + GList *clients, *iter; + ECalModel *model; + time_t new_time, range1, range2; + icaltimezone *timezone; + const gchar *default_tzloc = NULL; + GCancellable *cancellable; + gchar *sexp, *start, *end; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + g_return_if_fail (priv->search_direction != 0); + g_return_if_fail (priv->search_pending_count == 0); + + cal_searching_update_alert (cal_shell_view, NULL); + + if (cal_searching_check_candidates (cal_shell_view)) { + if (priv->searching_activity) { + e_activity_set_state (priv->searching_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + } + + return; + } + + if (!priv->searching_activity) { + EShellBackend *shell_backend = e_shell_view_get_shell_backend (E_SHELL_VIEW (cal_shell_view)); + + cancellable = g_cancellable_new (); + priv->searching_activity = e_activity_new (); + e_activity_set_cancellable (priv->searching_activity, cancellable); + e_activity_set_state (priv->searching_activity, E_ACTIVITY_RUNNING); + e_activity_set_text (priv->searching_activity, + priv->search_direction > 0 ? + _("Searching next matching event") : + _("Searching previous matching event")); + + e_shell_backend_add_activity (shell_backend, priv->searching_activity); + } + + new_time = time_add_day (priv->search_time, priv->search_direction); + if (new_time > priv->search_max_time || new_time < priv->search_min_time) { + gchar *alert_msg; + gint range_years; + + /* would get out of bounds, stop searching */ + e_activity_set_state (priv->searching_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + + range_years = cal_searching_get_search_range_years (cal_shell_view); + alert_msg = g_strdup_printf ( + priv->search_direction > 0 ? + ngettext ("Cannot find matching event in the next %d year", + "Cannot find matching event in the next %d years", + range_years) : + ngettext ("Cannot find matching event in the previous %d year", + "Cannot find matching event in the previous %d years", + range_years), + range_years); + cal_searching_update_alert (cal_shell_view, alert_msg); + g_free (alert_msg); + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + + return; + } + + model = gnome_calendar_get_model ( + e_cal_shell_content_get_calendar (cal_shell_view->priv->cal_shell_content)); + clients = e_cal_model_get_client_list (model); + + if (!clients) { + e_activity_set_state (priv->searching_activity, E_ACTIVITY_COMPLETED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + + cal_searching_update_alert (cal_shell_view, _("Cannot search with no active calendar")); + + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + + return; + } + + timezone = e_cal_model_get_timezone (model); + range1 = priv->search_time; + range2 = time_add_day (range1, priv->search_direction); + if (range1 < range2) { + start = isodate_from_time_t (time_day_begin (range1)); + end = isodate_from_time_t (time_day_end (range2)); + } else { + start = isodate_from_time_t (time_day_begin (range2)); + end = isodate_from_time_t (time_day_end (range1)); + } + + if (timezone && timezone != icaltimezone_get_utc_timezone ()) + default_tzloc = icaltimezone_get_location (timezone); + if (!default_tzloc) + default_tzloc = ""; + + sexp = g_strdup_printf ( + "(and %s (occur-in-time-range? " + "(make-time \"%s\") " + "(make-time \"%s\") \"%s\"))", + e_cal_model_get_search_query (model), start, end, default_tzloc); + + g_free (start); + g_free (end); + + cancellable = e_activity_get_cancellable (priv->searching_activity); + g_list_foreach (clients, (GFunc) g_object_ref, NULL); + priv->search_pending_count = g_list_length (clients); + priv->search_time = new_time; + + for (iter = clients; iter; iter = iter->next) { + ECalClient *client = iter->data; + + e_cal_client_get_object_list (client, sexp, cancellable, cal_search_get_object_list_cb, cal_shell_view); + } + + g_list_free_full (clients, g_object_unref); + g_free (sexp); +} + +void +e_cal_shell_view_search_events (ECalShellView *cal_shell_view, + gboolean search_forward) +{ + ECalShellViewPrivate *priv = cal_shell_view->priv; + ECalShellContent *cal_shell_content; + GnomeCalendarViewType view_type; + ECalendarView *calendar_view; + GnomeCalendar *calendar; + time_t start_time = 0; + gint range_years; + + if (priv->searching_activity || !priv->search_direction) + e_cal_shell_view_search_stop (cal_shell_view); + + cal_shell_content = cal_shell_view->priv->cal_shell_content; + calendar = e_cal_shell_content_get_calendar (cal_shell_content); + view_type = gnome_calendar_get_view (calendar); + calendar_view = gnome_calendar_get_calendar_view (calendar, view_type); + + if (!e_calendar_view_get_selected_time_range (calendar_view, &start_time, NULL)) { + e_shell_view_update_actions (E_SHELL_VIEW (cal_shell_view)); + return; + } + + start_time = time_day_begin (start_time); + if (priv->search_direction) { + time_t cached_start, cached_end, tmp; + + cached_start = priv->search_time; + cached_end = time_add_day (cached_start, (-1) * priv->search_direction); + + if (priv->search_direction > 0) { + tmp = cached_start; + cached_start = cached_end; + cached_end = tmp; + } + + /* clear cached results if searching out of cached bounds */ + if (start_time < cached_start || start_time > cached_end) + e_cal_shell_view_search_stop (cal_shell_view); + } + + priv->search_direction = search_forward ? +30 : -30; + + if (cal_searching_check_candidates (cal_shell_view)) + return; + + range_years = cal_searching_get_search_range_years (cal_shell_view); + + priv->search_pending_count = 0; + priv->search_time = start_time; + priv->search_min_time = start_time - (range_years * 365 * 24 * 60 * 60); + priv->search_max_time = start_time + (range_years * 365 * 24 * 60 * 60); + + if (priv->search_min_time < 0) + priv->search_min_time = 0; + if (priv->search_hit_cache) { + g_slist_free_full (priv->search_hit_cache, g_free); + priv->search_hit_cache = NULL; + } + + cal_iterate_searching (cal_shell_view); +} + +void +e_cal_shell_view_search_stop (ECalShellView *cal_shell_view) +{ + ECalShellViewPrivate *priv; + + g_return_if_fail (cal_shell_view != NULL); + g_return_if_fail (cal_shell_view->priv != NULL); + + priv = cal_shell_view->priv; + + cal_searching_update_alert (cal_shell_view, NULL); + + if (priv->searching_activity) { + g_cancellable_cancel (e_activity_get_cancellable (priv->searching_activity)); + e_activity_set_state (priv->searching_activity, E_ACTIVITY_CANCELLED); + g_object_unref (priv->searching_activity); + priv->searching_activity = NULL; + } + + if (priv->search_hit_cache) { + g_slist_free_full (priv->search_hit_cache, g_free); + priv->search_hit_cache = NULL; + } + + priv->search_direction = 0; +} diff --git a/modules/calendar/e-cal-shell-view-private.h b/modules/calendar/e-cal-shell-view-private.h index ad4984a4ef..4fc5e24724 100644 --- a/modules/calendar/e-cal-shell-view-private.h +++ b/modules/calendar/e-cal-shell-view-private.h @@ -122,6 +122,15 @@ struct _ECalShellViewPrivate { EActivity *memopad_activity; EActivity *taskpad_activity; + /* Time-range searching */ + EActivity *searching_activity; + gpointer search_alert; /* weak pointer to EAlert * */ + gint search_pending_count; /* how many clients are pending */ + time_t search_time; /* current search time from */ + time_t search_min_time, search_max_time; /* time boundary for searching */ + gint search_direction; /* negative value is backward, positive is forward, zero is error; in days */ + GSList *search_hit_cache; /* pointers on time_t for matched events */ + GFileMonitor *monitors[CHECK_NB]; }; @@ -159,6 +168,11 @@ void e_cal_shell_view_update_search_filter (ECalShellView *cal_shell_view); void e_cal_shell_view_update_timezone (ECalShellView *cal_shell_view); +void e_cal_shell_view_search_events + (ECalShellView *cal_shell_view, + gboolean search_forward); +void e_cal_shell_view_search_stop + (ECalShellView *cal_shell_view); /* Memo Pad Utilities */ diff --git a/modules/calendar/e-cal-shell-view.c b/modules/calendar/e-cal-shell-view.c index ad3477c5ea..9554f7440d 100644 --- a/modules/calendar/e-cal-shell-view.c +++ b/modules/calendar/e-cal-shell-view.c @@ -81,6 +81,8 @@ cal_shell_view_execute_search (EShellView *shell_view) gchar *temp; gint value; + e_cal_shell_view_search_stop (E_CAL_SHELL_VIEW (shell_view)); + shell_window = e_shell_view_get_shell_window (shell_view); shell_content = e_shell_view_get_shell_content (shell_view); shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); @@ -239,6 +241,9 @@ cal_shell_view_execute_search (EShellView *shell_view) gnome_calendar_set_search_query ( calendar, query, range_search, start_range, end_range); g_free (query); + + /* update also actions, thus Find Prev/Next/Stop will be sensitive as expected */ + e_shell_view_update_actions (shell_view); } static icalproperty * @@ -318,9 +323,12 @@ cal_shell_view_update_actions (EShellView *shell_view) EShell *shell; GnomeCalendarViewType view_type; GnomeCalendar *calendar; + ECalModel *model; ECalendarView *view; GtkAction *action; GList *list, *iter; + const gchar *model_sexp; + gboolean is_searching; gboolean sensitive; gboolean visible; guint32 state; @@ -364,6 +372,11 @@ cal_shell_view_update_actions (EShellView *shell_view) calendar = e_cal_shell_content_get_calendar (cal_shell_content); view_type = gnome_calendar_get_view (calendar); view = gnome_calendar_get_calendar_view (calendar, view_type); + model = gnome_calendar_get_model (calendar); + model_sexp = e_cal_model_get_search_query (model); + is_searching = model_sexp && *model_sexp && + g_strcmp0 (model_sexp, "#t") != 0 && + g_strcmp0 (model_sexp, "(contains? \"summary\" \"\")") != 0; list = e_calendar_view_get_selected_events (view); n_selected = g_list_length (list); @@ -452,6 +465,15 @@ cal_shell_view_update_actions (EShellView *shell_view) sensitive = can_delete_primary_source; gtk_action_set_sensitive (action, sensitive); + action = ACTION (CALENDAR_SEARCH_PREV); + gtk_action_set_sensitive (action, is_searching); + + action = ACTION (CALENDAR_SEARCH_NEXT); + gtk_action_set_sensitive (action, is_searching); + + action = ACTION (CALENDAR_SEARCH_STOP); + gtk_action_set_sensitive (action, is_searching && priv->searching_activity != NULL); + action = ACTION (EVENT_DELEGATE); sensitive = (n_selected == 1) && editable && @@ -511,7 +533,7 @@ cal_shell_view_update_actions (EShellView *shell_view) static void cal_shell_view_class_init (ECalShellViewClass *class, - GTypeModule *type_module) + GTypeModule *type_module) { GObjectClass *object_class; EShellViewClass *shell_view_class; -- cgit v1.2.3