aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorXan Lopez <xan@igalia.com>2012-02-29 00:39:45 +0800
committerXan Lopez <xan@igalia.com>2012-03-07 04:49:44 +0800
commit8ad6302a3f9e1a867f3dd61605196d22e9bd2f86 (patch)
tree0e5167f79659daa565fe455ae3c0a80f95e057ae /src
parent306fb5a64f9b347fde9cf99ee7a594f04e9d7601 (diff)
downloadgsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar.gz
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar.bz2
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar.lz
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar.xz
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.tar.zst
gsoc2013-epiphany-8ad6302a3f9e1a867f3dd61605196d22e9bd2f86.zip
Hook the new history to the URL entry
We now fetch the history from the SQL backend using a service thread, and merge the results with our old bookmark data.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/ephy-completion-model.c950
-rw-r--r--src/ephy-completion-model.h16
-rw-r--r--src/ephy-location-controller.c57
4 files changed, 422 insertions, 602 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 70f5574d0..56e699b77 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -92,6 +92,7 @@ libephymain_la_CPPFLAGS = \
-I$(top_srcdir)/embed \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/lib/egg \
+ -I$(top_srcdir)/lib/history \
-I$(top_srcdir)/lib/widgets \
-I$(top_srcdir)/src/bookmarks \
-DEXTENSIONS_DIR=\""$(libdir)/epiphany/$(EPIPHANY_API_VERSION)/extensions"\" \
diff --git a/src/ephy-completion-model.c b/src/ephy-completion-model.c
index ef671850f..0936a0ac3 100644
--- a/src/ephy-completion-model.c
+++ b/src/ephy-completion-model.c
@@ -1,6 +1,6 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
- * Copyright © 2002 Jorn Baayen <jorn@nl.linux.org>
+ * Copyright © 2012 Igalia S.L.
*
* 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
@@ -21,636 +21,484 @@
#include "config.h"
#include "ephy-completion-model.h"
+#include "ephy-browse-history.h"
+#include "ephy-embed-shell.h"
#include "ephy-favicon-cache.h"
#include "ephy-history.h"
-#include "ephy-node.h"
#include "ephy-shell.h"
+#include "ephy-tree-model-node.h"
#include <string.h>
-static void ephy_completion_model_class_init (EphyCompletionModelClass *klass);
-static void ephy_completion_model_init (EphyCompletionModel *model);
-static void ephy_completion_model_tree_model_init (GtkTreeModelIface *iface);
+G_DEFINE_TYPE (EphyCompletionModel, ephy_completion_model, GTK_TYPE_LIST_STORE)
#define EPHY_COMPLETION_MODEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_COMPLETION_MODEL, EphyCompletionModelPrivate))
-struct _EphyCompletionModelPrivate
-{
- EphyHistory *history_service;
- EphyBookmarks *bookmarks_service;
- EphyFaviconCache *favicon_cache;
-
- EphyNode *history;
- EphyNode *bookmarks;
- int stamp;
-};
+struct _EphyCompletionModelPrivate {
+ EphyBrowseHistory *browse_history;
+ EphyHistory *legacy_history_service;
+ EphyFaviconCache *favicon_cache;
-enum
-{
- HISTORY_GROUP,
- BOOKMARKS_GROUP
+ EphyNode *bookmarks;
+ GSList *search_terms;
};
-G_DEFINE_TYPE_WITH_CODE (EphyCompletionModel, ephy_completion_model, G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
- ephy_completion_model_tree_model_init))
-
static void
-ephy_completion_model_class_init (EphyCompletionModelClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- g_type_class_add_private (object_class, sizeof (EphyCompletionModelPrivate));
-}
-
-static GtkTreePath *
-get_path_real (EphyCompletionModel *model,
- EphyNode *root,
- EphyNode *child)
+ephy_completion_model_constructed (GObject *object)
{
- GtkTreePath *retval;
- int index;
-
- retval = gtk_tree_path_new ();
- index = ephy_node_get_child_index (root, child);
+ GType types[N_COL] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN,
+ GDK_TYPE_PIXBUF };
- if (root == model->priv->bookmarks)
- {
- index += ephy_node_get_n_children (model->priv->history);
- }
-
- gtk_tree_path_append_index (retval, index);
-
- return retval;
+ gtk_list_store_set_column_types (GTK_LIST_STORE (object),
+ N_COL,
+ types);
}
static void
-node_iter_from_node (EphyCompletionModel *model,
- EphyNode *root,
- EphyNode *child,
- GtkTreeIter *iter)
-{
- iter->stamp = model->priv->stamp;
- iter->user_data = child;
- iter->user_data2 = root;
-}
-
-static EphyNode *
-get_index_root (EphyCompletionModel *model, int *index)
-{
- int children;
-
- children = ephy_node_get_n_children (model->priv->history);
-
- if (*index >= children)
- {
- *index = *index - children;
-
- if (*index < ephy_node_get_n_children (model->priv->bookmarks))
- {
- return model->priv->bookmarks;
- }
- else
- {
- return NULL;
- }
- }
- else
- {
- return model->priv->history;
- }
+free_search_terms (GSList *search_terms)
+{
+ GSList *iter;
+
+ for (iter = search_terms; iter != NULL; iter = iter->next)
+ g_regex_unref ((GRegex*)iter->data);
+
+ g_slist_free (search_terms);
}
static void
-root_child_removed_cb (EphyNode *node,
- EphyNode *child,
- guint old_index,
- EphyCompletionModel *model)
+ephy_completion_model_finalize (GObject *object)
{
- GtkTreePath *path;
- guint index;
-
- path = gtk_tree_path_new ();
+ EphyCompletionModelPrivate *priv = EPHY_COMPLETION_MODEL (object)->priv;
- index = old_index;
- if (node == model->priv->bookmarks)
- {
- index += ephy_node_get_n_children (model->priv->history);
- }
- gtk_tree_path_append_index (path, index);
+ if (priv->search_terms) {
+ free_search_terms (priv->search_terms);
+ priv->search_terms = NULL;
+ }
- gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
- gtk_tree_path_free (path);
+ G_OBJECT_CLASS (ephy_completion_model_parent_class)->finalize (object);
}
static void
-root_child_added_cb (EphyNode *node,
- EphyNode *child,
- EphyCompletionModel *model)
+ephy_completion_model_class_init (EphyCompletionModelClass *klass)
{
- GtkTreePath *path;
- GtkTreeIter iter;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
- node_iter_from_node (model, node, child, &iter);
+ object_class->constructed = ephy_completion_model_constructed;
+ object_class->finalize = ephy_completion_model_finalize;
- path = get_path_real (model, node, child);
- gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
- gtk_tree_path_free (path);
+ g_type_class_add_private (object_class, sizeof (EphyCompletionModelPrivate));
}
static void
-root_child_changed_cb (EphyNode *node,
- EphyNode *child,
- guint property_id,
- EphyCompletionModel *model)
+ephy_completion_model_init (EphyCompletionModel *model)
{
- GtkTreePath *path;
- GtkTreeIter iter;
+ EphyCompletionModelPrivate *priv;
+ EphyBookmarks *bookmarks_service;
- node_iter_from_node (model, node, child, &iter);
+ model->priv = priv = EPHY_COMPLETION_MODEL_GET_PRIVATE (model);
- path = get_path_real (model, node, child);
- gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
- gtk_tree_path_free (path);
-}
+ priv->browse_history = EPHY_BROWSE_HISTORY (ephy_embed_shell_get_global_browse_history (embed_shell));
+ priv->legacy_history_service = EPHY_HISTORY (ephy_embed_shell_get_global_history (embed_shell));
+ priv->favicon_cache = EPHY_FAVICON_CACHE (ephy_embed_shell_get_favicon_cache (embed_shell));
-static void
-connect_signals (EphyCompletionModel *model, EphyNode *root)
-{
- ephy_node_signal_connect_object (root,
- EPHY_NODE_CHILD_ADDED,
- (EphyNodeCallback)root_child_added_cb,
- G_OBJECT (model));
- ephy_node_signal_connect_object (root,
- EPHY_NODE_CHILD_REMOVED,
- (EphyNodeCallback)root_child_removed_cb,
- G_OBJECT (model));
- ephy_node_signal_connect_object (root,
- EPHY_NODE_CHILD_CHANGED,
- (EphyNodeCallback)root_child_changed_cb,
- G_OBJECT (model));
-}
-
-static void
-ephy_completion_model_init (EphyCompletionModel *model)
-{
- model->priv = EPHY_COMPLETION_MODEL_GET_PRIVATE (model);
- model->priv->stamp = g_random_int ();
-
- model->priv->history_service = EPHY_HISTORY (
- ephy_embed_shell_get_global_history (
- embed_shell));
- model->priv->history = ephy_history_get_pages (
- model->priv->history_service);
- connect_signals (model, model->priv->history);
-
- model->priv->bookmarks_service = ephy_shell_get_bookmarks (ephy_shell);
- model->priv->bookmarks = ephy_bookmarks_get_bookmarks (
- model->priv->bookmarks_service);
- connect_signals (model, model->priv->bookmarks);
-
- model->priv->favicon_cache = EPHY_FAVICON_CACHE (
- ephy_embed_shell_get_favicon_cache (
- EPHY_EMBED_SHELL (ephy_shell)));
+ bookmarks_service = ephy_shell_get_bookmarks (ephy_shell);
+ priv->bookmarks = ephy_bookmarks_get_bookmarks (bookmarks_service);
}
-EphyCompletionModel *
-ephy_completion_model_new (void)
+static gboolean
+is_base_address (const char *address)
{
- EphyCompletionModel *model;
-
- model = EPHY_COMPLETION_MODEL (g_object_new (EPHY_TYPE_COMPLETION_MODEL,
- NULL));
-
- g_return_val_if_fail (model->priv != NULL, NULL);
-
- return model;
+ if (address == NULL)
+ return FALSE;
+
+ /* A base address is <scheme>://<host>/
+ * Neither scheme nor host contain a slash, so we can use slashes
+ * figure out if it's a base address.
+ *
+ * Note: previous code was using a GRegExp to do the same thing.
+ * While regexps are much nicer to read, they're also a lot
+ * slower.
+ */
+ address = strchr (address, '/');
+ if (address == NULL ||
+ address[1] != '/')
+ return FALSE;
+
+ address += 2;
+ address = strchr (address, '/');
+ if (address == NULL ||
+ address[1] != 0)
+ return FALSE;
+
+ return TRUE;
}
static int
-ephy_completion_model_get_n_columns (GtkTreeModel *tree_model)
-{
- return N_COL;
+get_relevance (const char *location,
+ int visit_count,
+ gboolean is_bookmark)
+{
+ /* FIXME: use frecency. */
+ int relevance = 0;
+
+ /* We have three ordered groups: history's base addresses,
+ bookmarks, deep history addresses. */
+ if (is_bookmark)
+ relevance = 1 << 5;
+ else {
+ visit_count = MIN (visit_count, (1 << 5) - 1);
+
+ if (is_base_address (location))
+ relevance = visit_count << 10;
+ else
+ relevance = visit_count;
+ }
+
+ return relevance;
}
-static GType
-ephy_completion_model_get_column_type (GtkTreeModel *tree_model,
- int index)
-{
- GType type = 0;
-
- switch (index)
- {
- case EPHY_COMPLETION_TEXT_COL:
- case EPHY_COMPLETION_ACTION_COL:
- case EPHY_COMPLETION_KEYWORDS_COL:
- type = G_TYPE_STRING;
- break;
- case EPHY_COMPLETION_EXTRA_COL:
- type = G_TYPE_BOOLEAN;
- break;
- case EPHY_COMPLETION_FAVICON_COL:
- type = GDK_TYPE_PIXBUF;
- break;
- case EPHY_COMPLETION_RELEVANCE_COL:
- type = G_TYPE_INT;
- break;
- }
-
- return type;
-}
+typedef struct {
+ char *title;
+ char *location;
+ char *keywords;
+ int relevance;
+ gboolean is_bookmark;
+} PotentialRow;
static void
-init_text_col (GValue *value, EphyNode *node, int group)
-{
- const char *text;
-
- switch (group)
- {
- case BOOKMARKS_GROUP:
- case HISTORY_GROUP:
- text = ephy_node_get_property_string
- (node, EPHY_NODE_PAGE_PROP_TITLE);
- break;
-
- default:
- text = "";
- }
-
- g_value_set_string (value, text);
+set_row_in_model (EphyCompletionModel *model, int position, PotentialRow *row)
+{
+ const char *favicon_location = ephy_history_get_icon (model->priv->legacy_history_service,
+ row->location);
+
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, position,
+ EPHY_COMPLETION_TEXT_COL, row->title ? row->title : "",
+ EPHY_COMPLETION_URL_COL, row->location,
+ EPHY_COMPLETION_ACTION_COL, row->location,
+ EPHY_COMPLETION_KEYWORDS_COL, row->keywords ? row->keywords : "",
+ EPHY_COMPLETION_EXTRA_COL, row->is_bookmark,
+ EPHY_COMPLETION_FAVICON_COL, ephy_favicon_cache_get (model->priv->favicon_cache,
+ favicon_location),
+ EPHY_COMPLETION_RELEVANCE_COL, row->relevance,
+ -1);
}
static void
-init_action_col (GValue *value, EphyNode *node)
+replace_rows_in_model (EphyCompletionModel *model, GSList *new_rows)
{
- const char *text;
+ /* This is by far the simplest way of doing, and yet it gives
+ * basically the same result than the other methods... */
+ int i;
- text = ephy_node_get_property_string
- (node, EPHY_NODE_BMK_PROP_LOCATION);
-
- g_value_set_string (value, text);
-}
+ gtk_list_store_clear (GTK_LIST_STORE (model));
-static void
-init_keywords_col (GValue *value, EphyNode *node, int group)
-{
- const char *text = NULL;
-
- switch (group)
- {
- case BOOKMARKS_GROUP:
- text = ephy_node_get_property_string
- (node, EPHY_NODE_BMK_PROP_KEYWORDS);
- break;
- }
-
- if (text == NULL)
- {
- text = "";
- }
-
- g_value_set_string (value, text);
-}
-static void
-init_favicon_col (EphyCompletionModel *model, GValue *value,
- EphyNode *node, int group)
-{
- const char *icon_location;
- GdkPixbuf *pixbuf = NULL;
- const char *url;
-
- switch (group)
- {
- case BOOKMARKS_GROUP:
- icon_location = ephy_node_get_property_string
- (node, EPHY_NODE_BMK_PROP_ICON);
- break;
- case HISTORY_GROUP:
- url = ephy_node_get_property_string
- (node, EPHY_NODE_PAGE_PROP_LOCATION);
- icon_location = ephy_history_get_icon (
- model->priv->history_service, url);
- break;
- default:
- icon_location = NULL;
- }
-
- if (icon_location)
- {
- pixbuf = ephy_favicon_cache_get (
- model->priv->favicon_cache, icon_location);
- }
-
- g_value_take_object (value, pixbuf);
+ for (i = 0; new_rows != NULL; i++) {
+ PotentialRow *row = (PotentialRow*)new_rows->data;
+
+ set_row_in_model (model, i, row);
+ new_rows = new_rows->next;
+ }
}
static gboolean
-is_base_address (const char *address)
-{
- if (address == NULL)
- return FALSE;
-
- /* a base address is <scheme>://<host>/
- * Neither scheme nor host contain a slash, so we can use slashes
- * figure out if it's a base address.
- *
- * Note: previous code was using a GRegExp to do the same thing.
- * While regexps are much nicer to read, they're also a lot
- * slower.
- */
- address = strchr (address, '/');
- if (address == NULL ||
- address[1] != '/')
- return FALSE;
-
- address += 2;
- address = strchr (address, '/');
- if (address == NULL ||
- address[1] != 0)
- return FALSE;
-
- return TRUE;
-}
-
-static void
-init_relevance_col (GValue *value, EphyNode *node, int group)
-{
- int relevance = 0;
-
- /* We have three ordered groups: history's base
- addresses, bookmarks, deep history addresses */
-
- if (group == BOOKMARKS_GROUP)
- {
- relevance = 1 << 5;
- }
- else if (group == HISTORY_GROUP)
- {
- const char *address;
- int visits;
-
- visits = ephy_node_get_property_int
- (node, EPHY_NODE_PAGE_PROP_VISITS);
- address = ephy_node_get_property_string
- (node, EPHY_NODE_PAGE_PROP_LOCATION);
-
- visits = MIN (visits, (1 << 5) - 1);
-
- if (is_base_address (address))
- {
- relevance = visits << 10;
- }
- else
- {
- relevance = visits;
- }
- }
-
- g_value_set_int (value, relevance);
-}
-
-static void
-init_url_col (GValue *value, EphyNode *node)
-{
- const char *url = NULL;
-
- url = ephy_node_get_property_string
- (node, EPHY_NODE_PAGE_PROP_LOCATION);
-
- g_value_set_string (value, url);
-}
-
-static void
-ephy_completion_model_get_value (GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- int column,
- GValue *value)
-{
- int group;
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
- EphyNode *node;
-
- g_return_if_fail (EPHY_IS_COMPLETION_MODEL (tree_model));
- g_return_if_fail (iter != NULL);
- g_return_if_fail (iter->stamp == model->priv->stamp);
-
- node = iter->user_data;
- group = (iter->user_data2 == model->priv->history) ?
- HISTORY_GROUP : BOOKMARKS_GROUP;
-
- switch (column)
- {
- case EPHY_COMPLETION_EXTRA_COL:
- g_value_init (value, G_TYPE_BOOLEAN);
- g_value_set_boolean (value, (group == BOOKMARKS_GROUP));
- break;
- case EPHY_COMPLETION_TEXT_COL:
- g_value_init (value, G_TYPE_STRING);
- init_text_col (value, node, group);
- break;
- case EPHY_COMPLETION_FAVICON_COL:
- g_value_init (value, GDK_TYPE_PIXBUF);
- init_favicon_col (model, value, node, group);
- break;
- case EPHY_COMPLETION_ACTION_COL:
- g_value_init (value, G_TYPE_STRING);
- init_action_col (value, node);
- break;
- case EPHY_COMPLETION_KEYWORDS_COL:
- g_value_init (value, G_TYPE_STRING);
- init_keywords_col (value, node, group);
- break;
- case EPHY_COMPLETION_RELEVANCE_COL:
- g_value_init (value, G_TYPE_INT);
- init_relevance_col (value, node, group);
- break;
- case EPHY_COMPLETION_URL_COL:
- g_value_init (value, G_TYPE_STRING);
- init_url_col (value, node);
- break;
- }
-}
-
-static GtkTreeModelFlags
-ephy_completion_model_get_flags (GtkTreeModel *tree_model)
-{
- return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
+should_add_bookmark_to_model (EphyCompletionModel *model,
+ const char *search_string,
+ const char *title,
+ const char *location,
+ const char *keywords)
+{
+ gboolean ret = TRUE;
+ EphyCompletionModelPrivate *priv = model->priv;
+
+ if (priv->search_terms) {
+ GSList *iter;
+ GRegex *current = NULL;
+
+ for (iter = priv->search_terms; iter != NULL; iter = iter->next) {
+ current = (GRegex*) iter->data;
+ if ((!g_regex_match (current, title, G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+ (!g_regex_match (current, location, G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+ (!g_regex_match (current, keywords, G_REGEX_MATCH_NOTEMPTY, NULL))) {
+ ret = FALSE;
+ break;
+ }
+ }
+ }
+
+ return ret;
}
-static gboolean
-ephy_completion_model_get_iter (GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- GtkTreePath *path)
-{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
- EphyNode *root, *child;
- int i;
-
- g_return_val_if_fail (EPHY_IS_COMPLETION_MODEL (model), FALSE);
- g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
-
- i = gtk_tree_path_get_indices (path)[0];
-
- root = get_index_root (model, &i);
- if (root == NULL) return FALSE;
+typedef struct {
+ EphyCompletionModel *model;
+ char *search_string;
+ EphyHistoryJobCallback callback;
+ gpointer user_data;
+} FindURLsData;
- child = ephy_node_get_nth_child (root, i);
- g_return_val_if_fail (child != NULL, FALSE);
-
- node_iter_from_node (model, root, child, iter);
-
- return TRUE;
-}
-
-static GtkTreePath *
-ephy_completion_model_get_path (GtkTreeModel *tree_model,
- GtkTreeIter *iter)
+static int
+find_url (gconstpointer a,
+ gconstpointer b)
{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
-
- g_return_val_if_fail (iter != NULL, NULL);
- g_return_val_if_fail (iter->user_data != NULL, NULL);
- g_return_val_if_fail (iter->user_data2 != NULL, NULL);
- g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
-
- return get_path_real (model, iter->user_data2, iter->user_data);
+ return g_strcmp0 (((PotentialRow*)a)->location,
+ ((char *)b));
}
-static gboolean
-ephy_completion_model_iter_next (GtkTreeModel *tree_model,
- GtkTreeIter *iter)
+static PotentialRow *
+potential_row_new (const char *title, const char *location,
+ const char *keywords, int visit_count,
+ gboolean is_bookmark)
{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
- EphyNode *node, *next, *root;
-
- g_return_val_if_fail (iter != NULL, FALSE);
- g_return_val_if_fail (iter->user_data != NULL, FALSE);
- g_return_val_if_fail (iter->user_data2 != NULL, FALSE);
- g_return_val_if_fail (iter->stamp == model->priv->stamp, FALSE);
-
- node = iter->user_data;
- root = iter->user_data2;
+ PotentialRow *row = g_slice_new0 (PotentialRow);
- next = ephy_node_get_next_child (root, node);
+ row->title = g_strdup (title);
+ row->location = g_strdup (location);
+ row->keywords = g_strdup (keywords);
+ row->relevance = get_relevance (location, visit_count, is_bookmark);
+ row->is_bookmark = is_bookmark;
- if (next == NULL && root == model->priv->history)
- {
- root = model->priv->bookmarks;
- next = ephy_node_get_nth_child (model->priv->bookmarks, 0);
- }
-
- if (next == NULL) return FALSE;
-
- node_iter_from_node (model, root, next, iter);
-
- return TRUE;
+ return row;
}
-static gboolean
-ephy_completion_model_iter_children (GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- GtkTreeIter *parent)
+static void
+free_potential_row (PotentialRow *row)
{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
- EphyNode *root, *first_node;
-
- if (parent != NULL)
- {
- return FALSE;
- }
-
- root = model->priv->history;
- first_node = ephy_node_get_nth_child (root, 0);
-
- if (first_node == NULL)
- {
- root = model->priv->bookmarks;
- first_node = ephy_node_get_nth_child (root, 0);
- }
-
- if (first_node == NULL)
- {
- return FALSE;
- }
-
- node_iter_from_node (model, root, first_node, iter);
-
- return TRUE;
+ g_free (row->title);
+ g_free (row->location);
+ g_free (row->keywords);
+
+ g_slice_free (PotentialRow, row);
}
-static gboolean
-ephy_completion_model_iter_has_child (GtkTreeModel *tree_model,
- GtkTreeIter *iter)
-{
- return FALSE;
+static GSList *
+add_to_potential_rows (GSList *rows,
+ const char *title,
+ const char *location,
+ const char *keywords,
+ int visit_count,
+ gboolean is_bookmark,
+ gboolean search_for_duplicates)
+{
+ gboolean found = FALSE;
+ PotentialRow *row = potential_row_new (title, location, keywords, visit_count, is_bookmark);
+
+ if (search_for_duplicates) {
+ GSList *p;
+
+ p = g_slist_find_custom (rows, location, find_url);
+ if (p) {
+ PotentialRow *match = (PotentialRow*)p->data;
+ if (row->relevance > match->relevance)
+ match->relevance = row->relevance;
+
+ found = TRUE;
+ free_potential_row (row);
+ }
+ }
+
+ if (!found)
+ rows = g_slist_prepend (rows, row);
+
+ return rows;
}
static int
-ephy_completion_model_iter_n_children (GtkTreeModel *tree_model,
- GtkTreeIter *iter)
+sort_by_relevance (gconstpointer a, gconstpointer b)
{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
-
- g_return_val_if_fail (EPHY_IS_COMPLETION_MODEL (tree_model), -1);
-
- if (iter == NULL)
- {
- return ephy_node_get_n_children (model->priv->history) +
- ephy_node_get_n_children (model->priv->bookmarks);
- }
+ PotentialRow *r1 = (PotentialRow*)a;
+ PotentialRow *r2 = (PotentialRow*)b;
- g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
-
- return 0;
+ if (r1->relevance < r2->relevance)
+ return 1;
+ else if (r1->relevance > r2->relevance)
+ return -1;
+ else
+ return 0;
}
-static gboolean
-ephy_completion_model_iter_nth_child (GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- GtkTreeIter *parent,
- int n)
-{
- EphyCompletionModel *model = EPHY_COMPLETION_MODEL (tree_model);
- EphyNode *node, *root;
-
- g_return_val_if_fail (EPHY_IS_COMPLETION_MODEL (tree_model), FALSE);
-
- if (parent != NULL)
- {
- return FALSE;
- }
-
- root = get_index_root (model, &n);
- node = ephy_node_get_nth_child (root, n);
-
- if (node == NULL) return FALSE;
-
- node_iter_from_node (model, root, node, iter);
+static void
+query_completed_cb (EphyHistoryService *service,
+ gboolean success,
+ gpointer result_data,
+ FindURLsData *user_data)
+{
+ EphyCompletionModel *model = user_data->model;
+ EphyCompletionModelPrivate *priv = model->priv;
+ GList *p, *urls;
+ GPtrArray *children;
+ GSList *list = NULL;
+ int i;
+
+ /* Bookmarks */
+ children = ephy_node_get_children (priv->bookmarks);
+
+ /* FIXME: perhaps this could be done in a service thread? There
+ * should never be a ton of bookmarks, but seems a bit cleaner and
+ * consistent with what we do for the history. */
+ for (i = 0; i < children->len; i++) {
+ EphyNode *kid;
+ const char *keywords, *location, *title;
+
+ kid = g_ptr_array_index (children, i);
+ location = ephy_node_get_property_string (kid, EPHY_NODE_BMK_PROP_LOCATION);
+ title = ephy_node_get_property_string (kid, EPHY_NODE_BMK_PROP_TITLE);
+ keywords = ephy_node_get_property_string (kid, EPHY_NODE_BMK_PROP_KEYWORDS);
+
+ if (should_add_bookmark_to_model (model, user_data->search_string,
+ title, location, keywords))
+ list = add_to_potential_rows (list, title, location, keywords, 0, TRUE, FALSE);
+ }
+
+ /* History */
+ urls = (GList*)result_data;
+
+ for (p = urls; p != NULL; p = p->next) {
+ EphyHistoryURL *url = (EphyHistoryURL*)p->data;
+
+ list = add_to_potential_rows (list, url->title, url->url, NULL, url->visit_count, FALSE, TRUE);
+ }
+
+ /* Sort the rows by relevance. */
+ list = g_slist_sort (list, sort_by_relevance);
+
+ /* Now that we have all the rows we want to insert, replace the rows
+ * in the current model one by one, sorted by relevance. */
+ replace_rows_in_model (model, list);
+
+ /* Notify */
+ if (user_data->callback)
+ user_data->callback (service, success, result_data, user_data->user_data);
+
+ g_free (user_data->search_string);
+ g_slice_free (FindURLsData, user_data);
+
+ g_slist_free_full (list, (GDestroyNotify)free_potential_row);
+}
- return TRUE;
+static void
+update_search_terms (EphyCompletionModel *model,
+ 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;
+ EphyCompletionModelPrivate *priv = model->priv;
+
+ if (priv->search_terms) {
+ free_search_terms (priv->search_terms);
+ priv->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);
+ priv->search_terms = g_slist_append (priv->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
-ephy_completion_model_iter_parent (GtkTreeModel *tree_model,
- GtkTreeIter *iter,
- GtkTreeIter *child)
-{
- return FALSE;
+#define MAX_COMPLETION_HISTORY_URLS 8
+
+void
+ephy_completion_model_update_for_string (EphyCompletionModel *model,
+ const char *search_string,
+ EphyHistoryJobCallback callback,
+ gpointer data)
+{
+ EphyCompletionModelPrivate *priv;
+ char **strings;
+ int i;
+ GList *query = NULL;
+ FindURLsData *user_data;
+
+ g_return_if_fail (EPHY_IS_COMPLETION_MODEL (model));
+ g_return_if_fail (search_string != NULL);
+
+ priv = model->priv;
+
+ /* Split the search string. */
+ strings = g_strsplit (search_string, " ", -1);
+ for (i = 0; strings[i]; i++)
+ query = g_list_append (query, g_strdup (strings[i]));
+ g_strfreev (strings);
+
+ update_search_terms (model, search_string);
+
+ user_data = g_slice_new (FindURLsData);
+ user_data->model = model;
+ user_data->search_string = g_strdup (search_string);
+ user_data->callback = callback;
+ user_data->user_data = data;
+
+ ephy_browse_history_find_urls (priv->browse_history,
+ 0, 0,
+ MAX_COMPLETION_HISTORY_URLS,
+ query,
+ (EphyHistoryJobCallback)query_completed_cb,
+ user_data);
}
-static void
-ephy_completion_model_tree_model_init (GtkTreeModelIface *iface)
+EphyCompletionModel *
+ephy_completion_model_new (void)
{
- iface->get_flags = ephy_completion_model_get_flags;
- iface->get_iter = ephy_completion_model_get_iter;
- iface->get_path = ephy_completion_model_get_path;
- iface->iter_next = ephy_completion_model_iter_next;
- iface->iter_children = ephy_completion_model_iter_children;
- iface->iter_has_child = ephy_completion_model_iter_has_child;
- iface->iter_n_children = ephy_completion_model_iter_n_children;
- iface->iter_nth_child = ephy_completion_model_iter_nth_child;
- iface->iter_parent = ephy_completion_model_iter_parent;
- iface->get_n_columns = ephy_completion_model_get_n_columns;
- iface->get_column_type = ephy_completion_model_get_column_type;
- iface->get_value = ephy_completion_model_get_value;
+ return g_object_new (EPHY_TYPE_COMPLETION_MODEL, NULL);
}
diff --git a/src/ephy-completion-model.h b/src/ephy-completion-model.h
index 62cae95c6..f02f81242 100644
--- a/src/ephy-completion-model.h
+++ b/src/ephy-completion-model.h
@@ -1,5 +1,5 @@
/*
- * Copyright © 2003 Marco Pesenti Gritti <marco@gnome.org>
+ * Copyright © 2012 Igalia S.L.
*
* 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
@@ -24,6 +24,8 @@
#ifndef EPHY_COMPLETION_MODEL_H
#define EPHY_COMPLETION_MODEL_H
+#include "ephy-history-service.h"
+
#include <gtk/gtk.h>
G_BEGIN_DECLS
@@ -51,7 +53,7 @@ typedef enum
typedef struct
{
- GObject parent;
+ GtkListStore parent;
/*< private >*/
EphyCompletionModelPrivate *priv;
@@ -59,13 +61,17 @@ typedef struct
typedef struct
{
- GObjectClass parent;
+ GtkListStoreClass parent;
} EphyCompletionModelClass;
-GType ephy_completion_model_get_type (void);
+GType ephy_completion_model_get_type (void);
-EphyCompletionModel *ephy_completion_model_new (void);
+EphyCompletionModel *ephy_completion_model_new (void);
+void ephy_completion_model_update_for_string (EphyCompletionModel *model,
+ const char *string,
+ EphyHistoryJobCallback callback,
+ gpointer data);
G_END_DECLS
#endif /* EPHY_COMPLETION_MODEL_H */
diff --git a/src/ephy-location-controller.c b/src/ephy-location-controller.c
index b0509ddaf..9315ad6cf 100644
--- a/src/ephy-location-controller.c
+++ b/src/ephy-location-controller.c
@@ -96,51 +96,8 @@ match_func (GtkEntryCompletion *completion,
GtkTreeIter *iter,
gpointer data)
{
- char *item = NULL;
- char *url = NULL;
- char *keywords = NULL;
-
- gboolean ret = FALSE;
- GtkTreeModel *model;
- GSList *search_terms;
-
- model = gtk_entry_completion_get_model (completion);
-
- gtk_tree_model_get (model, iter,
- EPHY_COMPLETION_TEXT_COL, &item,
- EPHY_COMPLETION_URL_COL, &url,
- EPHY_COMPLETION_KEYWORDS_COL, &keywords,
- -1);
-
- if (!key)
- return FALSE;
-
- search_terms = ephy_location_entry_get_search_terms (data);
-
- if (search_terms)
- {
- GSList *iter;
- GRegex *current = NULL;
-
- ret = TRUE;
- for (iter = search_terms; iter != NULL; iter = iter->next)
- {
- current = (GRegex*) iter->data;
- if ((!g_regex_match (current, item, G_REGEX_MATCH_NOTEMPTY, NULL)) &&
- (!g_regex_match (current, url, G_REGEX_MATCH_NOTEMPTY, NULL)) &&
- (!g_regex_match (current, keywords, G_REGEX_MATCH_NOTEMPTY, NULL)))
- {
- ret = FALSE;
- break;
- }
- }
- }
-
- g_free (item);
- g_free (url);
- g_free (keywords);
-
- return ret;
+ /* We want every row in the model to show up. */
+ return TRUE;
}
static void
@@ -211,6 +168,8 @@ static void
user_changed_cb (GtkWidget *widget, EphyLocationController *controller)
{
const char *address;
+ GtkTreeModel *model;
+ GtkEntryCompletion *completion;
address = ephy_location_entry_get_location (EPHY_LOCATION_ENTRY (widget));
@@ -218,6 +177,12 @@ user_changed_cb (GtkWidget *widget, EphyLocationController *controller)
g_signal_handlers_block_by_func (controller, G_CALLBACK (sync_address), widget);
ephy_location_controller_set_address (controller, address);
+
+ completion = gtk_entry_get_completion (GTK_ENTRY (widget));
+ model = gtk_entry_completion_get_model (completion);
+
+ ephy_completion_model_update_for_string (EPHY_COMPLETION_MODEL (model), address,
+ NULL, NULL);
g_signal_handlers_unblock_by_func (controller, G_CALLBACK (sync_address), widget);
}
@@ -428,7 +393,7 @@ ephy_location_controller_constructed (GObject *object)
EPHY_COMPLETION_EXTRA_COL,
EPHY_COMPLETION_FAVICON_COL);
g_object_unref (model);
-
+
ephy_location_entry_set_match_func (priv->location_entry,
match_func,
priv->location_entry,