diff --git a/src/gcal-context.c b/src/gcal-context.c index a49365bb..cd8b441e 100644 --- a/src/gcal-context.c +++ b/src/gcal-context.c @@ -33,6 +33,7 @@ struct _GcalContext GcalClock *clock; GoaClient *goa_client; GcalManager *manager; + GcalSearchEngine *search_engine; GSettings *settings; GcalTimeFormat time_format; GcalWeatherService *weather_service; @@ -49,6 +50,7 @@ enum PROP_CLOCK, PROP_GOA_CLIENT, PROP_MANAGER, + PROP_SEARCH_ENGINE, PROP_SETTINGS, PROP_TIME_FORMAT, PROP_TIMEZONE, @@ -140,6 +142,10 @@ gcal_context_get_property (GObject *object, g_value_set_object (value, self->manager); break; + case PROP_SEARCH_ENGINE: + g_value_set_object (value, self->search_engine); + break; + case PROP_SETTINGS: g_value_set_object (value, self->settings); break; @@ -172,6 +178,7 @@ gcal_context_set_property (GObject *object, case PROP_CLOCK: case PROP_GOA_CLIENT: case PROP_MANAGER: + case PROP_SEARCH_ENGINE: case PROP_SETTINGS: case PROP_TIME_FORMAT: case PROP_TIMEZONE: @@ -208,6 +215,12 @@ gcal_context_class_init (GcalContextClass *klass) GCAL_TYPE_MANAGER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_SEARCH_ENGINE] = g_param_spec_object ("search-engine", + "Search engine", + "Search engine", + GCAL_TYPE_SEARCH_ENGINE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_SETTINGS] = g_param_spec_object ("settings", "GNOME Calendar settings", "GNOME Calendar settings", @@ -244,6 +257,7 @@ gcal_context_init (GcalContext *self) self->manager = gcal_manager_new (); self->settings = g_settings_new ("org.gnome.calendar"); self->weather_service = gcal_weather_service_new (); + self->search_engine = gcal_search_engine_new (self); self->timezone_monitor = gcal_time_zone_monitor_new (); g_signal_connect_object (self->timezone_monitor, @@ -321,6 +335,21 @@ gcal_context_get_manager (GcalContext *self) return self->manager; } +/** + * gcal_context_get_search_engine: + * + * Retrieves the #GcalSearchEngine from @self. + * + * Returns: (transfer none): a #GcalSearchEngine + */ +GcalSearchEngine* +gcal_context_get_search_engine (GcalContext *self) +{ + g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); + + return self->search_engine; +} + /** * gcal_context_get_settings: * diff --git a/src/gcal-context.h b/src/gcal-context.h index 57b149be..497a47ac 100644 --- a/src/gcal-context.h +++ b/src/gcal-context.h @@ -23,6 +23,7 @@ #include "gcal-clock.h" #include "gcal-enums.h" #include "gcal-manager.h" +#include "gcal-search-engine.h" #include "weather/gcal-weather-service.h" #include @@ -41,6 +42,8 @@ GoaClient* gcal_context_get_goa_client (GcalContext GcalManager* gcal_context_get_manager (GcalContext *self); +GcalSearchEngine* gcal_context_get_search_engine (GcalContext *self); + GSettings* gcal_context_get_settings (GcalContext *self); GcalTimeFormat gcal_context_get_time_format (GcalContext *self); diff --git a/src/gcal-manager.c b/src/gcal-manager.c index 0d782f24..c39e3c2e 100644 --- a/src/gcal-manager.c +++ b/src/gcal-manager.c @@ -81,7 +81,6 @@ struct _GcalManager ECredentialsPrompter *credentials_prompter; ECalDataModel *e_data_model; - ECalDataModel *search_data_model; ECalDataModel *shell_search_data_model; ViewStateData *search_view_data; @@ -171,8 +170,6 @@ remove_source (GcalManager *self, e_cal_data_model_remove_client (self->e_data_model, e_source_get_uid (source)); - e_cal_data_model_remove_client (self->search_data_model, - e_source_get_uid (source)); unit = g_hash_table_lookup (self->clients, source); if (unit && unit->client) @@ -303,7 +300,6 @@ on_client_connected (GObject *source_object, if (enabled) { e_cal_data_model_add_client (self->e_data_model, client); - e_cal_data_model_add_client (self->search_data_model, client); if (self->shell_search_data_model != NULL) e_cal_data_model_add_client (self->shell_search_data_model, client); } @@ -655,7 +651,6 @@ gcal_manager_finalize (GObject *object) GCAL_ENTRY; g_clear_object (&self->e_data_model); - g_clear_object (&self->search_data_model); g_clear_object (&self->shell_search_data_model); if (self->search_view_data) @@ -1080,56 +1075,6 @@ gcal_manager_set_subscriber (GcalManager *self, GCAL_EXIT; } -/** - * gcal_manager_set_search_subscriber: - * @self: a #GcalManager - * @subscriber: a #ECalDataModelSubscriber - * @range_start: the start of the range - * @range_end: the end of the range - * - * Sets the @subscriber to show events between @range_start - * and @range_end. - */ -void -gcal_manager_set_search_subscriber (GcalManager *self, - ECalDataModelSubscriber *subscriber, - time_t range_start, - time_t range_end) -{ - GCAL_ENTRY; - - g_return_if_fail (GCAL_IS_MANAGER (self)); - - e_cal_data_model_subscribe (self->search_data_model, - subscriber, - range_start, - range_end); - - GCAL_EXIT; -} - -/** - * gcal_manager_set_query: - * @self: A #GcalManager instance - * @query: (nullable): query terms or %NULL - * - * Set the query terms of the #ECalDataModel or clear it if %NULL is - * passed - */ -void -gcal_manager_set_query (GcalManager *self, - const gchar *query) -{ - GCAL_ENTRY; - - g_return_if_fail (GCAL_IS_MANAGER (self)); - - e_cal_data_model_set_filter (self->search_data_model, - query != NULL ? query : "#t"); - - GCAL_EXIT; -} - /** * gcal_manager_query_client_data: * @@ -1246,7 +1191,6 @@ gcal_manager_enable_source (GcalManager *self, } e_cal_data_model_add_client (self->e_data_model, unit->client); - e_cal_data_model_add_client (self->search_data_model, unit->client); if (self->shell_search_data_model) e_cal_data_model_add_client (self->shell_search_data_model, unit->client); @@ -1291,7 +1235,6 @@ gcal_manager_disable_source (GcalManager *self, source_uid = e_source_get_uid (source); e_cal_data_model_remove_client (self->e_data_model, source_uid); - e_cal_data_model_remove_client (self->search_data_model, source_uid); if (self->shell_search_data_model != NULL) e_cal_data_model_remove_client (self->shell_search_data_model, source_uid); @@ -1821,12 +1764,9 @@ gcal_manager_startup (GcalManager *self) /* create data model */ self->e_data_model = e_cal_data_model_new (gcal_thread_submit_job); - self->search_data_model = e_cal_data_model_new (gcal_thread_submit_job); e_cal_data_model_set_expand_recurrences (self->e_data_model, TRUE); e_cal_data_model_set_timezone (self->e_data_model, e_cal_util_get_system_timezone ()); - e_cal_data_model_set_expand_recurrences (self->search_data_model, TRUE); - e_cal_data_model_set_timezone (self->search_data_model, e_cal_util_get_system_timezone ()); sources = e_source_registry_list_enabled (self->source_registry, E_SOURCE_EXTENSION_CALENDAR); self->sources_at_launch = g_list_length (sources); @@ -1838,3 +1778,15 @@ gcal_manager_startup (GcalManager *self) GCAL_EXIT; } + +ECalClient* +gcal_manager_get_client (GcalManager *self, + ESource *source) +{ + GcalManagerUnit *unit; + + g_return_val_if_fail (GCAL_IS_MANAGER (self), NULL); + + unit = g_hash_table_lookup (self->clients, source); + return unit ? unit->client : NULL; +} diff --git a/src/gcal-manager.h b/src/gcal-manager.h index 1939fe91..5e9ee964 100644 --- a/src/gcal-manager.h +++ b/src/gcal-manager.h @@ -121,6 +121,9 @@ GList* gcal_manager_get_shell_search_events (GcalManager void gcal_manager_startup (GcalManager *self); +ECalClient* gcal_manager_get_client (GcalManager *self, + ESource *source); + G_END_DECLS #endif /* __GCAL_MANAGER_H__ */ diff --git a/src/meson.build b/src/meson.build index eefa81d3..f2142960 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,10 @@ gcal_deps = [ sources = files( 'search/gcal-search-button.c', + 'search/gcal-search-engine.c', + 'search/gcal-search-hit.c', + 'search/gcal-search-hit-event.c', + 'search/gcal-search-model.c', 'utils/gcal-date-time-utils.c', 'utils/gcal-thread-utils.c', 'utils/gcal-utils.c', @@ -51,7 +55,6 @@ sources = files( 'gcal-night-light-monitor.c', 'gcal-quick-add-popover.c', 'gcal-recurrence.c', - 'gcal-search-popover.c', 'gcal-shell-search-provider.c', 'gcal-source-dialog.c', 'gcal-time-selector.c', diff --git a/src/search/gcal-search-button.c b/src/search/gcal-search-button.c index 669b3508..e9cc9b89 100644 --- a/src/search/gcal-search-button.c +++ b/src/search/gcal-search-button.c @@ -23,10 +23,16 @@ #include "gcal-context.h" #include "gcal-search-button.h" +#include + +#define MIN_WIDTH 450 + struct _GcalSearchButton { DzlSuggestionButton parent; + GCancellable *cancellable; + GcalContext *context; }; @@ -46,6 +52,74 @@ static GParamSpec *properties [N_PROPS]; * Callbacks */ +static void +position_suggestion_popover_func (DzlSuggestionEntry *entry, + GdkRectangle *area, + gboolean *is_absolute, + gpointer user_data) +{ + gint new_width; + +#define RIGHT_MARGIN 6 + + dzl_suggestion_entry_window_position_func (entry, area, is_absolute, NULL); + + new_width = MAX (area->width * 2 / 5, MIN_WIDTH); + area->x += area->width - new_width; + area->width = new_width - RIGHT_MARGIN; + area->y -= 3; + +#undef RIGHT_MARGIN + +} + +static void +on_search_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr (GListModel) model = NULL; + g_autoptr (GError) error = NULL; + DzlSuggestionEntry *entry; + GcalSearchButton *self; + + self = GCAL_SEARCH_BUTTON (user_data); + model = gcal_search_engine_search_finish (GCAL_SEARCH_ENGINE (source_object), result, &error); + + entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); + dzl_suggestion_entry_set_model (entry, model); +} + +static void +on_search_entry_changed_cb (GcalSearchButton *self) +{ + g_autofree gchar *sexp_query = NULL; + DzlSuggestionEntry *entry; + GcalSearchEngine *search_engine; + const gchar *typed_text; + + entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); + typed_text = dzl_suggestion_entry_get_typed_text (entry); + + g_cancellable_cancel (self->cancellable); + + if (dzl_str_empty0 (typed_text)) + { + g_debug ("Cancelling search"); + dzl_suggestion_entry_set_model (entry, NULL); + return; + } + + sexp_query = g_strdup_printf ("(contains? \"summary\" \"%s\")", typed_text); + search_engine = gcal_context_get_search_engine (self->context); + gcal_search_engine_search (search_engine, + sexp_query, + 50, + self->cancellable, + on_search_finished_cb, + g_object_ref (self)); +} + static void on_unfocus_action_activated_cb (GSimpleAction *action, GVariant *param, @@ -89,6 +163,8 @@ gcal_search_button_finalize (GObject *object) { GcalSearchButton *self = (GcalSearchButton *)object; + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); g_clear_object (&self->context); G_OBJECT_CLASS (gcal_search_button_parent_class)->finalize (object); @@ -177,8 +253,18 @@ gcal_search_button_init (GcalSearchButton *self) gtk_widget_insert_action_group (GTK_WIDGET (self), "search", G_ACTION_GROUP (group)); entry = dzl_suggestion_button_get_entry (DZL_SUGGESTION_BUTTON (self)); - controller = dzl_shortcut_controller_find (GTK_WIDGET (entry)); + g_signal_connect_object (entry, + "changed", + G_CALLBACK (on_search_entry_changed_cb), + self, + G_CONNECT_SWAPPED); + dzl_suggestion_entry_set_position_func (entry, + position_suggestion_popover_func, + self, + NULL); + + controller = dzl_shortcut_controller_find (GTK_WIDGET (entry)); dzl_shortcut_controller_add_command_callback (controller, "org.gnome.calendar.search", "f", diff --git a/src/search/gcal-search-engine.c b/src/search/gcal-search-engine.c new file mode 100644 index 00000000..d25382b6 --- /dev/null +++ b/src/search/gcal-search-engine.c @@ -0,0 +1,331 @@ +/* gcal-search-engine.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalSearchEngine" + +#include "gcal-context.h" +#include "gcal-date-time-utils.h" +#include "gcal-search-engine.h" +#include "gcal-search-model.h" +#include "gcal-thread-utils.h" + +#include + +typedef struct +{ + GcalSearchEngine *engine; + gchar *query; + gint max_results; + GDateTime *range_start; + GDateTime *range_end; +} SearchData; + +struct _GcalSearchEngine +{ + GObject parent; + + ECalDataModel *data_model; + + GcalContext *context; +}; + +G_DEFINE_TYPE (GcalSearchEngine, gcal_search_engine, G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_CONTEXT, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + + +/* + * Auxiliary methods + */ + +static void +search_data_free (gpointer data) +{ + SearchData *search_data = data; + + if (!data) + return; + + gcal_clear_date_time (&search_data->range_start); + gcal_clear_date_time (&search_data->range_end); + g_clear_pointer (&search_data->query, g_free); + g_clear_object (&search_data->engine); + g_slice_free (SearchData, data); +} + + +/* + * Callbacks + */ + +static void +search_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr (GcalSearchModel) model = NULL; + GcalSearchEngine *self; + SearchData *data; + time_t start; + time_t end; + + self = GCAL_SEARCH_ENGINE (source_object); + data = (SearchData*) task_data; + start = g_date_time_to_unix (data->range_start); + end = g_date_time_to_unix (data->range_end); + + model = gcal_search_model_new (cancellable, data->max_results); + e_cal_data_model_set_filter (self->data_model, data->query); + + e_cal_data_model_subscribe (self->data_model, + E_CAL_DATA_MODEL_SUBSCRIBER (model), + start, + end); + + //e_cal_data_model_unsubscribe (self->data_model, E_CAL_DATA_MODEL_SUBSCRIBER (model)); + + g_task_return_pointer (task, g_steal_pointer (&model), g_object_unref); +} + +static void +on_manager_source_added_cb (GcalManager *manager, + ESource *source, + gboolean enabled, + GcalSearchEngine *self) +{ + ECalClient *client; + + g_debug ("Removing source %s from search results", e_source_get_uid (source)); + + client = gcal_manager_get_client (manager, source); + + if (enabled) + e_cal_data_model_add_client (self->data_model, client); +} + +static void +on_manager_source_enabled_cb (GcalManager *manager, + ESource *source, + gboolean enabled, + GcalSearchEngine *self) +{ + g_debug ("Changing source %s from search results", e_source_get_uid (source)); + + if (enabled) + { + ECalClient *client; + + client = gcal_manager_get_client (manager, source); + e_cal_data_model_add_client (self->data_model, client); + } + else + { + e_cal_data_model_remove_client (self->data_model, e_source_get_uid (source)); + } +} + +static void +on_manager_source_removed_cb (GcalManager *manager, + ESource *source, + GcalSearchEngine *self) +{ + g_debug ("Removing source %s from search results", e_source_get_uid (source)); + + e_cal_data_model_remove_client (self->data_model, e_source_get_uid (source)); +} + +static void +on_timezone_changed_cb (GcalContext *context, + GParamSpec *pspec, + GcalSearchEngine *self) +{ + g_debug ("Timezone changed"); +} + + +/* + * GObject overrides + */ + +static void +gcal_search_engine_finalize (GObject *object) +{ + GcalSearchEngine *self = (GcalSearchEngine *)object; + + g_clear_object (&self->context); + g_clear_object (&self->data_model); + + G_OBJECT_CLASS (gcal_search_engine_parent_class)->finalize (object); +} + +static void +gcal_search_engine_constructed (GObject *object) +{ + GcalSearchEngine *self = (GcalSearchEngine *)object; + GcalManager *manager; + + G_OBJECT_CLASS (gcal_search_engine_parent_class)->constructed (object); + + /* Setup the data model */ + self->data_model = e_cal_data_model_new (gcal_thread_submit_job); + e_cal_data_model_set_expand_recurrences (self->data_model, TRUE); + e_cal_data_model_set_timezone (self->data_model, e_cal_util_get_system_timezone ()); + + + manager = gcal_context_get_manager (self->context); + g_signal_connect_object (manager, "source-added", G_CALLBACK (on_manager_source_added_cb), self, 0); + g_signal_connect_object (manager, "source-removed", G_CALLBACK (on_manager_source_removed_cb), self, 0); + g_signal_connect_object (manager, "source-enabled", G_CALLBACK (on_manager_source_enabled_cb), self, 0); + + g_signal_connect_object (self->context, + "notify::timezone", + G_CALLBACK (on_timezone_changed_cb), + self, + 0); +} + +static void +gcal_search_engine_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalSearchEngine *self = GCAL_SEARCH_ENGINE (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_object (value, self->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_engine_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalSearchEngine *self = GCAL_SEARCH_ENGINE (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_assert (self->context == NULL); + self->context = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_engine_class_init (GcalSearchEngineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_search_engine_finalize; + object_class->constructed = gcal_search_engine_constructed; + object_class->get_property = gcal_search_engine_get_property; + object_class->set_property = gcal_search_engine_set_property; + + /** + * GcalSearchEngine::context: + * + * The #GcalContext of the application. + */ + properties[PROP_CONTEXT] = g_param_spec_object ("context", + "Data context", + "Data context", + GCAL_TYPE_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gcal_search_engine_init (GcalSearchEngine *self) +{ +} + +GcalSearchEngine * +gcal_search_engine_new (GcalContext *context) +{ + return g_object_new (GCAL_TYPE_SEARCH_ENGINE, + "context", context, + NULL); +} + +void +gcal_search_engine_search (GcalSearchEngine *self, + const gchar *search_query, + gint max_results, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GDateTime) now = NULL; + g_autoptr (GTask) task = NULL; + SearchData *data = NULL; + GTimeZone *timezone; + + g_return_if_fail (GCAL_IS_SEARCH_ENGINE (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + timezone = gcal_context_get_timezone (self->context); + now = g_date_time_new_now (timezone); + + data = g_slice_new0 (SearchData); + data->engine = g_object_ref (self); + data->query = g_strdup (search_query); + data->max_results = max_results; + data->range_start = g_date_time_add_weeks (now, -1); + data->range_end = g_date_time_add_weeks (now, 3); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gcal_search_engine_search); + g_task_set_priority (task, G_PRIORITY_LOW); + g_task_set_task_data (task, data, (GDestroyNotify) search_data_free); + + g_task_run_in_thread (task, search_func); +} + +GListModel* +gcal_search_engine_search_finish (GcalSearchEngine *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GCAL_IS_SEARCH_ENGINE (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/search/gcal-search-engine.h b/src/search/gcal-search-engine.h new file mode 100644 index 00000000..f59a16bf --- /dev/null +++ b/src/search/gcal-search-engine.h @@ -0,0 +1,45 @@ +/* gcal-search-engine.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_SEARCH_ENGINE (gcal_search_engine_get_type()) +G_DECLARE_FINAL_TYPE (GcalSearchEngine, gcal_search_engine, GCAL, SEARCH_ENGINE, GObject) + +typedef struct _GcalContext GcalContext; + +GcalSearchEngine* gcal_search_engine_new (GcalContext *context); + +void gcal_search_engine_search (GcalSearchEngine *self, + const gchar *search_query, + gint max_results, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GListModel* gcal_search_engine_search_finish (GcalSearchEngine *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/search/gcal-search-hit-event.c b/src/search/gcal-search-hit-event.c new file mode 100644 index 00000000..35214c2f --- /dev/null +++ b/src/search/gcal-search-hit-event.c @@ -0,0 +1,225 @@ +/* gcal-search-hit-event.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalSearchHitEvent" + +#include "gcal-search-hit.h" +#include "gcal-search-hit-event.h" +#include "gcal-utils.h" + +struct _GcalSearchHitEvent +{ + DzlSuggestion parent; + + GcalEvent *event; +}; + +static void gcal_search_hit_interface_init (GcalSearchHitInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GcalSearchHitEvent, gcal_search_hit_event, DZL_TYPE_SUGGESTION, + G_IMPLEMENT_INTERFACE (GCAL_TYPE_SEARCH_HIT, gcal_search_hit_interface_init)) + +enum +{ + PROP_0, + PROP_EVENT, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +/* + * Auxiliary methods + */ + +static void +set_event (GcalSearchHitEvent *self, + GcalEvent *event) +{ + g_autofree gchar *date_string = NULL; + DzlSuggestion *suggestion; + + self->event = g_object_ref (event); + + suggestion = DZL_SUGGESTION (self); + dzl_suggestion_set_id (suggestion, gcal_event_get_uid (event)); + dzl_suggestion_set_title (suggestion, gcal_event_get_summary (event)); + + /* FIXME: use a better date description */ + date_string = g_date_time_format (gcal_event_get_date_start (event), "%x %X %z"); + dzl_suggestion_set_subtitle (suggestion, date_string); +} + + +/* + * DzlSuggestion overrides + */ + +static cairo_surface_t* +gcal_search_hit_event_get_icon_surface (DzlSuggestion *suggestion, + GtkWidget *widget) +{ + GcalSearchHitEvent *self; + cairo_surface_t *surface; + ESource *source; + GdkRGBA color; + + self = GCAL_SEARCH_HIT_EVENT (suggestion); + source = gcal_event_get_source (self->event); + + get_color_name_from_source (source, &color); + surface = get_circle_surface_from_color (&color, 16); + + /* Inject our custom style class into the given widget */ + gtk_style_context_add_class (gtk_widget_get_style_context (widget), "calendar-color-image"); + + return surface; +} + + +/* + * GcalSearchHit interface + */ + +static gint +gcal_search_hit_event_get_priority (GcalSearchHit *search_hit) +{ + return 0; +} + +static gint +gcal_search_hit_event_compare (GcalSearchHit *a, + GcalSearchHit *b) +{ + GcalEvent *event_a; + GcalEvent *event_b; + time_t now_utc; + + g_assert (GCAL_IS_SEARCH_HIT_EVENT (a)); + g_assert (GCAL_IS_SEARCH_HIT_EVENT (b)); + + event_a = GCAL_SEARCH_HIT_EVENT (a)->event; + event_b = GCAL_SEARCH_HIT_EVENT (b)->event; + now_utc = time (NULL); + + return -gcal_event_compare_with_current (event_a, event_b, now_utc); +} + +static void +gcal_search_hit_interface_init (GcalSearchHitInterface *iface) +{ + iface->get_priority = gcal_search_hit_event_get_priority; + iface->compare = gcal_search_hit_event_compare; +} + + +/* + * GObject overrides + */ + +static void +gcal_search_hit_event_finalize (GObject *object) +{ + GcalSearchHitEvent *self = (GcalSearchHitEvent *)object; + + g_clear_object (&self->event); + + G_OBJECT_CLASS (gcal_search_hit_event_parent_class)->finalize (object); +} + +static void +gcal_search_hit_event_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalSearchHitEvent *self = GCAL_SEARCH_HIT_EVENT (object); + + switch (prop_id) + { + case PROP_EVENT: + g_value_set_object (value, self->event); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_event_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalSearchHitEvent *self = GCAL_SEARCH_HIT_EVENT (object); + + switch (prop_id) + { + case PROP_EVENT: + g_assert (self->event == NULL); + set_event (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_search_hit_event_class_init (GcalSearchHitEventClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + DzlSuggestionClass *suggestion_class = DZL_SUGGESTION_CLASS (klass); + + object_class->finalize = gcal_search_hit_event_finalize; + object_class->get_property = gcal_search_hit_event_get_property; + object_class->set_property = gcal_search_hit_event_set_property; + + suggestion_class->get_icon_surface = gcal_search_hit_event_get_icon_surface; + + properties[PROP_EVENT] = g_param_spec_object ("event", + "Event", + "Event", + GCAL_TYPE_EVENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gcal_search_hit_event_init (GcalSearchHitEvent *self) +{ +} + +GcalSearchHitEvent * +gcal_search_hit_event_new (GcalEvent *event) +{ + return g_object_new (GCAL_TYPE_SEARCH_HIT_EVENT, + "event", event, + NULL); +} + +GcalEvent* +gcal_search_hit_event_get_event (GcalSearchHitEvent *self) +{ + g_return_val_if_fail (GCAL_IS_SEARCH_HIT_EVENT (self), NULL); + + return self->event; +} diff --git a/src/search/gcal-search-hit-event.h b/src/search/gcal-search-hit-event.h new file mode 100644 index 00000000..1eb9c001 --- /dev/null +++ b/src/search/gcal-search-hit-event.h @@ -0,0 +1,36 @@ +/* gcal-search-hit-event.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gcal-event.h" + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_SEARCH_HIT_EVENT (gcal_search_hit_event_get_type()) +G_DECLARE_FINAL_TYPE (GcalSearchHitEvent, gcal_search_hit_event, GCAL, SEARCH_HIT_EVENT, DzlSuggestion) + +GcalSearchHitEvent* gcal_search_hit_event_new (GcalEvent *event); + +GcalEvent* gcal_search_hit_event_get_event (GcalSearchHitEvent *self); + +G_END_DECLS diff --git a/src/search/gcal-search-hit.c b/src/search/gcal-search-hit.c new file mode 100644 index 00000000..519202e3 --- /dev/null +++ b/src/search/gcal-search-hit.c @@ -0,0 +1,47 @@ +/* gcal-search-hit.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gcal-search-hit.h" + +G_DEFINE_INTERFACE (GcalSearchHit, gcal_search_hit, DZL_TYPE_SUGGESTION) + +static void +gcal_search_hit_default_init (GcalSearchHitInterface *iface) +{ +} + +gint +gcal_search_hit_get_priority (GcalSearchHit *self) +{ + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (self), 0); + g_return_val_if_fail (GCAL_SEARCH_HIT_GET_IFACE (self)->get_priority, 0); + + return GCAL_SEARCH_HIT_GET_IFACE (self)->get_priority (self); +} + +gint +gcal_search_hit_compare (GcalSearchHit *a, + GcalSearchHit *b) +{ + g_return_val_if_fail (GCAL_IS_SEARCH_HIT (a), 0); + g_return_val_if_fail (GCAL_SEARCH_HIT_GET_IFACE (a)->compare, 0); + + return GCAL_SEARCH_HIT_GET_IFACE (a)->compare (a, b); +} diff --git a/src/search/gcal-search-hit.h b/src/search/gcal-search-hit.h new file mode 100644 index 00000000..d76be0c5 --- /dev/null +++ b/src/search/gcal-search-hit.h @@ -0,0 +1,45 @@ +/* gcal-search-hit.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_SEARCH_HIT (gcal_search_hit_get_type ()) +G_DECLARE_INTERFACE (GcalSearchHit, gcal_search_hit, GCAL, SEARCH_HIT, DzlSuggestion) + +struct _GcalSearchHitInterface +{ + GTypeInterface parent; + + gint (*get_priority) (GcalSearchHit *self); + + gint (*compare) (GcalSearchHit *a, + GcalSearchHit *b); +}; + +gint gcal_search_hit_get_priority (GcalSearchHit *self); + +gint gcal_search_hit_compare (GcalSearchHit *a, + GcalSearchHit *b); + +G_END_DECLS diff --git a/src/search/gcal-search-model.c b/src/search/gcal-search-model.c new file mode 100644 index 00000000..d8871017 --- /dev/null +++ b/src/search/gcal-search-model.c @@ -0,0 +1,240 @@ +/* gcal-search-model.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalSearchModel" + +#include "e-cal-data-model.h" +#include "gcal-debug.h" +#include "gcal-search-hit.h" +#include "gcal-search-hit-event.h" +#include "gcal-search-model.h" +#include "gcal-utils.h" + +#include + +struct _GcalSearchModel +{ + GObject parent; + + GCancellable *cancellable; + gint max_results; + + GListModel *model; +}; + +static void e_cal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface); + +static void g_list_model_interface_init (GListModelInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GcalSearchModel, gcal_search_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, + e_cal_data_model_subscriber_interface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, + g_list_model_interface_init)) + +/* + * Callbacks + */ + +static void +on_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GcalSearchModel *self) +{ + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); +} + + +/* + * ECalDataModelSubscriber interface + */ + +static gint +compare_search_hits_cb (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + GcalSearchHit *hit_a, *hit_b; + gint result; + + hit_a = (GcalSearchHit *) a; + hit_b = (GcalSearchHit *) b; + + /* Priority */ + result = gcal_search_hit_get_priority (hit_a) - gcal_search_hit_get_priority (hit_b); + + if (result != 0) + return result; + + /* Compare func */ + return gcal_search_hit_compare (hit_a, hit_b); +} + +static void +gcal_search_model_component_added (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *component) +{ + g_autoptr (GcalSearchHitEvent) search_hit = NULL; + g_autoptr (GcalEvent) event = NULL; + g_autoptr (GError) error = NULL; + GcalSearchModel *self; + ESource *source; + + self = GCAL_SEARCH_MODEL (subscriber); + + if (g_list_model_get_n_items (self->model) > self->max_results) + return; + + source = e_client_get_source (E_CLIENT (client)); + event = gcal_event_new (source, component, &error); + + if (error) + { + g_warning ("Error adding event to search results: %s", error->message); + return; + } + + GCAL_TRACE_MSG ("Adding search hit '%s'", gcal_event_get_summary (event)); + + search_hit = gcal_search_hit_event_new (event); + + g_list_store_insert_sorted (G_LIST_STORE (self->model), + search_hit, + compare_search_hits_cb, + self); +} + +static void +gcal_search_model_component_modified (ECalDataModelSubscriber *subscriber, + ECalClient *client, + ECalComponent *comp) +{ +} + +static void +gcal_search_model_component_removed (ECalDataModelSubscriber *subscriber, + ECalClient *client, + const gchar *uid, + const gchar *rid) +{ +} + +static void +gcal_search_model_freeze (ECalDataModelSubscriber *subscriber) +{ +} + +static void +gcal_search_model_thaw (ECalDataModelSubscriber *subscriber) +{ +} + +static void +e_cal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface) +{ + iface->component_added = gcal_search_model_component_added; + iface->component_modified = gcal_search_model_component_modified; + iface->component_removed = gcal_search_model_component_removed; + iface->freeze = gcal_search_model_freeze; + iface->thaw = gcal_search_model_thaw; +} + + +/* + * GListModel interface + */ + +static GType +gcal_search_model_get_item_type (GListModel *model) +{ + return DZL_TYPE_SUGGESTION; +} + +static guint +gcal_search_model_get_n_items (GListModel *model) +{ + GcalSearchModel *self = (GcalSearchModel *)model; + return g_list_model_get_n_items (self->model); +} + +static gpointer +gcal_search_model_get_item (GListModel *model, + guint position) +{ + GcalSearchModel *self = (GcalSearchModel *)model; + return g_list_model_get_item (self->model, position); +} + +static void +g_list_model_interface_init (GListModelInterface *iface) +{ + iface->get_item_type = gcal_search_model_get_item_type; + iface->get_n_items = gcal_search_model_get_n_items; + iface->get_item = gcal_search_model_get_item; +} + + +/* + * GObject overrides + */ + +static void +gcal_search_model_finalize (GObject *object) +{ + GcalSearchModel *self = (GcalSearchModel *)object; + + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->cancellable); + g_clear_object (&self->model); + + G_OBJECT_CLASS (gcal_search_model_parent_class)->finalize (object); +} + +static void +gcal_search_model_class_init (GcalSearchModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_search_model_finalize; +} + +static void +gcal_search_model_init (GcalSearchModel *self) +{ + self->model = (GListModel*) g_list_store_new (DZL_TYPE_SUGGESTION); + g_signal_connect_object (self->model, "items-changed", G_CALLBACK (on_model_items_changed_cb), self, 0); +} + +GcalSearchModel * +gcal_search_model_new (GCancellable *cancellable, + gint max_results) +{ + GcalSearchModel *model; + + model = g_object_new (GCAL_TYPE_SEARCH_MODEL, NULL); + model->max_results = max_results; + model->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + + return model; +} diff --git a/src/search/gcal-search-model.h b/src/search/gcal-search-model.h new file mode 100644 index 00000000..9b7c1fc8 --- /dev/null +++ b/src/search/gcal-search-model.h @@ -0,0 +1,33 @@ +/* gcal-search-model.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_SEARCH_MODEL (gcal_search_model_get_type()) +G_DECLARE_FINAL_TYPE (GcalSearchModel, gcal_search_model, GCAL, SEARCH_MODEL, GObject) + +GcalSearchModel* gcal_search_model_new (GCancellable *cancellable, + gint max_results); + +G_END_DECLS