[epiphany/wip/dazzle: 1/10] wip: Use DzlSuggestionEntry
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/dazzle: 1/10] wip: Use DzlSuggestionEntry
- Date: Wed, 30 Aug 2017 01:46:06 +0000 (UTC)
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]