[gnome-calendar] search: Introduce a search engine



commit 6994723d79299718b62e324bce04ec463eede9e2
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sat Apr 27 22:11:13 2019 -0300

    search: Introduce a search engine
    
    This is architectured in such a way that it will
    allow us to introduce other searcheable terms in
    the future.
    
    All the functionality that in the past was in
    GcalManager is now spread across GcalSearchModel,
    GcalSearchHitEvent and GcalSearchEngine.

 src/gcal-context.c                 |  29 ++++
 src/gcal-context.h                 |   3 +
 src/gcal-manager.c                 |  72 ++------
 src/gcal-manager.h                 |   3 +
 src/meson.build                    |   5 +-
 src/search/gcal-search-button.c    |  88 +++++++++-
 src/search/gcal-search-engine.c    | 331 +++++++++++++++++++++++++++++++++++++
 src/search/gcal-search-engine.h    |  45 +++++
 src/search/gcal-search-hit-event.c | 225 +++++++++++++++++++++++++
 src/search/gcal-search-hit-event.h |  36 ++++
 src/search/gcal-search-hit.c       |  47 ++++++
 src/search/gcal-search-hit.h       |  45 +++++
 src/search/gcal-search-model.c     | 240 +++++++++++++++++++++++++++
 src/search/gcal-search-model.h     |  33 ++++
 14 files changed, 1140 insertions(+), 62 deletions(-)
---
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 <glib-object.h>
@@ -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 <math.h>
+
+#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",
                                                 "<Primary>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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <dazzle.h>
+
+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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "gcal-event.h"
+
+#include <dazzle.h>
+
+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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <dazzle.h>
+
+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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <dazzle.h>
+
+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 <georges stavracas gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]