/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright © 2002 Jorn Baayen <jorn@nl.linux.org>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "ephy-completion-model.h"
#include "ephy-favicon-cache.h"
#include "ephy-node.h"
#include "ephy-shell.h"
#include "ephy-history.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);
#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;
};
enum
{
HISTORY_GROUP,
BOOKMARKS_GROUP
};
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)
{
GtkTreePath *retval;
int index;
retval = gtk_tree_path_new ();
index = ephy_node_get_child_index (root, child);
if (root == model->priv->bookmarks)
{
index += ephy_node_get_n_children (model->priv->history);
}
gtk_tree_path_append_index (retval, index);
return retval;
}
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;
}
}
static void
root_child_removed_cb (EphyNode *node,
EphyNode *child,
guint old_index,
EphyCompletionModel *model)
{
GtkTreePath *path;
guint index;
path = gtk_tree_path_new ();
index = old_index;
if (node == model->priv->bookmarks)
{
index += ephy_node_get_n_children (model->priv->history);
}
gtk_tree_path_append_index (path, index);
gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
gtk_tree_path_free (path);
}
static void
root_child_added_cb (EphyNode *node,
EphyNode *child,
EphyCompletionModel *model)
{
GtkTreePath *path;
GtkTreeIter iter;
node_iter_from_node (model, node, child, &iter);
path = get_path_real (model, node, child);
gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
gtk_tree_path_free (path);
}
static void
root_child_changed_cb (EphyNode *node,
EphyNode *child,
guint property_id,
EphyCompletionModel *model)
{
GtkTreePath *path;
GtkTreeIter iter;
node_iter_from_node (model, node, child, &iter);
path = get_path_real (model, node, child);
gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
gtk_tree_path_free (path);
}
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)));
}
EphyCompletionModel *
ephy_completion_model_new (void)
{
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;
}
static int
ephy_completion_model_get_n_columns (GtkTreeModel *tree_model)
{
return N_COL;
}
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;
}
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);
}
static void
init_action_col (GValue *value, EphyNode *node)
{
const char *text;
text = ephy_node_get_property_string
(node, EPHY_NODE_BMK_PROP_LOCATION);
g_value_set_string (value, text);
}
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);
}
const GRegex *base_address_regex = NULL;
static gboolean
is_base_address (const char *address)
{
if (base_address_regex == NULL) {
base_address_regex = g_regex_new ("//.*/$",
G_REGEX_OPTIMIZE,
G_REGEX_MATCH_NOTEMPTY,
NULL);
}
return g_regex_match (base_address_regex, address, G_REGEX_MATCH_NOTEMPTY, NULL);
}
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;
}
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;
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)
{
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);
}
static gboolean
ephy_completion_model_iter_next (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
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;
next = ephy_node_get_next_child (root, node);
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;
}
static gboolean
ephy_completion_model_iter_children (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent)
{
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;
}
static gboolean
ephy_completion_model_iter_has_child (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
return FALSE;
}
static int
ephy_completion_model_iter_n_children (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
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);
}
g_return_val_if_fail (model->priv->stamp == iter->stamp, -1);
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);
return TRUE;
}
static gboolean
ephy_completion_model_iter_parent (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *child)
{
return FALSE;
}
static void
ephy_completion_model_tree_model_init (GtkTreeModelIface *iface)
{
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;
}