/*
* 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 <gtk/gtkentry.h>
#include <gtk/gtkwindow.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkmain.h>
#include <libgnomeui/gnome-entry.h>
#include <string.h>
/**
* 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;
gboolean going_to_site;
gboolean editing;
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_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 gboolean
location_focus_out_cb (GtkWidget *widget, GdkEventFocus *event, EphyLocationEntry *w)
{
w->priv->editing = FALSE;
return FALSE;
}
static void
ephy_location_entry_init (EphyLocationEntry *w)
{
EphyLocationEntryPrivate *p = g_new0 (EphyLocationEntryPrivate, 1);
w->priv = p;
p->last_action_target = NULL;
p->editing = FALSE;
p->before_completion = NULL;
ephy_location_entry_build (w);
g_signal_connect (w->priv->entry,
"focus_out_event",
G_CALLBACK (location_focus_out_cb),
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->before_completion);
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;
g_free (p->before_completion),
p->before_completion = gtk_editable_get_chars (GTK_EDITABLE(p->entry), 0, -1);
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 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;
}
static void
real_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);
}
/* 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;
guint keyval = event->keyval;
if (p->autocompletion_timeout != 0)
{
g_source_remove (p->autocompletion_timeout);
p->autocompletion_timeout = 0;
}
if (p->show_alternatives_timeout != 0)
{
g_source_remove (p->show_alternatives_timeout);
p->show_alternatives_timeout = 0;
}
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:
case GDK_Up:
case GDK_Down:
case GDK_Page_Up:
case GDK_Page_Down:
ephy_location_entry_autocompletion_hide_alternatives (w);
return FALSE;
case GDK_Escape:
real_entry_set_location (w, p->before_completion);
gtk_editable_set_position (GTK_EDITABLE (p->entry), -1);
ephy_location_entry_autocompletion_hide_alternatives (w);
return FALSE;
default:
w->priv->editing = TRUE;
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)
w->priv->editing = FALSE;
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->show_alternatives_timeout == 0
&& p->autocompletion_window_visible)
{
p->show_alternatives_timeout = g_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)
{
if (!w->priv->editing)
{
real_entry_set_location (w, new_location);
}
}
gchar *
ephy_location_entry_get_location (EphyLocationEntry *w)
{
char *location = gtk_editable_get_chars (GTK_EDITABLE (w->priv->entry), 0, -1);
return location;
}
static void
ephy_location_entry_autocompletion_window_url_selected_cb (EphyAutocompletionWindow *aw,
const char *target,
int action,
EphyLocationEntry *w)
{
real_entry_set_location (w, action ? w->priv->before_completion : target);
}
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, "selected",
G_CALLBACK (ephy_location_entry_autocompletion_window_url_selected_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
{
real_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_edit (EphyLocationEntry *w)
{
GtkWidget *toplevel;
w->priv->editing = TRUE;
toplevel = gtk_widget_get_toplevel (w->priv->entry);
gtk_window_set_focus (GTK_WINDOW(toplevel),
w->priv->entry);
}
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)
{
e->priv->going_to_site = TRUE;
}
}
static void
ephy_location_entry_editable_changed_cb (GtkEditable *editable, EphyLocationEntry *e)
{
EphyLocationEntryPrivate *p = e->priv;
ephy_location_entry_set_autocompletion_key (e);
if (p->going_to_site)
{
char *url = ephy_location_entry_get_location (e);
if (url && url[0] != '\0')
{
p->going_to_site = FALSE;
g_signal_emit (e, EphyLocationEntrySignals[ACTIVATED], 0, NULL, url);
g_free (url);
}
}
}
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;
}
}
void
ephy_location_entry_clear_history (EphyLocationEntry *w)
{
gnome_entry_clear_history (GNOME_ENTRY (w->priv->combo));
}