[epiphany/wip/dazzle: 1/10] wip: Use DzlSuggestionEntry



commit a8095bdcb2d8d27e978592a1df493bec87997bcb
Author: Michael Catanzaro <mcatanzaro igalia com>
Date:   Sat Aug 26 23:14:38 2017 -0500

    wip: Use DzlSuggestionEntry

 lib/widgets/contrib/gd-two-lines-renderer.c |  621 --------------------------
 lib/widgets/contrib/gd-two-lines-renderer.h |   75 ----
 lib/widgets/ephy-location-entry.c           |  290 +------------
 lib/widgets/ephy-location-entry.h           |   18 +-
 lib/widgets/meson.build                     |    2 +-
 meson.build                                 |    1 +
 src/ephy-completion-model.c                 |  625 ---------------------------
 src/ephy-completion-model.h                 |   53 ---
 src/ephy-location-controller.c              |  133 +------
 src/ephy-suggestion-model.c                 |  419 ++++++++++++++++++
 src/ephy-suggestion-model.h                 |   43 ++
 src/meson.build                             |    5 +-
 tests/ephy-completion-model-test.c          |  101 -----
 tests/ephy-location-entry-test.c            |  160 -------
 tests/meson.build                           |   14 +-
 15 files changed, 488 insertions(+), 2072 deletions(-)
---
diff --git a/lib/widgets/ephy-location-entry.c b/lib/widgets/ephy-location-entry.c
index 89c92ea..a3c843f 100644
--- a/lib/widgets/ephy-location-entry.c
+++ b/lib/widgets/ephy-location-entry.c
@@ -33,8 +33,8 @@
 #include "ephy-signal-accumulator.h"
 #include "ephy-title-widget.h"
 #include "ephy-uri-helpers.h"
-#include "gd-two-lines-renderer.h"
 
+#include <dazzle.h>
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
@@ -54,7 +54,7 @@
  */
 
 struct _EphyLocationEntry {
-  GtkEntry parent_instance;
+  DzlSuggestionEntry parent_instance;
 
   GtkTreeModel *model;
 
@@ -91,12 +91,6 @@ struct _EphyLocationEntry {
 
 static gboolean ephy_location_entry_reset_internal (EphyLocationEntry *, gboolean);
 
-static void extracell_data_func (GtkCellLayout   *cell_layout,
-                                 GtkCellRenderer *cell,
-                                 GtkTreeModel    *tree_model,
-                                 GtkTreeIter     *iter,
-                                 gpointer         data);
-
 enum {
   PROP_0,
   PROP_ADDRESS,
@@ -115,7 +109,7 @@ static gint signals[LAST_SIGNAL] = { 0 };
 
 static void ephy_location_entry_title_widget_interface_init (EphyTitleWidgetInterface *iface);
 
-G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, GTK_TYPE_ENTRY,
+G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, DZL_TYPE_SUGGESTION_ENTRY,
                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_TITLE_WIDGET,
                                                 ephy_location_entry_title_widget_interface_init))
 
@@ -480,6 +474,10 @@ entry_key_press_cb (GtkEntry          *entry,
     ephy_location_entry_activate (location_entry);
   }
 
+  /* FIXME: Why do we have to activate the location entry manually? */
+  if (event->keyval == GDK_KEY_Return)
+    g_signal_emit_by_name (entry, "activate");
+
   return FALSE;
 }
 
@@ -531,48 +529,6 @@ entry_activate_after_cb (GtkEntry          *entry,
   }
 }
 
-static gboolean
-match_selected_cb (GtkEntryCompletion *completion,
-                   GtkTreeModel       *model,
-                   GtkTreeIter        *iter,
-                   EphyLocationEntry  *entry)
-{
-  char *item = NULL;
-  guint state;
-
-  gtk_tree_model_get (model, iter,
-                      entry->action_col, &item, -1);
-  if (item == NULL) return FALSE;
-
-  ephy_gui_get_current_event (NULL, &state, NULL);
-
-  entry->needs_reset = (state == GDK_CONTROL_MASK ||
-                        state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
-
-  ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), item);
-  /* gtk_im_context_reset (GTK_ENTRY (entry)->im_context); */
-  g_signal_emit_by_name (entry, "activate");
-
-  g_free (item);
-
-  return TRUE;
-}
-
-static void
-action_activated_after_cb (GtkEntryCompletion *completion,
-                           gint                index,
-                           EphyLocationEntry  *lentry)
-{
-  guint state, button;
-
-  ephy_gui_get_current_event (NULL, &state, &button);
-  if ((state == GDK_CONTROL_MASK ||
-       state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ||
-      button == 2) {
-    ephy_location_entry_reset_internal (lentry, TRUE);
-  }
-}
-
 static void
 entry_clear_activate_cb (GtkMenuItem       *item,
                          EphyLocationEntry *entry)
@@ -828,238 +784,6 @@ schedule_dns_prefetch (EphyLocationEntry *entry, guint interval, const gchar *ur
 }
 #endif
 
-static gboolean
-cursor_on_match_cb (GtkEntryCompletion *completion,
-                    GtkTreeModel       *model,
-                    GtkTreeIter        *iter,
-                    EphyLocationEntry  *le)
-{
-  char *url = NULL;
-  GtkWidget *entry;
-
-  gtk_tree_model_get (model, iter,
-                      le->url_col,
-                      &url, -1);
-  entry = gtk_entry_completion_get_entry (completion);
-
-  /* Prevent the update so we keep the highlight from our input.
-   * See textcell_data_func().
-   */
-  le->block_update = TRUE;
-  gtk_entry_set_text (GTK_ENTRY (entry), url);
-  gtk_editable_set_position (GTK_EDITABLE (entry), -1);
-  le->block_update = FALSE;
-
-#if 0
-/* FIXME: Refactor the DNS prefetch, this is a layering violation */
-  schedule_dns_prefetch (le, 250, (const gchar *)url);
-#endif
-
-  g_free (url);
-
-  return TRUE;
-}
-
-static void
-extracell_data_func (GtkCellLayout   *cell_layout,
-                     GtkCellRenderer *cell,
-                     GtkTreeModel    *tree_model,
-                     GtkTreeIter     *iter,
-                     gpointer         data)
-{
-  EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (data);
-  gboolean is_bookmark = FALSE;
-  GValue visible = { 0, };
-
-  gtk_tree_model_get (tree_model, iter,
-                      entry->extra_col, &is_bookmark,
-                      -1);
-
-  if (is_bookmark)
-    g_object_set (cell,
-                  "icon-name", "user-bookmarks-symbolic",
-                  NULL);
-
-  g_value_init (&visible, G_TYPE_BOOLEAN);
-  g_value_set_boolean (&visible, is_bookmark);
-  g_object_set_property (G_OBJECT (cell), "visible", &visible);
-  g_value_unset (&visible);
-}
-
-/**
- * ephy_location_entry_set_match_func:
- * @entry: an #EphyLocationEntry widget
- * @match_func: a #GtkEntryCompletionMatchFunc
- * @user_data: user_data to pass to the @match_func
- * @notify: a #GDestroyNotify, like the one given to
- * gtk_entry_completion_set_match_func
- *
- * Sets the match_func for the internal #GtkEntryCompletion to @match_func.
- *
- **/
-void
-ephy_location_entry_set_match_func (EphyLocationEntry          *entry,
-                                    GtkEntryCompletionMatchFunc match_func,
-                                    gpointer                    user_data,
-                                    GDestroyNotify              notify)
-{
-  GtkEntryCompletion *completion;
-
-  completion = gtk_entry_get_completion (GTK_ENTRY (entry));
-  gtk_entry_completion_set_match_func (completion, match_func, user_data, notify);
-}
-
-/**
- * ephy_location_entry_set_completion:
- * @entry: an #EphyLocationEntry widget
- * @model: the #GtkModel for the completion
- * @text_col: column id to access #GtkModel relevant data
- * @action_col: column id to access #GtkModel relevant data
- * @keywords_col: column id to access #GtkModel relevant data
- * @relevance_col: column id to access #GtkModel relevant data
- * @url_col: column id to access #GtkModel relevant data
- * @extra_col: column id to access #GtkModel relevant data
- * @favicon_col: column id to access #GtkModel relevant data
- *
- * Initializes @entry to have a #GtkEntryCompletion using @model as the
- * internal #GtkModel. The *_col arguments are for internal data retrieval from
- * @model, like when setting the text property of one of the #GtkCellRenderer
- * of the completion.
- *
- **/
-void
-ephy_location_entry_set_completion (EphyLocationEntry *entry,
-                                    GtkTreeModel      *model,
-                                    guint              text_col,
-                                    guint              action_col,
-                                    guint              keywords_col,
-                                    guint              relevance_col,
-                                    guint              url_col,
-                                    guint              extra_col,
-                                    guint              favicon_col)
-{
-  GtkEntryCompletion *completion;
-  GtkCellRenderer *cell;
-
-  entry->text_col = text_col;
-  entry->action_col = action_col;
-  entry->keywords_col = keywords_col;
-  entry->relevance_col = relevance_col;
-  entry->url_col = url_col;
-  entry->extra_col = extra_col;
-  entry->favicon_col = favicon_col;
-
-  completion = gtk_entry_completion_new ();
-  gtk_entry_completion_set_model (completion, model);
-  g_signal_connect (completion, "match-selected",
-                    G_CALLBACK (match_selected_cb), entry);
-  g_signal_connect_after (completion, "action-activated",
-                          G_CALLBACK (action_activated_after_cb), entry);
-
-  cell = gtk_cell_renderer_pixbuf_new ();
-  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
-                              cell, FALSE);
-  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
-                                 cell, "pixbuf", favicon_col);
-
-  /* Pixel-perfect aligment with the location entry favicon
-   * (16x16). Consider that this /might/ depend on the theme.
-   *
-   * The GtkEntryCompletion can not be themed so we work-around
-   * that with padding and fixed sizes.
-   * For the first cell, this is:
-   *
-   * ___+++++iiiiiiiiiiiiiiii++__ttt...bbb++++++__
-   *
-   * _ = widget spacing, can not be handled (3 px)
-   * + = padding (5 px) (ICON_PADDING_LEFT)
-   * i = the icon (16 px) (ICON_CONTENT_WIDTH)
-   * + = padding (2 px) (ICON_PADDING_RIGHT) (cut by the fixed_size)
-   * _ = spacing between cells, can not be handled (2 px)
-   * t = the text (expands)
-   * b = bookmark icon (16 px)
-   * + = padding (6 px) (BKMK_PADDING_RIGHT)
-   * _ = widget spacing, can not be handled (2 px)
-   *
-   * Each character is a pixel.
-   *
-   * The text cell and the bookmark icon cell are much more
-   * flexible in its aligment, because they do not have to align
-   * with anything in the entry.
-   */
-
-#define ROW_PADDING_VERT 4
-
-#define ICON_PADDING_LEFT 5
-#define ICON_CONTENT_WIDTH 16
-#define ICON_PADDING_RIGHT 9
-
-#define ICON_CONTENT_HEIGHT 16
-
-#define TEXT_PADDING_LEFT 0
-
-#define BKMK_PADDING_RIGHT 6
-
-  gtk_cell_renderer_set_padding
-    (cell, ICON_PADDING_LEFT, ROW_PADDING_VERT);
-  gtk_cell_renderer_set_fixed_size
-    (cell,
-    (ICON_PADDING_LEFT + ICON_CONTENT_WIDTH + ICON_PADDING_RIGHT),
-    ICON_CONTENT_HEIGHT);
-  gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
-
-  cell = gd_two_lines_renderer_new ();
-  g_object_set (cell,
-                "ellipsize", PANGO_ELLIPSIZE_END,
-                "text-lines", 2,
-                NULL);
-  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
-                              cell, TRUE);
-  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
-                                 cell, "text", text_col);
-  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
-                                 cell, "line-two", url_col);
-
-  /* Pixel-perfect aligment with the text in the location entry.
-   * See above.
-   */
-  gtk_cell_renderer_set_padding
-    (cell, TEXT_PADDING_LEFT, ROW_PADDING_VERT);
-  gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
-
-  /*
-   * As the width of the entry completion is known in advance
-   * (as big as the entry you are completing on), we can set
-   * any fixed width (the 1 is just this random number here)
-   * Since the height is known too, we avoid computing the actual
-   * sizes of the cells, which takes a lot of CPU time and does
-   * not get used anyway.
-   */
-  gtk_cell_renderer_set_fixed_size (cell, 1, -1);
-  gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (cell), 2);
-
-  cell = gtk_cell_renderer_pixbuf_new ();
-  g_object_set (cell, "follow-state", TRUE, NULL);
-  gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (completion),
-                            cell, FALSE);
-  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
-                                      cell, extracell_data_func,
-                                      entry,
-                                      NULL);
-
-  /* Pixel-perfect aligment. This just keeps the same margin from
-   * the border than the favicon on the other side. See above. */
-  gtk_cell_renderer_set_padding
-    (cell, BKMK_PADDING_RIGHT, ROW_PADDING_VERT);
-
-  g_object_set (completion, "inline-selection", TRUE, NULL);
-  g_signal_connect (completion, "cursor-on-match",
-                    G_CALLBACK (cursor_on_match_cb), entry);
-
-  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
-  g_object_unref (completion);
-}
-
 /**
  * ephy_location_entry_get_can_undo:
  * @entry: an #EphyLocationEntry widget
diff --git a/lib/widgets/ephy-location-entry.h b/lib/widgets/ephy-location-entry.h
index 490524a..a826f75 100644
--- a/lib/widgets/ephy-location-entry.h
+++ b/lib/widgets/ephy-location-entry.h
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include <dazzle.h>
 #include <gtk/gtk.h>
 
 #include "ephy-security-levels.h"
@@ -31,7 +32,7 @@ G_BEGIN_DECLS
 
 #define EPHY_TYPE_LOCATION_ENTRY (ephy_location_entry_get_type())
 
-G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, GtkEntry)
+G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, DzlSuggestionEntry)
 
 typedef enum {
   EPHY_LOCATION_ENTRY_BOOKMARK_ICON_HIDDEN,
@@ -41,21 +42,6 @@ typedef enum {
 
 GtkWidget      *ephy_location_entry_new                        (void);
 
-void            ephy_location_entry_set_completion             (EphyLocationEntry *entry,
-                                                                GtkTreeModel      *model,
-                                                                guint              text_col,
-                                                                guint              action_col,
-                                                                guint              keywords_col,
-                                                                guint              relevance_col,
-                                                                guint              url_col,
-                                                                guint              extra_col,
-                                                                guint              favicon_col);
-
-void            ephy_location_entry_set_match_func             (EphyLocationEntry           *entry,
-                                                                GtkEntryCompletionMatchFunc  match_func,
-                                                                gpointer                     user_data,
-                                                                GDestroyNotify               notify);
-
 gboolean        ephy_location_entry_get_can_undo               (EphyLocationEntry *entry);
 
 gboolean        ephy_location_entry_get_can_redo               (EphyLocationEntry *entry);
diff --git a/lib/widgets/meson.build b/lib/widgets/meson.build
index 519b36d..6b82a1f 100644
--- a/lib/widgets/meson.build
+++ b/lib/widgets/meson.build
@@ -9,7 +9,6 @@ enums = gnome.mkenums('ephy-widgets',
 )
 
 libephywidgets_sources = [
-  'contrib/gd-two-lines-renderer.c',
   'contrib/nautilus-floating-bar.c',
   'ephy-certificate-dialog.c',
   'ephy-downloads-popover.c',
@@ -32,6 +31,7 @@ libephywidgets_deps = [
   gio_dep,
   glib_dep,
   gtk_dep,
+  libdazzle_dep,
   libsoup_dep,
   webkit2gtk_dep
 ]
diff --git a/meson.build b/meson.build
index 5a83411..b5c1951 100644
--- a/meson.build
+++ b/meson.build
@@ -60,6 +60,7 @@ hogweed_dep = dependency('hogweed', version: nettle_requirement)
 icu_uc_dep = dependency('icu-uc', version: '>= 4.6')
 iso_codes_dep = dependency('iso-codes', version: '>= 0.35')
 json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4')
+libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.25.90')
 libnotify_dep = dependency('libnotify', version: '>= 0.5.1')
 libsecret_dep = dependency('libsecret-1', version: '>= 0.14')
 libsoup_dep = dependency('libsoup-2.4', version: '>= 2.48.0')
diff --git a/src/ephy-location-controller.c b/src/ephy-location-controller.c
index 3ccbcfe..97d44e2 100644
--- a/src/ephy-location-controller.c
+++ b/src/ephy-location-controller.c
@@ -22,8 +22,6 @@
 #include "config.h"
 #include "ephy-location-controller.h"
 
-
-#include "ephy-completion-model.h"
 #include "ephy-debug.h"
 #include "ephy-dnd.h"
 #include "ephy-embed-container.h"
@@ -31,9 +29,11 @@
 #include "ephy-link.h"
 #include "ephy-location-entry.h"
 #include "ephy-shell.h"
+#include "ephy-suggestion-model.h"
 #include "ephy-title-widget.h"
 #include "ephy-widgets-type-builtins.h"
 
+#include <dazzle.h>
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
 #include <string.h>
@@ -78,16 +78,6 @@ G_DEFINE_TYPE_WITH_CODE (EphyLocationController, ephy_location_controller, G_TYP
                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
                                                 NULL))
 
-static gboolean
-match_func (GtkEntryCompletion *completion,
-            const char         *key,
-            GtkTreeIter        *iter,
-            gpointer            data)
-{
-  /* We want every row in the model to show up. */
-  return TRUE;
-}
-
 static void
 entry_drag_data_received_cb (GtkWidget *widget,
                              GdkDragContext *context,
@@ -181,33 +171,18 @@ entry_activate_cb (GtkEntry               *entry,
 }
 
 static void
-update_done_cb (EphyHistoryService *service,
-                gboolean            success,
-                gpointer            result_data,
-                gpointer            user_data)
-{
-  /* FIXME: this hack is needed for the completion entry popup
-   * to resize smoothly. See:
-   * https://bugzilla.gnome.org/show_bug.cgi?id=671074 */
-  gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (user_data));
-}
-
-static void
 user_changed_cb (GtkWidget *widget, EphyLocationController *controller)
 {
   const char *address;
-  GtkTreeModel *model;
-  GtkEntryCompletion *completion;
+  GListModel *model;
 
   address = ephy_title_widget_get_address (EPHY_TITLE_WIDGET (widget));
 
   LOG ("user_changed_cb, address %s", address);
 
-  completion = gtk_entry_get_completion (GTK_ENTRY (widget));
-  model = gtk_entry_completion_get_model (completion);
+  model = dzl_suggestion_entry_get_model (DZL_SUGGESTION_ENTRY (widget));
 
-  ephy_completion_model_update_for_string (EPHY_COMPLETION_MODEL (model), address,
-                                           update_done_cb, completion);
+  ephy_suggestion_model_query_async (EPHY_SUGGESTION_MODEL (model), address, NULL, NULL, NULL);
 }
 
 static void
@@ -291,84 +266,12 @@ switch_page_cb (GtkNotebook            *notebook,
 }
 
 static void
-action_activated_cb (GtkEntryCompletion     *completion,
-                     int                     index,
-                     EphyLocationController *controller)
-{
-  GtkWidget *entry;
-  char *content;
-  char *url;
-  char **engine_names;
-
-  entry = gtk_entry_completion_get_entry (completion);
-  content = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
-  if (content == NULL)
-    return;
-
-  engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
-  url = ephy_search_engine_manager_build_search_address (controller->search_engine_manager,
-                                                         engine_names[index],
-                                                         content);
-  g_strfreev (engine_names);
-
-  ephy_link_open (EPHY_LINK (controller), url, NULL,
-                  ephy_link_flags_from_current_event ());
-  g_free (content);
-  g_free (url);
-}
-
-static void
-fill_entry_completion_with_actions (GtkEntryCompletion     *completion,
-                                    EphyLocationController *controller)
-{
-  char **engine_names;
-
-  engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
-
-  controller->num_search_engines_actions = 0;
-
-  for (guint i = 0; engine_names[i] != NULL; i++) {
-    gtk_entry_completion_insert_action_text (completion, i, engine_names[i]);
-    controller->num_search_engines_actions++;
-  }
-
-  g_strfreev (engine_names);
-}
-
-static void
-add_completion_actions (EphyLocationController *controller,
-                        EphyLocationEntry      *lentry)
-{
-  GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (lentry));
-
-  fill_entry_completion_with_actions (completion, controller);
-  g_signal_connect (completion, "action_activated",
-                    G_CALLBACK (action_activated_cb), controller);
-}
-
-static void
-search_engines_changed_cb (EphySearchEngineManager *manager,
-                           gpointer  data)
-{
-  EphyLocationController *controller;
-  GtkEntryCompletion *completion;
-
-  controller = EPHY_LOCATION_CONTROLLER (data);
-  completion = gtk_entry_get_completion (GTK_ENTRY (controller->title_widget));
-
-  for (guint i = 0; i < controller->num_search_engines_actions; i++)
-    gtk_entry_completion_delete_action (completion, 0);
-
-  fill_entry_completion_with_actions (completion, controller);
-}
-
-static void
 ephy_location_controller_constructed (GObject *object)
 {
   EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
   EphyHistoryService *history_service;
   EphyBookmarksManager *bookmarks_manager;
-  EphyCompletionModel *model;
+  EphySuggestionModel *model;
   GtkWidget *notebook, *widget;
 
   G_OBJECT_CLASS (ephy_location_controller_parent_class)->constructed (object);
@@ -388,27 +291,13 @@ ephy_location_controller_constructed (GObject *object)
 
   history_service = ephy_embed_shell_get_global_history_service (ephy_embed_shell_get_default ());
   bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  model = ephy_completion_model_new (history_service, bookmarks_manager);
-  ephy_location_entry_set_completion (EPHY_LOCATION_ENTRY (controller->title_widget),
-                                      GTK_TREE_MODEL (model),
-                                      EPHY_COMPLETION_TEXT_COL,
-                                      EPHY_COMPLETION_ACTION_COL,
-                                      EPHY_COMPLETION_KEYWORDS_COL,
-                                      EPHY_COMPLETION_RELEVANCE_COL,
-                                      EPHY_COMPLETION_URL_COL,
-                                      EPHY_COMPLETION_EXTRA_COL,
-                                      EPHY_COMPLETION_FAVICON_COL);
+  model = ephy_suggestion_model_new (history_service, bookmarks_manager);
+  dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (controller->title_widget), G_LIST_MODEL (model));
   g_object_unref (model);
 
-  ephy_location_entry_set_match_func (EPHY_LOCATION_ENTRY (controller->title_widget),
-                                      match_func,
-                                      controller->title_widget,
-                                      NULL);
-
-  add_completion_actions (controller, EPHY_LOCATION_ENTRY (controller->title_widget));
-
-  g_signal_connect_object (controller->search_engine_manager, "changed",
-                           G_CALLBACK (search_engines_changed_cb), controller, 0);
+/* FIXME: What to do with search engines? Add them to the model? Probably not?
+ * Make the bangs mandatory?
+ */
 
   g_object_bind_property (controller, "editable",
                           controller->title_widget, "editable",
diff --git a/src/ephy-suggestion-model.c b/src/ephy-suggestion-model.c
new file mode 100644
index 0000000..a843ef5
--- /dev/null
+++ b/src/ephy-suggestion-model.c
@@ -0,0 +1,419 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright (C) 2017 Christian Hergert <chergert redhat 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/>.
+ */
+
+#include "config.h"
+#include "ephy-suggestion-model.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+
+#define MAX_COMPLETION_HISTORY_URLS 8
+
+struct _EphySuggestionModel {
+  GObject               parent;
+  EphyHistoryService   *history_service;
+  EphyBookmarksManager *bookmarks_manager;
+  GSequence            *items;
+  GSList               *search_terms;
+};
+
+enum {
+  PROP_0,
+  PROP_BOOKMARKS_MANAGER,
+  PROP_HISTORY_SERVICE,
+  N_PROPS
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphySuggestionModel, ephy_suggestion_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ephy_suggestion_model_finalize (GObject *object)
+{
+  EphySuggestionModel *self = (EphySuggestionModel *)object;
+
+  g_clear_object (&self->bookmarks_manager);
+  g_clear_object (&self->history_service);
+  g_clear_pointer (&self->items, g_sequence_free);
+
+  g_slist_free_full (self->search_terms, (GDestroyNotify)g_regex_unref);
+
+  G_OBJECT_CLASS (ephy_suggestion_model_parent_class)->finalize (object);
+}
+
+static void
+ephy_suggestion_model_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (object);
+
+  switch (prop_id) {
+    case PROP_HISTORY_SERVICE:
+      g_value_set_object (value, self->history_service);
+      break;
+    case PROP_BOOKMARKS_MANAGER:
+      g_value_set_object (value, self->bookmarks_manager);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_suggestion_model_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (object);
+
+  switch (prop_id) {
+    case PROP_HISTORY_SERVICE:
+      self->history_service = g_value_dup_object (value);
+      break;
+    case PROP_BOOKMARKS_MANAGER:
+      self->bookmarks_manager = g_value_dup_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_suggestion_model_class_init (EphySuggestionModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ephy_suggestion_model_finalize;
+  object_class->get_property = ephy_suggestion_model_get_property;
+  object_class->set_property = ephy_suggestion_model_set_property;
+
+  properties [PROP_BOOKMARKS_MANAGER] =
+    g_param_spec_object ("bookmarks-manager",
+                         "Bookmarks Manager",
+                         "The bookmarks manager for suggestions",
+                         EPHY_TYPE_BOOKMARKS_MANAGER,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HISTORY_SERVICE] =
+    g_param_spec_object ("history-service",
+                         "History Service",
+                         "The history service for suggestions",
+                         EPHY_TYPE_HISTORY_SERVICE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ephy_suggestion_model_init (EphySuggestionModel *self)
+{
+  self->items = g_sequence_new (g_object_unref);
+}
+
+static GType
+ephy_suggestion_model_get_item_type (GListModel *model)
+{
+  return DZL_TYPE_SUGGESTION;
+}
+
+static guint
+ephy_suggestion_model_get_n_items (GListModel *model)
+{
+  EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (model);
+
+  return g_sequence_get_length (self->items);
+}
+
+static gpointer
+ephy_suggestion_model_get_item (GListModel *model,
+                                guint       position)
+{
+  EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (model);
+  GSequenceIter *iter;
+  DzlSuggestion *suggestion;
+
+  iter = g_sequence_get_iter_at_pos (self->items, position);
+  suggestion = g_sequence_get (iter);
+
+  return g_object_ref (suggestion);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ephy_suggestion_model_get_item_type;
+  iface->get_item = ephy_suggestion_model_get_item;
+  iface->get_n_items = ephy_suggestion_model_get_n_items;
+}
+
+EphySuggestionModel *
+ephy_suggestion_model_new (EphyHistoryService   *history_service,
+                           EphyBookmarksManager *bookmarks_manager)
+{
+  g_return_val_if_fail (EPHY_IS_HISTORY_SERVICE (history_service), NULL);
+  g_return_val_if_fail (EPHY_IS_BOOKMARKS_MANAGER (bookmarks_manager), NULL);
+
+  return g_object_new (EPHY_TYPE_SUGGESTION_MODEL,
+                       "history-service", history_service,
+                       "bookmarks-manager", bookmarks_manager,
+                       NULL);
+}
+
+static void
+update_search_terms (EphySuggestionModel *self,
+                     const char          *text)
+{
+  const char *current;
+  const char *ptr;
+  char *tmp;
+  char *term;
+  GRegex *term_regex;
+  GRegex *quote_regex;
+  gint count;
+  gboolean inside_quotes = FALSE;
+
+  g_assert (EPHY_IS_SUGGESTION_MODEL (self));
+
+  if (self->search_terms) {
+    g_slist_free_full (self->search_terms, (GDestroyNotify)g_regex_unref);
+    self->search_terms = NULL;
+  }
+
+  quote_regex = g_regex_new ("\"", G_REGEX_OPTIMIZE,
+                             G_REGEX_MATCH_NOTEMPTY, NULL);
+
+  /*
+   * This code loops through the string using pointer arythmetics.
+   * Although the string we are handling may contain UTF-8 chars
+   * this works because only ASCII chars affect what is actually
+   * copied from the string as a search term.
+   */
+  for (count = 0, current = ptr = text; ptr[0] != '\0'; ptr++, count++) {
+    /*
+     * If we found a double quote character; we will
+     * consume bytes up until the next quote, or
+     * end of line;
+     */
+    if (ptr[0] == '"')
+      inside_quotes = !inside_quotes;
+
+    /*
+     * If we found a space, and we are not looking for a
+     * closing double quote, or if the next char is the
+     * end of the string, append what we have already as
+     * a search term.
+     */
+    if (((ptr[0] == ' ') && (!inside_quotes)) || ptr[1] == '\0') {
+      /*
+       * We special-case the end of the line because
+       * we would otherwise not copy the last character
+       * of the search string, since the for loop will
+       * stop before that.
+       */
+      if (ptr[1] == '\0')
+        count++;
+
+      /*
+       * remove quotes, and quote any regex-sensitive
+       * characters
+       */
+      tmp = g_regex_escape_string (current, count);
+      term = g_regex_replace (quote_regex, tmp, -1, 0,
+                              "", G_REGEX_MATCH_NOTEMPTY, NULL);
+      g_strstrip (term);
+      g_free (tmp);
+
+      /* we don't want empty search terms */
+      if (term[0] != '\0') {
+        term_regex = g_regex_new (term,
+                                  G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+                                  G_REGEX_MATCH_NOTEMPTY, NULL);
+        self->search_terms = g_slist_append (self->search_terms, term_regex);
+      }
+      g_free (term);
+
+      /* count will be incremented by the for loop */
+      count = -1;
+      current = ptr + 1;
+    }
+  }
+
+  g_regex_unref (quote_regex);
+}
+
+static gboolean
+should_add_bookmark_to_model (EphySuggestionModel *self,
+                              const char          *search_string,
+                              const char          *title,
+                              const char          *location)
+{
+  gboolean ret = TRUE;
+
+  if (self->search_terms) {
+    GSList *iter;
+    GRegex *current = NULL;
+
+    for (iter = self->search_terms; iter != NULL; iter = iter->next) {
+      current = (GRegex *)iter->data;
+      if ((!g_regex_match (current, title ? title : "", G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+          (!g_regex_match (current, location ? location : "", G_REGEX_MATCH_NOTEMPTY, NULL))) {
+        ret = FALSE;
+        break;
+      }
+    }
+  }
+
+  return ret;
+}
+
+static void
+query_completed_cb (EphyHistoryService *service,
+                    gboolean            success,
+                    gpointer            result_data,
+                    gpointer            user_data)
+{
+  GTask *task = user_data;
+  EphySuggestionModel *self;
+  const gchar *query;
+  GSequence *bookmarks;
+  GList *urls = NULL;
+  guint removed;
+  guint added = 0;
+
+  self = g_task_get_source_object (task);
+  query = g_task_get_task_data (task);
+
+  removed = g_sequence_get_length (self->items);
+
+  g_clear_pointer (&self->items, g_sequence_free);
+  self->items = g_sequence_new (g_object_unref);
+
+  /* Add bookmarks */
+  bookmarks = ephy_bookmarks_manager_get_bookmarks (self->bookmarks_manager);
+  for (GSequenceIter *iter = g_sequence_get_begin_iter (bookmarks);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter)) {
+    EphyBookmark *bookmark;
+    const char *url, *title;
+
+    bookmark = g_sequence_get (iter);
+
+    url = ephy_bookmark_get_url (bookmark);
+    title = ephy_bookmark_get_title (bookmark);
+
+    if (should_add_bookmark_to_model (self, query, title, url)) {
+      DzlSuggestion *suggestion;
+      char *escaped_url = g_markup_escape_text (url, -1);
+      char *escaped_title = g_markup_escape_text (title, -1);
+
+      suggestion = g_object_new (DZL_TYPE_SUGGESTION,
+                                 "title", escaped_url,
+                                 "subtitle", escaped_title,
+                                 "id", url,
+                                 NULL);
+      g_sequence_append (self->items, suggestion);
+      added++;
+
+      g_free (escaped_url);
+      g_free (escaped_title);
+    }
+  }
+
+  /* History */
+  urls = (GList *)result_data;
+
+  for (const GList *p = g_list_last (urls); p != NULL; p = p->prev) {
+    EphyHistoryURL *url = (EphyHistoryURL *)p->data;
+    DzlSuggestion *suggestion;
+    char *escaped_url = g_markup_escape_text (url->url, -1);
+    char *escaped_title = g_markup_escape_text (url->title, -1);
+
+    suggestion = g_object_new (DZL_TYPE_SUGGESTION,
+                               "icon-name", "web-browser-symbolic",
+                               "id", url->url,
+                               "title", escaped_url,
+                               "subtitle", escaped_title,
+                               NULL);
+    g_sequence_prepend (self->items, suggestion);
+    added++;
+
+    g_free (escaped_url);
+    g_free (escaped_title);
+  }
+
+  g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
+
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+}
+
+void
+ephy_suggestion_model_query_async (EphySuggestionModel *self,
+                                   const gchar         *query,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  GTask *task = NULL;
+  char **strings;
+  GList *qlist = NULL;
+
+  g_return_if_fail (EPHY_IS_SUGGESTION_MODEL (self));
+  g_return_if_fail (query != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ephy_suggestion_model_query_async);
+  g_task_set_task_data (task, g_strdup (query), g_free);
+
+  /* Split the search string. */
+  strings = g_strsplit (query, " ", -1);
+  for (guint i = 0; strings[i]; i++)
+    qlist = g_list_append (qlist, g_strdup (strings[i]));
+
+  update_search_terms (self, query);
+
+  ephy_history_service_find_urls (self->history_service,
+                                  0, 0,
+                                  MAX_COMPLETION_HISTORY_URLS, 0,
+                                  qlist,
+                                  EPHY_HISTORY_SORT_MOST_VISITED,
+                                  cancellable,
+                                  (EphyHistoryJobCallback)query_completed_cb,
+                                  task);
+
+  g_strfreev (strings);
+}
+
+gboolean
+ephy_suggestion_model_query_finish (EphySuggestionModel  *self,
+                                    GAsyncResult         *result,
+                                    GError              **error)
+{
+  g_return_val_if_fail (EPHY_IS_SUGGESTION_MODEL (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/ephy-suggestion-model.h b/src/ephy-suggestion-model.h
new file mode 100644
index 0000000..613e9aa
--- /dev/null
+++ b/src/ephy-suggestion-model.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright (C) 2017 Christian Hergert <chergert redhat 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "ephy-history-service.h"
+#include "ephy-bookmarks-manager.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SUGGESTION_MODEL (ephy_suggestion_model_get_type())
+
+G_DECLARE_FINAL_TYPE (EphySuggestionModel, ephy_suggestion_model, EPHY, SUGGESTION_MODEL, GObject)
+
+EphySuggestionModel *ephy_suggestion_model_new          (EphyHistoryService    *history_service,
+                                                         EphyBookmarksManager  *bookmarks_manager);
+void                 ephy_suggestion_model_query_async  (EphySuggestionModel   *self,
+                                                         const gchar           *query,
+                                                         GCancellable          *cancellable,
+                                                         GAsyncReadyCallback    callback,
+                                                         gpointer               user_data);
+gboolean             ephy_suggestion_model_query_finish (EphySuggestionModel   *self,
+                                                         GAsyncResult          *result,
+                                                         GError               **error);
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index 8c9e97e..b6e2197 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -24,7 +24,6 @@ libephymain_sources = [
   'clear-data-dialog.c',
   'cookies-dialog.c',
   'ephy-action-helper.c',
-  'ephy-completion-model.c',
   'ephy-encoding-dialog.c',
   'ephy-encoding-row.c',
   'ephy-header-bar.c',
@@ -36,6 +35,7 @@ libephymain_sources = [
   'ephy-search-engine-dialog.c',
   'ephy-session.c',
   'ephy-shell.c',
+  'ephy-suggestion-model.c',
   'ephy-window.c',
   'passwords-dialog.c',
   'popup-commands.c',
@@ -90,6 +90,7 @@ codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated',
   namespace: 'Ephy'
 )
 
+'''
 search_provider_sources = [
   'search-provider/ephy-search-provider.c',
   'search-provider/ephy-search-provider-main.c',
@@ -103,7 +104,7 @@ executable('epiphany-search-provider',
   install_dir: libexecdir,
   install_rpath: pkglibdir
 )
-
+'''
 
 resource_files = files('resources/epiphany.gresource.xml')
 resources = gnome.compile_resources('epiphany-resources',
diff --git a/tests/meson.build b/tests/meson.build
index 11edde2..ee320ef 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -9,13 +9,7 @@ if get_option('enable_unit_tests')
   # ephytestutils_dep = declare_dependency(
   #   link_with: libephytestutils
   # )
-
-  completion_model_test = executable('test-ephy-completion-model',
-    'ephy-completion-model-test.c',
-    dependencies: ephymain_dep
-  )
-  test('Completion model test', completion_model_test)
-
+  #
   # FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=778153
   # download_test = executable('test-ephy-download',
   #   'ephy-download-test.c',
@@ -55,12 +49,6 @@ if get_option('enable_unit_tests')
   )
   test('History test', history_test)
 
-  location_entry_test = executable('test-location-entry',
-    'ephy-location-entry-test.c',
-    dependencies: ephymain_dep
-  )
-  test('Location entry test', location_entry_test)
-
   migration_test = executable('test-ephy-migration',
     'ephy-migration-test.c',
     dependencies: ephymain_dep



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