/* * Copyright (C) 2002 Ricardo Fernández Pascual * Copyright (C) 2003 Marco Pesenti Gritti * Copyright (C) 2003 Christian Persch * * 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ #include "ephy-tree-model-node.h" #include "ephy-location-entry.h" #include "ephy-marshal.h" #include "ephy-debug.h" #include "ephy-file-helpers.h" #include #include #include #include #include #include #include #include #include #define EPHY_LOCATION_ENTRY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_LOCATION_ENTRY, EphyLocationEntryPrivate)) struct _EphyLocationEntryPrivate { EphyTreeModelNode *combo_model; GtkWidget *combo; GtkWidget *entry; char *before_completion; gboolean user_changed; gboolean activation_mode; EphyNodeDb *combo_db; EphyNode *combo_root; char *xml_file; guint text_col; guint action_col; guint keywords_col; guint relevance_col; }; static char *web_prefixes [] = { "http://www.", "http://", "https://www.", "https://", "www." }; static int n_web_prefixes = G_N_ELEMENTS (web_prefixes); static void ephy_location_entry_class_init (EphyLocationEntryClass *klass); static void ephy_location_entry_init (EphyLocationEntry *le); static void ephy_location_entry_finalize (GObject *o); static GObjectClass *parent_class = NULL; enum EphyLocationEntrySignalsEnum { USER_CHANGED, LAST_SIGNAL }; static gint EphyLocationEntrySignals[LAST_SIGNAL]; enum { LOCATION_HISTORY_NODE_ID = 1 }; enum { EPHY_NODE_LOC_HISTORY_PROP_TEXT = 1 }; #define MAX_LOC_HISTORY_ITEMS 10 #define EPHY_LOC_HISTORY_XML_ROOT "ephy_location_history" #define EPHY_LOC_HISTORY_XML_VERSION "0.1" GType ephy_location_entry_get_type (void) { static GType ephy_location_entry_type = 0; if (ephy_location_entry_type == 0) { static const GTypeInfo our_info = { sizeof (EphyLocationEntryClass), NULL, NULL, (GClassInitFunc) ephy_location_entry_class_init, NULL, NULL, sizeof (EphyLocationEntry), 0, (GInstanceInitFunc) ephy_location_entry_init }; ephy_location_entry_type = g_type_register_static (GTK_TYPE_TOOL_ITEM, "EphyLocationEntry", &our_info, 0); } return ephy_location_entry_type; } static gboolean ephy_location_entry_set_tooltip (GtkToolItem *tool_item, GtkTooltips *tooltips, const char *tip_text, const char *tip_private) { EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (tool_item); g_return_val_if_fail (EPHY_IS_LOCATION_ENTRY (entry), FALSE); gtk_tooltips_set_tip (tooltips, entry->priv->entry, tip_text, tip_private); return TRUE; } static void ephy_location_entry_class_init (EphyLocationEntryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkToolItemClass *tool_item_class = GTK_TOOL_ITEM_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = ephy_location_entry_finalize; tool_item_class->set_tooltip = ephy_location_entry_set_tooltip; EphyLocationEntrySignals[USER_CHANGED] = g_signal_new ( "user_changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP, G_STRUCT_OFFSET (EphyLocationEntryClass, user_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); g_type_class_add_private (object_class, sizeof (EphyLocationEntryPrivate)); } static void ephy_location_entry_activation_finished (EphyLocationEntry *entry) { if (entry->priv->activation_mode) { GtkWidget *toolbar; entry->priv->activation_mode = FALSE; toolbar = gtk_widget_get_ancestor (GTK_WIDGET (entry), GTK_TYPE_TOOLBAR); gtk_widget_hide (toolbar); } } static gboolean location_focus_out_cb (GtkWidget *leidget, GdkEventFocus *event, EphyLocationEntry *le) { ephy_location_entry_activation_finished (le); return FALSE; } static void editable_changed_cb (GtkEditable *editable, EphyLocationEntry *e) { EphyLocationEntryPrivate *p = e->priv; if (p->user_changed) { g_signal_emit (e, EphyLocationEntrySignals[USER_CHANGED], 0); } } static gboolean entry_button_press_cb (GtkWidget *entry, GdkEventButton *event, EphyLocationEntry *le) { if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) { ephy_location_entry_activate (le); return TRUE; } return FALSE; } static void add_to_history (EphyLocationEntry *le, const char *text) { GPtrArray *children; int i, l, n_items, index = -1; int *order; /* check if it already exists */ children = ephy_node_get_children (le->priv->combo_root); n_items = children->len; for (i = 0; i < n_items; i++) { EphyNode *kid; const char *node_text; kid = g_ptr_array_index (children, i); node_text = ephy_node_get_property_string (kid, EPHY_NODE_LOC_HISTORY_PROP_TEXT); if (strcmp (text, node_text) == 0) { index = i; break; } } ephy_node_thaw (le->priv->combo_root); /* it doesnt exist, add it */ if (index < 0) { GValue value = { 0, }; EphyNode *node; node = ephy_node_new (le->priv->combo_db); g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, text); ephy_node_set_property (node, EPHY_NODE_LOC_HISTORY_PROP_TEXT, &value); if (n_items >= MAX_LOC_HISTORY_ITEMS) { EphyNode *last; last = ephy_node_get_nth_child (le->priv->combo_root, n_items - 1); ephy_node_remove_child (le->priv->combo_root, last); } ephy_node_add_child (le->priv->combo_root, node); } /* move it at the top */ n_items = ephy_node_get_n_children (le->priv->combo_root); order = g_new0 (int, n_items); l = 1; if (index == -1) index = n_items -1; for (i = 0; i < n_items; i++) { if (index != i) { order[i] = l; l++; } else { order[i] = 0; } } ephy_node_reorder_children (le->priv->combo_root, order); g_free (order); } static void entry_activate_cb (GtkEntry *entry, EphyLocationEntry *le) { char *content; content = gtk_editable_get_chars (GTK_EDITABLE(entry), 0, -1); if (content) { add_to_history (le, content); g_free (content); } } static gboolean completion_func (GtkEntryCompletion *completion, const char *key, GtkTreeIter *iter, gpointer data) { int i; char *item = NULL; char *keywords = NULL; char *normalized_string, *normalized_keywords; char *case_normalized_string, *case_normalized_keywords; gboolean ret = FALSE; EphyLocationEntry *le = EPHY_LOCATION_ENTRY (data); GtkTreeModel *model; model = gtk_entry_completion_get_model (completion); gtk_tree_model_get (model, iter, le->priv->text_col, &item, -1); gtk_tree_model_get (model, iter, le->priv->keywords_col, &keywords, -1); normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL); case_normalized_string = g_utf8_casefold (normalized_string, -1); normalized_keywords = g_utf8_normalize (keywords, -1, G_NORMALIZE_ALL); case_normalized_keywords = g_utf8_casefold (normalized_keywords, -1); if (!strncmp (key, case_normalized_string, strlen (key))) { ret = TRUE; } else if (strstr (case_normalized_keywords, key)) { ret = TRUE; } else { for (i = 0; i < n_web_prefixes; i++) { char *key_prefixed; key_prefixed = g_strconcat (web_prefixes[i], key, NULL); if (!strncmp (key_prefixed, case_normalized_string, strlen (key_prefixed))) { ret = TRUE; break; } g_free (key_prefixed); } } g_free (item); g_free (normalized_string); g_free (case_normalized_string); return ret; } static gboolean match_selected_cb (GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, EphyLocationEntry *le) { char *item = NULL; gtk_tree_model_get (model, iter, le->priv->action_col, &item, -1); ephy_location_entry_set_location (le, item); g_signal_emit_by_name (le->priv->entry, "activate"); g_free (item); return TRUE; } static void ephy_location_entry_construct_contents (EphyLocationEntry *le) { EphyLocationEntryPrivate *p = le->priv; int combo_text_col; LOG ("EphyLocationEntry constructing contents %p", le) p->combo_model = ephy_tree_model_node_new (p->combo_root, NULL); combo_text_col = ephy_tree_model_node_add_prop_column (p->combo_model, G_TYPE_STRING, EPHY_NODE_LOC_HISTORY_PROP_TEXT); p->combo = gtk_combo_box_entry_new (GTK_TREE_MODEL (p->combo_model), combo_text_col); gtk_container_add (GTK_CONTAINER (le), p->combo); gtk_widget_show (p->combo); p->entry = GTK_BIN (p->combo)->child; g_signal_connect (p->entry, "button_press_event", G_CALLBACK (entry_button_press_cb), le); g_signal_connect (p->entry, "changed", G_CALLBACK (editable_changed_cb), le); g_signal_connect (p->entry, "activate", G_CALLBACK (entry_activate_cb), le); } static void ephy_location_entry_init (EphyLocationEntry *le) { EphyLocationEntryPrivate *p; LOG ("EphyLocationEntry initialising %p", le) p = EPHY_LOCATION_ENTRY_GET_PRIVATE (le); le->priv = p; p->user_changed = TRUE; p->activation_mode = FALSE; p->combo_db = ephy_node_db_new ("NodeDB"); p->combo_root = ephy_node_new_with_id (p->combo_db, LOCATION_HISTORY_NODE_ID); p->xml_file = g_build_filename (ephy_dot_dir (), "ephy-location-history.xml", NULL); ephy_node_db_load_from_file (p->combo_db, p->xml_file, EPHY_LOC_HISTORY_XML_ROOT, EPHY_LOC_HISTORY_XML_VERSION); ephy_location_entry_construct_contents (le); gtk_tool_item_set_expand (GTK_TOOL_ITEM (le), TRUE); g_signal_connect (le->priv->entry, "focus_out_event", G_CALLBACK (location_focus_out_cb), le); } static void save_location_history (EphyLocationEntry *le) { xmlDocPtr doc; xmlNodePtr root; GPtrArray *children; int i; xmlIndentTreeOutput = TRUE; doc = xmlNewDoc ("1.0"); root = xmlNewDocNode (doc, NULL, EPHY_LOC_HISTORY_XML_ROOT, NULL); xmlSetProp (root, "version", EPHY_LOC_HISTORY_XML_VERSION); xmlDocSetRootElement (doc, root); children = ephy_node_get_children (le->priv->combo_root); for (i = 0; i < children->len; i++) { EphyNode *kid; kid = g_ptr_array_index (children, i); ephy_node_save_to_xml (kid, root); } ephy_node_thaw (le->priv->combo_root); ephy_file_save_xml (le->priv->xml_file, doc); xmlFreeDoc(doc); } static void ephy_location_entry_finalize (GObject *o) { EphyLocationEntry *le; le = EPHY_LOCATION_ENTRY (o); save_location_history (le); g_free (le->priv->xml_file); LOG ("EphyLocationEntry finalized") G_OBJECT_CLASS (parent_class)->finalize (o); } GtkWidget * ephy_location_entry_new (void) { return GTK_WIDGET (g_object_new (EPHY_TYPE_LOCATION_ENTRY, NULL)); } static gint sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) { gint valuea, valueb; EphyLocationEntry *le = EPHY_LOCATION_ENTRY (data); gtk_tree_model_get (model, a, le->priv->relevance_col, &valuea, -1); gtk_tree_model_get (model, b, le->priv->relevance_col, &valueb, -1); return valueb - valuea; } void ephy_location_entry_set_completion (EphyLocationEntry *le, GtkTreeModel *model, guint text_col, guint action_col, guint keywords_col, guint relevance_col) { GtkTreeModel *sort_model; GtkEntryCompletion *completion; GtkCellRenderer *cell; le->priv->text_col = text_col; le->priv->action_col = action_col; le->priv->keywords_col = keywords_col; le->priv->relevance_col = relevance_col; sort_model = gtk_tree_model_sort_new_with_model (model); g_object_unref (model); gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (sort_model), sort_func, le, NULL); completion = gtk_entry_completion_new (); gtk_entry_completion_set_model (completion, sort_model); g_object_unref (sort_model); gtk_entry_completion_set_match_func (completion, completion_func, le, NULL); g_signal_connect (completion, "match_selected", G_CALLBACK (match_selected_cb), le); cell = gtk_cell_renderer_text_new (); 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_entry_set_completion (GTK_ENTRY (le->priv->entry), completion); g_object_unref (completion); } void ephy_location_entry_set_location (EphyLocationEntry *le, const gchar *new_location) { EphyLocationEntryPrivate *p = le->priv; g_return_if_fail (new_location != NULL); p->user_changed = FALSE; gtk_entry_set_text (GTK_ENTRY (p->entry), new_location); p->user_changed = TRUE; } const char * ephy_location_entry_get_location (EphyLocationEntry *le) { return gtk_entry_get_text (GTK_ENTRY (le->priv->entry)); } void ephy_location_entry_activate (EphyLocationEntry *le) { GtkWidget *toplevel, *toolbar; toolbar = gtk_widget_get_ancestor (GTK_WIDGET (le), GTK_TYPE_TOOLBAR); if (!GTK_WIDGET_VISIBLE (toolbar)) { le->priv->activation_mode = TRUE; gtk_widget_show (toolbar); } toplevel = gtk_widget_get_toplevel (le->priv->entry); gtk_editable_select_region (GTK_EDITABLE(le->priv->entry), 0, -1); gtk_window_set_focus (GTK_WINDOW(toplevel), le->priv->entry); } void ephy_location_entry_clear_history (EphyLocationEntry *le) { EphyNode *node; while ((node = ephy_node_get_nth_child (le->priv->combo_root, 0)) != NULL) { ephy_node_unref (node); } save_location_history (le); } GtkWidget * ephy_location_entry_get_entry (EphyLocationEntry *le) { return le->priv->entry; }