/* * Copyright (C) 2002 Ricardo Fernández Pascual * * 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. */ #include "ephy-location-entry.h" #include "ephy-autocompletion-window.h" #include "ephy-marshal.h" #include "ephy-gobject-misc.h" #include "eel-gconf-extensions.h" #include "ephy-prefs.h" #include "ephy-debug.h" #include #include #include #include #include #include /** * Private data */ struct _EphyLocationEntryPrivate { GtkWidget *combo; GtkWidget *entry; gchar *before_completion; EphyAutocompletion *autocompletion; EphyAutocompletionWindow *autocompletion_window; gboolean autocompletion_window_visible; gint autocompletion_timeout; gint show_alternatives_timeout; gboolean block_set_autocompletion_key; gchar *autocompletion_key; gchar *last_completion; char *last_action_target; }; #define AUTOCOMPLETION_DELAY 10 #define SHOW_ALTERNATIVES_DELAY 100 /** * Private functions, only availble from this file */ static void ephy_location_entry_class_init (EphyLocationEntryClass *klass); static void ephy_location_entry_init (EphyLocationEntry *w); static void ephy_location_entry_finalize_impl (GObject *o); static void ephy_location_entry_build (EphyLocationEntry *w); static gboolean ephy_location_entry_key_press_event_cb (GtkWidget *entry, GdkEventKey *event, EphyLocationEntry *w); static void ephy_location_entry_activate_cb (GtkEntry *entry, EphyLocationEntry *w); static void ephy_location_entry_autocompletion_sources_changed_cb (EphyAutocompletion *aw, EphyLocationEntry *w); static gint ephy_location_entry_autocompletion_to (EphyLocationEntry *w); static gint ephy_location_entry_autocompletion_show_alternatives_to (EphyLocationEntry *w); static void ephy_location_entry_autocompletion_window_url_activated_cb /***/ (EphyAutocompletionWindow *aw, const gchar *target, int action, EphyLocationEntry *w); static void ephy_location_entry_list_event_after_cb (GtkWidget *list, GdkEvent *event, EphyLocationEntry *e); static void ephy_location_entry_editable_changed_cb (GtkEditable *editable, EphyLocationEntry *e); static void ephy_location_entry_set_autocompletion_key (EphyLocationEntry *e); static void ephy_location_entry_autocompletion_show_alternatives (EphyLocationEntry *w); static void ephy_location_entry_autocompletion_hide_alternatives (EphyLocationEntry *w); static void ephy_location_entry_autocompletion_window_hidden_cb (EphyAutocompletionWindow *aw, EphyLocationEntry *w); static gpointer gtk_hbox_class; /** * Signals enums and ids */ enum EphyLocationEntrySignalsEnum { ACTIVATED, LAST_SIGNAL }; static gint EphyLocationEntrySignals[LAST_SIGNAL]; /** * EphyLocationEntry object */ MAKE_GET_TYPE (ephy_location_entry, "EphyLocationEntry", EphyLocationEntry, ephy_location_entry_class_init, ephy_location_entry_init, GTK_TYPE_HBOX); static void ephy_location_entry_class_init (EphyLocationEntryClass *klass) { G_OBJECT_CLASS (klass)->finalize = ephy_location_entry_finalize_impl; gtk_hbox_class = g_type_class_peek_parent (klass); EphyLocationEntrySignals[ACTIVATED] = g_signal_new ( "activated", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP, G_STRUCT_OFFSET (EphyLocationEntryClass, activated), NULL, NULL, ephy_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); } static void ephy_location_entry_init (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = g_new0 (EphyLocationEntryPrivate, 1); w->priv = p; p->last_action_target = NULL; ephy_location_entry_build (w); } static void ephy_location_entry_finalize_impl (GObject *o) { EphyLocationEntry *w = EPHY_LOCATION_ENTRY (o); EphyLocationEntryPrivate *p = w->priv; if (p->autocompletion) { g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, w); g_signal_handlers_disconnect_matched (p->autocompletion_window, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, w); g_object_unref (G_OBJECT (p->autocompletion)); g_object_unref (G_OBJECT (p->autocompletion_window)); } LOG ("EphyLocationEntry finalized") g_free (p); G_OBJECT_CLASS (gtk_hbox_class)->finalize (o); } GtkWidget * ephy_location_entry_new (void) { return GTK_WIDGET (g_object_new (EPHY_TYPE_LOCATION_ENTRY, NULL)); } static void ephy_location_entry_build (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; GtkWidget *list; p->combo = gnome_entry_new ("ephy-url-history"); p->entry = GTK_COMBO (p->combo)->entry; gtk_widget_show (p->combo); gtk_box_pack_start (GTK_BOX (w), p->combo, TRUE, TRUE, 0); g_signal_connect (p->entry, "key-press-event", G_CALLBACK (ephy_location_entry_key_press_event_cb), w); g_signal_connect (p->entry, "activate", G_CALLBACK (ephy_location_entry_activate_cb), w); g_signal_connect (p->entry, "changed", G_CALLBACK (ephy_location_entry_editable_changed_cb), w); list = GTK_COMBO (p->combo)->list; g_signal_connect_after (list, "event-after", G_CALLBACK (ephy_location_entry_list_event_after_cb), w); } static gboolean ephy_location_ignore_prefix (EphyLocationEntry *w) { char *text; int text_len; int i, k; gboolean result = FALSE; static const gchar *prefixes[] = { EPHY_AUTOCOMPLETION_USUAL_WEB_PREFIXES, NULL }; text = ephy_location_entry_get_location (w); text_len = g_utf8_strlen (text, -1); for (i = 0; prefixes[i] != NULL; i++) { const char *prefix = prefixes[i]; for (k = 0; k < g_utf8_strlen (prefix, -1); k++) { if (text_len == (k + 1) && (strncmp (text, prefix, k + 1) == 0)) { result = TRUE; } } } g_free (text); return result; } static gint ephy_location_entry_autocompletion_show_alternatives_to (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; if (ephy_location_ignore_prefix (w)) return FALSE; if (p->autocompletion) { LOG ("Show alternatives") ephy_location_entry_set_autocompletion_key (w); ephy_location_entry_autocompletion_show_alternatives (w); } p->show_alternatives_timeout = 0; return FALSE; } static void ephy_location_entry_autocompletion_hide_alternatives (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; if (p->autocompletion_window) { ephy_autocompletion_window_hide (p->autocompletion_window); p->autocompletion_window_visible = FALSE; } } static void ephy_location_entry_autocompletion_show_alternatives (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; if (p->autocompletion_window) { ephy_autocompletion_window_show (p->autocompletion_window); p->autocompletion_window_visible = TRUE; } } static void ephy_location_entry_autocompletion_unselect_alternatives (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; if (p->autocompletion_window) { ephy_autocompletion_window_unselect (p->autocompletion_window); } } static gint ephy_location_entry_autocompletion_to (EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; gchar *text; gchar *common_prefix; LOG ("Autocompletion to") ephy_location_entry_set_autocompletion_key (w); { GtkEditable *editable = GTK_EDITABLE (p->entry); gint sstart, send; gint pos = gtk_editable_get_position (editable); const gchar *text = gtk_entry_get_text (GTK_ENTRY (p->entry)); gint text_len = strlen (text); gtk_editable_get_selection_bounds (editable, &sstart, &send); if (pos != text_len || send != text_len) { /* the user is editing the entry, don't mess it */ LOG ("The user seems editing the text: pos = %d, strlen (text) = %d, sstart = %d, send = %d", pos, strlen (text), sstart, send) p->autocompletion_timeout = 0; return FALSE; } } common_prefix = ephy_autocompletion_get_common_prefix (p->autocompletion); LOG ("common_prefix: %s", common_prefix) if (common_prefix && (!p->before_completion || p->before_completion[0] == '\0')) { text = ephy_location_entry_get_location (w); g_free (p->before_completion); p->before_completion = text; } if (common_prefix) { /* check original length */ guint text_len = strlen (p->autocompletion_key); p->block_set_autocompletion_key = TRUE; /* set entry to completed text */ gtk_entry_set_text (GTK_ENTRY (p->entry), common_prefix); /* move selection appropriately */ gtk_editable_select_region (GTK_EDITABLE (p->entry), text_len, -1); p->block_set_autocompletion_key = FALSE; g_free (p->last_completion); p->last_completion = common_prefix; } p->autocompletion_timeout = 0; return FALSE; } static int get_editable_number_of_chars (GtkEditable *editable) { char *text; int length; text = gtk_editable_get_chars (editable, 0, -1); length = g_utf8_strlen (text, -1); g_free (text); return length; } static gboolean position_is_at_end (GtkEditable *editable) { int end; end = get_editable_number_of_chars (editable); return gtk_editable_get_position (editable) == end; } /* this is from the old location entry, need to do the autocompletion before implementing this */ static gboolean ephy_location_entry_key_press_event_cb (GtkWidget *entry, GdkEventKey *event, EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; static gboolean suggest = FALSE; guint keyval = event->keyval; if (p->autocompletion_timeout != 0) { gtk_timeout_remove (p->autocompletion_timeout); p->autocompletion_timeout = 0; } if (p->show_alternatives_timeout != 0) { gtk_timeout_remove (p->show_alternatives_timeout); p->show_alternatives_timeout = 0; } /* only suggest heuristic completions if TAB is hit twice */ if (event->keyval != GDK_Tab) { suggest = FALSE; } if (((event->state & GDK_Control_L || event->state & GDK_Control_R) && (keyval == GDK_a || keyval == GDK_b || keyval == GDK_c || keyval == GDK_d || keyval == GDK_e || keyval == GDK_f || keyval == GDK_h || keyval == GDK_k || keyval == GDK_u || keyval == GDK_v || keyval == GDK_w || keyval == GDK_x)) || (event->state == 0 && event->keyval == GDK_BackSpace)) { ephy_location_entry_autocompletion_hide_alternatives (w); return FALSE; } /* don't grab alt combos, thus you can still access the menus. */ if (event->state & GDK_MOD1_MASK) { ephy_location_entry_autocompletion_hide_alternatives (w); return FALSE; } /* make sure the end key works at all times */ if ((!((event->state & GDK_SHIFT_MASK) || (event->state & GDK_CONTROL_MASK) || (event->state & GDK_MOD1_MASK)) && (event->keyval == GDK_End))) { ephy_location_entry_autocompletion_hide_alternatives (w); gtk_editable_select_region (GTK_EDITABLE (p->entry), 0, 0); gtk_editable_set_position (GTK_EDITABLE (p->entry), -1); ephy_location_entry_autocompletion_unselect_alternatives (w); return TRUE; } switch (event->keyval) { case GDK_Left: case GDK_Right: ephy_location_entry_autocompletion_hide_alternatives (w); return FALSE; case GDK_Up: case GDK_Down: case GDK_Page_Up: case GDK_Page_Down: ephy_location_entry_autocompletion_hide_alternatives (w); //ephy_embed_grab_focus (window->active_embed); return FALSE; case GDK_Tab: { gchar *common_prefix = NULL; gchar *text; ephy_location_entry_set_autocompletion_key (w); gtk_editable_delete_selection (GTK_EDITABLE (p->entry)); text = ephy_location_entry_get_location (w); ephy_location_entry_autocompletion_unselect_alternatives (w); if (p->autocompletion) { common_prefix = ephy_autocompletion_get_common_prefix (p->autocompletion); } suggest = FALSE; if (common_prefix) { if (!p->before_completion) { p->before_completion = g_strdup (text); } p->block_set_autocompletion_key = TRUE; gtk_entry_set_text (GTK_ENTRY (p->entry), common_prefix); gtk_editable_set_position (GTK_EDITABLE (p->entry), -1); p->block_set_autocompletion_key = FALSE; ephy_location_entry_autocompletion_show_alternatives (w); if (!strcmp (common_prefix, text)) { /* really suggest something the next time */ suggest = TRUE; } g_free (common_prefix); } else { ephy_location_entry_autocompletion_hide_alternatives (w); } g_free (text); return TRUE; } case GDK_Escape: ephy_location_entry_autocompletion_hide_alternatives (w); if (p->before_completion) { ephy_location_entry_set_location (w, p->before_completion); g_free (p->before_completion); p->before_completion = NULL; gtk_editable_set_position (GTK_EDITABLE (p->entry), -1); return TRUE; } break; default: ephy_location_entry_autocompletion_unselect_alternatives (w); if ((event->string[0] > 32) && (event->string[0] < 126) && position_is_at_end (GTK_EDITABLE (entry))) { p->show_alternatives_timeout = g_timeout_add (SHOW_ALTERNATIVES_DELAY, (GSourceFunc) ephy_location_entry_autocompletion_show_alternatives_to, w); } break; } return FALSE; } static gboolean ephy_location_entry_content_is_text (const char *content) { return ((g_strrstr (content, ".") == NULL) && (g_strrstr (content, "/") == NULL)); } static void ephy_location_entry_activate_cb (GtkEntry *entry, EphyLocationEntry *w) { char *content; char *target = NULL; content = gtk_editable_get_chars (GTK_EDITABLE(entry), 0, -1); if (ephy_location_entry_content_is_text (content)) { target = w->priv->last_action_target; } ephy_location_entry_autocompletion_hide_alternatives (w); LOG ("In ephy_location_entry_activate_cb, activating %s", content) g_signal_emit (w, EphyLocationEntrySignals[ACTIVATED], 0, target, content); g_free (content); } static void ephy_location_entry_autocompletion_sources_changed_cb (EphyAutocompletion *aw, EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; LOG ("in ephy_location_entry_autocompletion_sources_changed_cb") if (p->autocompletion_timeout == 0 && p->last_completion && !strcmp (p->last_completion, gtk_entry_get_text (GTK_ENTRY (p->entry)))) { p->autocompletion_timeout = gtk_timeout_add (AUTOCOMPLETION_DELAY, (GSourceFunc) ephy_location_entry_autocompletion_to, w); } if (p->show_alternatives_timeout == 0 && p->autocompletion_window_visible) { p->show_alternatives_timeout = gtk_timeout_add (SHOW_ALTERNATIVES_DELAY, (GSourceFunc) ephy_location_entry_autocompletion_show_alternatives_to, w); } } void ephy_location_entry_set_location (EphyLocationEntry *w, const gchar *new_location) { EphyLocationEntryPrivate *p = w->priv; int pos; gtk_editable_delete_text (GTK_EDITABLE (p->entry), 0, -1); gtk_editable_insert_text (GTK_EDITABLE (p->entry), new_location, g_utf8_strlen (new_location, -1), &pos); } gchar * ephy_location_entry_get_location (EphyLocationEntry *w) { char *location = gtk_editable_get_chars (GTK_EDITABLE (w->priv->entry), 0, -1); return location; } void ephy_location_entry_set_autocompletion (EphyLocationEntry *w, EphyAutocompletion *ac) { EphyLocationEntryPrivate *p = w->priv; if (p->autocompletion) { g_signal_handlers_disconnect_matched (p->autocompletion, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, w); g_signal_handlers_disconnect_matched (p->autocompletion_window, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, w); g_object_unref (G_OBJECT (p->autocompletion)); g_object_unref (p->autocompletion_window); } p->autocompletion = ac; if (p->autocompletion) { g_object_ref (G_OBJECT (p->autocompletion)); p->autocompletion_window = ephy_autocompletion_window_new (p->autocompletion, p->entry); g_signal_connect (p->autocompletion_window, "activated", G_CALLBACK (ephy_location_entry_autocompletion_window_url_activated_cb), w); g_signal_connect (p->autocompletion_window, "hidden", G_CALLBACK (ephy_location_entry_autocompletion_window_hidden_cb), w); g_signal_connect (p->autocompletion, "sources-changed", G_CALLBACK (ephy_location_entry_autocompletion_sources_changed_cb), w); ephy_location_entry_set_autocompletion_key (w); } } static void ephy_location_entry_autocompletion_window_url_activated_cb (EphyAutocompletionWindow *aw, const char *target, int action, EphyLocationEntry *w) { char *content; if (action) { if (w->priv->last_action_target) g_free (w->priv->last_action_target); w->priv->last_action_target = g_strdup (target); } else { ephy_location_entry_set_location (w, target); } content = gtk_editable_get_chars (GTK_EDITABLE(w->priv->entry), 0, -1); LOG ("In location_entry_autocompletion_window_url_activated_cb, going to %s", content); ephy_location_entry_autocompletion_hide_alternatives (w); g_signal_emit (w, EphyLocationEntrySignals[ACTIVATED], 0, action ? content : NULL, target); g_free (content); } static void ephy_location_entry_autocompletion_window_hidden_cb (EphyAutocompletionWindow *aw, EphyLocationEntry *w) { EphyLocationEntryPrivate *p = w->priv; LOG ("In location_entry_autocompletion_window_hidden_cb"); p->autocompletion_window_visible = FALSE; if (p->show_alternatives_timeout) { g_source_remove (p->show_alternatives_timeout); p->show_alternatives_timeout = 0; } if (p->autocompletion_timeout) { g_source_remove (p->autocompletion_timeout); p->autocompletion_timeout = 0; } } void ephy_location_entry_activate (EphyLocationEntry *w) { GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (w->priv->entry); gtk_editable_select_region (GTK_EDITABLE(w->priv->entry), 0, -1); gtk_window_set_focus (GTK_WINDOW(toplevel), w->priv->entry); } static void ephy_location_entry_list_event_after_cb (GtkWidget *list, GdkEvent *event, EphyLocationEntry *e) { if (event->type == GDK_BUTTON_PRESS && ((GdkEventButton *) event)->button == 1) { gchar *url = ephy_location_entry_get_location (e); g_signal_emit (e, EphyLocationEntrySignals[ACTIVATED], 0, url); g_free (url); } } static void ephy_location_entry_editable_changed_cb (GtkEditable *editable, EphyLocationEntry *e) { ephy_location_entry_set_autocompletion_key (e); } static void ephy_location_entry_set_autocompletion_key (EphyLocationEntry *e) { EphyLocationEntryPrivate *p = e->priv; if (p->autocompletion && !p->block_set_autocompletion_key) { GtkEditable *editable = GTK_EDITABLE (p->entry); gint sstart, send; gchar *text; gtk_editable_get_selection_bounds (editable, &sstart, &send); text = gtk_editable_get_chars (editable, 0, sstart); ephy_autocompletion_set_key (p->autocompletion, text); g_free (p->autocompletion_key); p->autocompletion_key = text; } }