/*
 *  Copyright (C) 2000 Marco Pesenti Gritti
 *
 *  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 "galeon.h"
#include "embed.h"
#include "window.h"
#include "misc.h"
#include "autocompletion.h"
#include "history.h"
#include "bookmarks.h"
#include "prefs.h"
#include "mozilla.h"
#include "context.h"
#include "toolbar.h"
#include "find.h"
#include "session.h"
#include "spinner.h"
#include "themes.h"
#include "dnd-hints.h"

#include <string.h>
#include <gtkmozembed.h>
#include <libgnome/gnome-config.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-util.h>
#include <libgnomeui/gnome-app.h>
#include <libgnomeui/gnome-popup-menu.h>
#include <libgnomeui/gnome-stock.h>
#include <libgnomeui/gnome-entry.h>
#include <libgnomeui/gnome-app-helper.h>
#include <gdk/gdkkeysyms.h>

/* local function prototypes */
gboolean window_delete_cb (GtkWidget *widget, GdkEventAny *event, 
			   GaleonWindow *window);
gint window_zoom_spin_timeout_cb (GaleonWindow *window);
void window_entry_changed_cb (GtkEditable *editable, gboolean *changed); 
void window_location_gnomeentry_popwin_cb (GtkWidget *widget, 
					   GaleonWindow *window);
static void window_handle_go_button (GaleonWindow *window, LinkState state);
static void window_move_tab_to_window_menu_item_cb (GtkMenuItem *mi, 
						   GaleonWindow *window);
static gchar *guess_protocol (gchar *text);
GaleonWindow *window_get_window_at_location (gint x, gint y);
gint window_notebook_get_page_at_location (GtkNotebook *notebook,
					   gint x, gint y);
gboolean window_notebook_get_tab_borders (GtkNotebook *notebook,
					  gint page_num, gint *left,
					  gint *right, gint *top,
					  gint *bottom);
gint window_notebook_get_page_at_location (GtkNotebook *notebook, gint x,
					   gint y);
void window_notebook_initiate_grab (GaleonWindow *window, GtkWidget *widget);
gboolean window_notebook_motion_cb (GtkWidget *widget, GdkEventMotion *e,
				    GaleonWindow *window);
gboolean window_notebook_leave_cb (GtkWidget *widget, GdkEventCrossing *e,
				   GaleonWindow *window);

GTimer *zoom_timer = NULL;
#define ZOOM_DELAY 0.20

static GdkCursor *drag_cursor = NULL;

/**
 * mozembed_selection_get_cb: get selection on copy link location
 * FIXME HYBRID: move this elsewhere
 */
void
window_selection_get_cb (GtkWidget *widget, GtkSelectionData *selection_data,
			 guint info, guint time_stamp, gpointer data)
{
	gchar *text;

	text = gtk_object_get_data (GTK_OBJECT(widget), "selection");

	g_return_if_fail (text != NULL);

	gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
				8, text, strlen (text));
}

/**
 * window_selection_received_cb: load url if received on middle button click
 */
void
window_selection_received_cb (GtkWidget *widget,
			      GtkSelectionData *selection_data,
			      guint time, GaleonWindow *window)
{
	GdkModifierType modifier;
	
	return_if_not_window (window);

	if (selection_data->data)
	{
		gdk_window_get_pointer (window->WMain->window,
			NULL, NULL, &modifier);

		embed_activate_link_keyboard (window->active_embed, NULL, 
					      selection_data->data, modifier);
	}
}

/**
 * window_delete_cb: deleting toplevel window
 */
gboolean
window_delete_cb (GtkWidget *widget, GdkEventAny *event, GaleonWindow *window)
{
	/* this can cause one of two things; if this is one of many windows
	 * then we want to close this window but keep the others going. If
	 * this is the last window, we want to behave as though the user
	 * chose to exit Galeon (and do automatic session saving if the
	 * pref is set */

	if (g_list_length (all_windows) == 1 && 
	    !galeon_server_mode && !galeon_panel_mode)
	{
		/* exit the session */
		session_quit (FALSE);
	}
	else
	{
		/* close the window */
		window_close (window);
	}

	return TRUE;
}

/** 
 * window_back_forward_button_press_cb:
 */
gboolean
window_back_forward_button_press_cb (GtkButton *button,
				     GdkEventButton *event,
				     GaleonWindow *window)
{
	GtkMenu *menu = NULL;
	GaleonEmbed *embed;
	
	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	if (event->button == 3)
	{
		if (GTK_WIDGET (button) == window->forward_button ||
		    GTK_WIDGET (button) == window->forward_menuitem)
		{
			menu = embed_create_forward_menu (embed);
		}
		else if (GTK_WIDGET (button) == window->back_button ||
			 GTK_WIDGET (button) == window->back_menuitem)
		{
			menu = embed_create_back_menu (embed);
		}
		else
		{
			g_assert_not_reached ();
		}
		gnome_popup_menu_do_popup_modal (GTK_WIDGET (menu), NULL,
						 NULL, event, NULL);
		gtk_widget_unref (GTK_WIDGET(menu));
		return TRUE;
	}	
	return FALSE;
}

/** 
 * window_back_history_button_press_cb:
 */
gboolean
window_back_history_button_press_cb (GtkButton *button,
				     GdkEventButton *event,
				     GaleonWindow *window)
{
	GtkMenu *menu = NULL;
	GaleonEmbed *embed;
	
	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	menu = embed_create_back_menu (embed);
	gtk_signal_emit_stop_by_name (GTK_OBJECT (button),
				      "button-press-event");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
	gnome_popup_menu_do_popup_modal (GTK_WIDGET (menu), 
					 menu_position_under_widget,
					 button, event, NULL);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
	gtk_widget_unref (GTK_WIDGET(menu));
	return TRUE;
}

/** 
 * window_forward_history_button_press_cb:
 */
gboolean
window_forward_history_button_press_cb (GtkButton *button,
					GdkEventButton *event,
					GaleonWindow *window)
{
	GtkMenu *menu = NULL;
	GaleonEmbed *embed;
	
	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	menu = embed_create_forward_menu (embed);
	gtk_signal_emit_stop_by_name (GTK_OBJECT (button),
				      "button-press-event");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
	gnome_popup_menu_do_popup_modal (GTK_WIDGET (menu), 
					 menu_position_under_widget,
					 button, event, NULL);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
	gtk_widget_unref (GTK_WIDGET(menu));
	return TRUE;
}

/** 
 * window_home_button_release_event_cb: home button clicked
 */
gboolean
window_home_button_release_event_cb (GtkButton *button, GdkEventButton *event,
				     GaleonWindow *window)
{
	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);

	if (event->button != 3)
	{
		window_go_home (window, mouse_state_to_link_state 
						(event->button, event->state));
		if (GTK_WIDGET (button) == window->homepage_menuitem)
		{
			GtkMenuShell *shell =
				GTK_MENU_SHELL (GTK_WIDGET (button)->parent);
			gtk_menu_shell_deactivate (shell);
		}
		return TRUE;
	}
	return FALSE;
}

/** 
 * window_home_button_press_event_cb:  Open home page in new window on
 * middle click, or display a context menu on right-click.
 */
gboolean
window_home_button_press_event_cb (GtkButton *button, GdkEventButton *event,
				   GaleonWindow *window)
{
	GaleonEmbed *embed;
	GtkWidget *menu;
	gint action;

	/* homepage context menu */
	/* although there's only one entry I think it's still necessary
	 * to have a context menu for UI consistency -- MattA */
	static GnomeUIInfo menu_uiinfo[] =
	{
		GNOMEUIINFO_ITEM_STOCK (N_("Set current page as home page"),
					NULL, NULL, GNOME_STOCK_MENU_HOME),
		GNOMEUIINFO_END
	};
	
	/* check args */
	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	/* handle button accordingly */	
	if (event->button == 3)
	{
		/* show context menu */
		menu = gnome_popup_menu_new (menu_uiinfo);
		if (GTK_WIDGET (button) != window->homepage_menuitem)
		{
			context_menu_add_seperator (GTK_MENU (menu));
			context_show_appearance_menu (window, GTK_MENU (menu), 
						      NULL, TRUE, TRUE);
		}
		action = gnome_popup_menu_do_popup_modal 
			(GTK_WIDGET (menu), NULL, NULL, event, NULL);

		/* perform set homepage action if chosen */
		if (action == 0 && embed->site_location != NULL &&
		    strlen (embed->site_location) != 0)
		{
			eel_gconf_set_string (CONF_GENERAL_HOMEPAGE,
						 embed->site_location);
		}

		/* destroy the popup menu */
		gtk_widget_unref (menu);
		return TRUE;
	}
	return FALSE;
}

/** 
 * window_new_button_press_cb: new browser button pressed
 */
gboolean
window_new_button_press_cb (GtkButton *button, GdkEventButton *event,
			    GaleonWindow *window)
{
	GaleonEmbed *embed;
	GtkWidget *menu;
	gint action;

	/* new button context menu */
	static GnomeUIInfo menu_uiinfo[] =
	{
		GNOMEUIINFO_ITEM_STOCK (N_("Open new window"),
					NULL, NULL, GNOME_STOCK_MENU_NEW),
		GNOMEUIINFO_ITEM_STOCK (N_("Open new tab"),
					NULL, NULL, GNOME_STOCK_MENU_NEW),
		GNOMEUIINFO_END
	};

	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	switch (event->button)
	{
	case 3:
		/* show context menu */
		menu = gnome_popup_menu_new (menu_uiinfo);
		context_menu_add_seperator (GTK_MENU (menu));
		context_show_appearance_menu (window, GTK_MENU (menu), 
					      NULL, TRUE, TRUE);
		action = gnome_popup_menu_do_popup_modal 
			(GTK_WIDGET (menu), NULL, NULL, event, NULL);
		
		/* do action */
		if (action != -1)
		{
			embed_create_default (embed, action == 0);
		}
		gtk_widget_unref (menu);
		return TRUE;

	default:
		return FALSE;
	}
}
/** 
 * window_new_button_release_cb: new browser button clicked
 */
gboolean
window_new_button_release_cb (GtkButton *button, GdkEventButton *event,
			    GaleonWindow *window)
{
	gint tabbed_mode;
	gint shift_modifier;
	GaleonEmbed *embed;
	GaleonEmbed *new_embed;

	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	tabbed_mode = eel_gconf_get_boolean (CONF_TABS_TABBED) ? 1 : 0;
	shift_modifier = (event->state & GDK_SHIFT_MASK) ? 0 : 1;

	switch (event->button)
	{
	case 1:
		new_embed = embed_create_default (embed, (tabbed_mode ^ 
							  shift_modifier));
		/* FIXME: why this, why here? */
		window_update_nav_controls (window);

		window_grab_location_focus (window);
		return TRUE;
		
	case 2:
		/* hmmm, this is probably a bit hacky ;-) -- MattA */
		embed = embed_create (embed, (tabbed_mode ^ shift_modifier), 
				      TRUE, TRUE, GTK_MOZ_EMBED_FLAG_ALLCHROME);
		embed_set_visibility (embed, TRUE);		
		gtk_selection_convert (embed->parent_window->WMain,
				       GDK_SELECTION_PRIMARY,
				       GDK_SELECTION_TYPE_STRING,
				       GDK_CURRENT_TIME);
		return FALSE;
	default:
		return FALSE;
	}
}

/**
 * window_new_button_drag_data_received_cb: called when data is dropped
 * on the "New" button
 */
void
window_new_button_drag_data_received_cb (GtkWidget *widget, 
					 GdkDragContext *drag_context,
					 gint x, gint y,
					 GtkSelectionData *selection_data,
					 guint info, guint time,
					 GaleonWindow *window)
{
	gboolean tabbed_mode;
	gchar **tmp;

	/* check args */
	g_return_if_fail (selection_data != NULL);
	g_return_if_fail (selection_data->data != NULL);
	return_if_not_window (window);
	return_if_not_embed (window->active_embed);

	/* get state */
	tabbed_mode = eel_gconf_get_boolean (CONF_TABS_TABBED);

	/* check drop data */
	switch (info)
	{
	case DND_TARGET_NETSCAPE_URL:
		tmp = g_strsplit(selection_data->data, "\n", 2);
		if (tmp)
		{
			embed_create_from_url (window->active_embed,
					       tmp[0],
					       TRUE, !tabbed_mode);
		}
		else
		{
			embed_create_from_url (window->active_embed,
					       selection_data->data,
					       TRUE, !tabbed_mode);
		}
		window_update_nav_controls (window);
		g_strfreev (tmp);
		break;
	case DND_TARGET_GALEON_URL:
	case DND_TARGET_STRING:
		embed_create_from_url (window->active_embed,
				       selection_data->data,
				       TRUE, !tabbed_mode);
		window_update_nav_controls (window);
		break;

	default:
		g_warning ("Unknown DND type");
		break;
	}
}

/** 
 * window_back_forward_button_release_cb: back/forward button clicked
 */
gboolean
window_back_forward_button_release_cb (GtkButton *button,
				     GdkEventButton *event,
				     GaleonWindow *window)
{
	char *url = NULL;
	GaleonEmbed *embed;
	
	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);
	return_val_if_not_embed (window->active_embed, FALSE);

	if (event->button == 3)
		return FALSE;
	
	embed = window->active_embed;
	if (event->button == 1 && !(event->state & GDK_SHIFT_MASK))
	{
		if (GTK_WIDGET (button) == window->forward_button ||
		    GTK_WIDGET (button) == window->forward_menuitem)
		{
			gtk_moz_embed_go_forward 
				(GTK_MOZ_EMBED (embed->mozEmbed));
		}
		else if (GTK_WIDGET (button) == window->back_button ||
			 GTK_WIDGET (button) == window->back_menuitem)
		{
			gtk_moz_embed_go_back 
				(GTK_MOZ_EMBED (embed->mozEmbed));
		}
		else
		{
			g_assert_not_reached ();
		}
		if (GTK_WIDGET (button) == window->back_menuitem ||
	    	    GTK_WIDGET (button) == window->forward_menuitem)
		{
			GtkMenuShell *shell =
				GTK_MENU_SHELL (GTK_WIDGET (button)->parent);
			gtk_menu_shell_deactivate (shell);
		}
		return TRUE;
	}
	
	if (GTK_WIDGET (button) == window->forward_button ||
	    GTK_WIDGET (button) == window->forward_menuitem)
	{
		url = mozilla_session_history_get_url_relative (embed, 1);
	}
	else if (GTK_WIDGET (button) == window->back_button ||
		 GTK_WIDGET (button) == window->back_menuitem)
	{
		url = mozilla_session_history_get_url_relative (embed, -1);
	}
	else
	{
		g_assert_not_reached ();
	}
	embed_activate_link_mouse (window->active_embed, NULL, url, event);
	if (url != NULL) free (url);

	if (GTK_WIDGET (button) == window->back_menuitem ||
	    GTK_WIDGET (button) == window->forward_menuitem)
	{
		GtkMenuShell *shell =
			GTK_MENU_SHELL (GTK_WIDGET (button)->parent);
		gtk_menu_shell_deactivate (shell);
	}
	
	return TRUE;
}

/** 
 * window_new_button_clicked_cb: new browser button pressed
 */
gboolean
window_up_button_press_event_cb (GtkButton *button, GdkEventButton *event,
				 GaleonWindow *window)
{
	GtkMenu *menu;

	/* check arguments */
	return_val_if_not_window (window, FALSE);
	return_val_if_not_embed (window->active_embed, FALSE);

	/* check button */
	if (event->button != 3)
	{
		return TRUE;
	}

	/* make menu */
	menu = embed_create_up_menu (window->active_embed);
	if (menu == NULL)
	{
		return TRUE;
	}

	/* show popup menu */
	gnome_popup_menu_do_popup_modal (GTK_WIDGET (menu),
					 NULL, NULL, event, NULL);

	/* destroy the popup menu */
	gtk_object_unref (GTK_OBJECT (menu));

	/* success */
	return FALSE;
}

/** 
 * window_up_button_release_event_cb: up browser button clicked
 */
gboolean
window_up_button_release_event_cb (GtkButton *button, GdkEventButton *event,
				   GaleonWindow *window)
{
	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);
	return_val_if_not_embed (window->active_embed, FALSE);
	if (event->button == 3)
		return FALSE;
	embed_go_up (window->active_embed, 0, 
		mouse_state_to_link_state (event->button, event->state));
	if (GTK_WIDGET (button) == window->up_menuitem)
	{
		GtkMenuShell *shell =
			GTK_MENU_SHELL (GTK_WIDGET (button)->parent);
		gtk_menu_shell_deactivate (shell);
	}
	return TRUE;
}

/** 
 * window_refresh_button_release_event_cb: refresh button released
 */
gboolean
window_refresh_button_release_event_cb (GtkButton *button, 
					GdkEventButton *event,
					GaleonWindow *window)
{
	GaleonEmbed *embed;

	/* check args */
	return_val_if_not_window (window, FALSE);
	return_if_not_sane_click (button, event);

	embed = window->active_embed;
	return_val_if_not_embed (embed, TRUE);

	switch (event->button)
	{
	case 1:
		if (event->state & GDK_SHIFT_MASK)
		{
			embed_reload (embed, 
				      GTK_MOZ_EMBED_FLAG_RELOADBYPASSPROXYANDCACHE);	
		}
		else
		{
			embed_reload (embed, 
				      GTK_MOZ_EMBED_FLAG_RELOADNORMAL);	
		}
		return FALSE;
		break;
	}

	if (GTK_WIDGET (button) == window->refresh_menuitem)
	{
		GtkMenuShell *shell =
			GTK_MENU_SHELL (GTK_WIDGET (button)->parent);
		gtk_menu_shell_deactivate (shell);
	}
	return TRUE;
}

/** 
 * window_refresh_button_press_event_cb: refresh button pressed
 */
gboolean
window_refresh_button_press_event_cb (GtkButton *button, GdkEventButton *event,
				      GaleonWindow *window)
{
	GtkWidget *menu;
	gint action;
	GList *l;
	GaleonEmbed *embed;

	/* reload context menu */
	static GnomeUIInfo menu_uiinfo[] =
	{
		GNOMEUIINFO_ITEM_STOCK (N_("Reload all tabs"),
					NULL, NULL, GNOME_STOCK_MENU_REFRESH),
		GNOMEUIINFO_ITEM_STOCK (N_("Reload all windows"),
					NULL, NULL, GNOME_STOCK_MENU_REFRESH),
		GNOMEUIINFO_SEPARATOR,
		GNOMEUIINFO_ITEM_STOCK (N_("Bypass cache"),
					NULL, NULL, GNOME_STOCK_MENU_REFRESH),
		GNOMEUIINFO_ITEM_STOCK (N_("Bypass proxy"),
					NULL, NULL, GNOME_STOCK_MENU_REFRESH),
		GNOMEUIINFO_ITEM_STOCK (N_("Bypass cache and proxy"),
					NULL, NULL, GNOME_STOCK_MENU_REFRESH),
		GNOMEUIINFO_END
	};

	/* check args */
	return_val_if_not_window (window, FALSE);

	embed = window->active_embed;
	return_val_if_not_embed (embed, TRUE);

	/* handle button accordingly */	
	switch (event->button)
	{
	case 1:
		/* nothing -- handled by click callback */
		break;

	case 2:
		/* nothing on middle click */
		break;

	case 3:
		/* show context menu */
		menu = gnome_popup_menu_new (menu_uiinfo);
		if (GTK_WIDGET (button) != window->refresh_menuitem)
		{
			context_menu_add_seperator (GTK_MENU (menu));
			context_show_appearance_menu (window, GTK_MENU (menu), 
						      NULL, TRUE, TRUE);
		}
		action = gnome_popup_menu_do_popup_modal 
			(GTK_WIDGET (menu), NULL, NULL, event, NULL);

		/* perform set homepage action if chosen */
		switch (action)
		{
		case 0: /* reload all tabs */
			window_reload_all (window);
			break;

		case 1: /* reload all windows */
			for (l = all_windows; l != NULL; l = g_list_next (l))
			{
				window_reload_all ((GaleonWindow *)(l->data));
			}
			break;
		case 3: 
			embed_reload (embed, GTK_MOZ_EMBED_FLAG_RELOADBYPASSCACHE);
			break;
		case 4: 
			embed_reload (embed, GTK_MOZ_EMBED_FLAG_RELOADBYPASSPROXY);
			break;
		case 5: 
			embed_reload (embed, 
				      GTK_MOZ_EMBED_FLAG_RELOADBYPASSPROXYANDCACHE);
			break;
		}

		/* destroy the popup menu */
		gtk_widget_unref (menu);
		break;
	}

	return TRUE;
}

/** 
 * window_stop_button_clicked_cb: stop button clicked
 */
void
window_stop_button_clicked_cb (GtkButton *button, GaleonWindow *window)
{
	return_if_not_window (window);
	gtk_moz_embed_stop_load (GTK_MOZ_EMBED(window->active_embed->mozEmbed));
}

/** 
 * window_generic_button_press_event_cb: some button clicked, show appearance
 * menu
 */
gboolean
window_generic_button_press_event_cb (GtkButton *button, GdkEventButton *event,
				      GaleonWindow *window)
{
	GtkWidget *menu;

	/* check args */
	return_val_if_not_window (window, FALSE);

	/* handle button accordingly */	
	switch (event->button)
	{
	case 1:
		/* nothing -- handled by click callback */
		break;

	case 2:
		/* nothing on middle click */
		break;

	case 3:
		/* show context menu */
		menu = gtk_menu_new ();
		context_show_appearance_menu (window, GTK_MENU(menu), 
					      NULL, TRUE, FALSE);
		gnome_popup_menu_do_popup_modal 
			(GTK_WIDGET (menu), NULL, NULL, event, NULL);

		/* destroy the popup menu */
		gtk_widget_unref (menu);
		break;
	}

	return TRUE;
}

/** 
 * window_location_entry_key_press_cb: key pressed in the url entry
 */
gboolean
window_location_entry_key_press_cb (GtkWidget *widget, GdkEventKey *event,
				    GaleonWindow *window)
{
	static gchar *before_completion = NULL;
	static gboolean suggest = FALSE;
	GtkEntry *entry = GTK_ENTRY (widget);
	GtkEditable *editable = GTK_EDITABLE (widget);
	gboolean url_dialog = FALSE;
	GaleonEmbed *embed;
	guint keyval;

	g_return_val_if_fail (GTK_IS_ENTRY (widget), TRUE);
	return_val_if_not_window (window, TRUE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, TRUE);

	auto_completion_reset();

	/* only suggest heuristic completions if TAB is hit twice */
	if (event->keyval != GDK_Tab)
		suggest = FALSE;

	/* don't process the standard GtkEntry editing shortcuts */
	if (event->keyval > GDK_A && event->keyval < GDK_Z)
		keyval = event->keyval + 0x20;
	else
		keyval = event->keyval;

	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))
	{
		return TRUE;
	}

	/* don't grab alt combos, thus you can still access the menus. */
	if (event->state & GDK_MOD1_MASK)
		return FALSE;

	/* check to see if key press is in the window's location bar or
	   the open url dialog */
	if (widget != window->location_entry)
	{
		url_dialog = TRUE;
	}

	if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
	{
		if (url_dialog)
		{
			open_url_handle_link (window, 0);
		}
		else
		{
			window_handle_go_button (window, 0);
		}
		return TRUE;
	}

	/* 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)))
	{
		gtk_editable_select_region(editable, 0, 0); 
		gtk_editable_set_position(editable, -1);
		return TRUE;
	}

	switch (event->keyval)
	{
	case GDK_Left:
	case GDK_Right:
		return TRUE;
	case GDK_Tab:
	{
		gchar *common_prefix = NULL;
		gchar *text;

		gtk_editable_delete_selection (editable);
		text = gtk_editable_get_chars (editable, 0, -1);
		common_prefix = 
			auto_completion_complete_url_extended (text, suggest);
		suggest = FALSE;
		if (common_prefix)
		{
			if (!before_completion) 
				before_completion = g_strdup(text);

			gtk_entry_set_text (entry, common_prefix);
			auto_completion_display_alternatives (window, widget);
			if (!strcmp (common_prefix, text))
			{
				/* really suggest something the next time */
				suggest = TRUE; 
			}
			g_free (common_prefix);
		}
		return TRUE;
	}
	case GDK_Escape:
		auto_completion_reset();

		if (before_completion)
		{
			gtk_entry_set_text (entry, before_completion);
			g_free (before_completion);
			before_completion = NULL;
			return TRUE;
		}
		else
		{
			gtk_entry_set_text (entry, embed->site_location);
		}
		break;
	default:
		break;
	}

	if ((event->string[0] > 32) && (event->string[0] < 126)) 
	{
		if (before_completion != NULL) 
		{
			g_free (before_completion);
			before_completion = NULL;
		}
		auto_completion_display (window, widget);
		return TRUE;
	}

	/* The URL dialog connects to this handler with *_connect_after,
	   so we need to return TRUE by default */
	if (url_dialog)
		return TRUE;

	return FALSE;
}

/**
 * window_location_entry_button_press_cb: select the whole location field
 * when the user double clicks on the entry. This is preferable to the
 * GNOME and X standard way of selecting since it's not selecting english
 * text but instead a URL.
 */
gboolean
window_location_entry_button_press_cb (GtkWidget *entry, 
				       GdkEventButton *event, 
				       GaleonWindow *window)
{
	if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
	{
		gtk_editable_set_position (GTK_EDITABLE (entry), -1);
		gtk_entry_select_region (GTK_ENTRY (entry), 0, -1);

		/* Ugh, more GTK+ goofiness. Returning TRUE doesn't stop
		   emission of the signal, so we have to do it ourselves */
		gtk_signal_emit_stop_by_name (GTK_OBJECT (entry),
		                              "button-press-event");
		return TRUE;
	}
	return TRUE;
}

static void
window_handle_go_button (GaleonWindow *window, LinkState state)
{
	gchar *text;
	gchar *text2;
	GList *wl;
	GnomeEntry *ge;
	GaleonEmbed *embed;

	return_if_not_window (window);
	embed = window->active_embed;
	return_if_not_embed (embed);

	if (window->location_entry == NULL) return;
	if (window->location_gnomeentry == NULL) return;

	text = gtk_editable_get_chars (GTK_EDITABLE (window->location_entry),
				       0, -1);

	/* make sure the string isn't empty */
	if (strcmp (text, "") != 0)
	{
		/* forcibly save contents of entry */
		ge = GNOME_ENTRY (window->location_gnomeentry);
		gnome_entry_save_history (ge);

		/* add item to location bars in all other
		 * open browser windows */
		for (wl = all_windows; wl != NULL; wl = wl->next)
		{
			GaleonWindow *awindow = (GaleonWindow *)(wl->data);

			/* skip if no locatio entry to fill */
			if (awindow->location_gnomeentry == NULL)
			{
				continue;
			}

			/* reload entry */
			gnome_entry_load_history 
				(GNOME_ENTRY (awindow->location_gnomeentry));
		}

		text2 = bookmarks_parse_nick (text, NULL);

		if (text2) 
		{
			g_free (text);
			text = text2;
		}
		else
		{
			text2 = guess_protocol (text);
			if (text2)
			{
				g_free (text);
				text = text2;
			}
		}
	}
	/* empty string -- load a blank page */
	else
	{
		g_free (text);
		text = g_strdup ("about:blank");
	}

	embed_activate_link (embed, NULL, text, state);

	if (window->active_embed)
	{
		embed_grab_focus (embed);
	}

	g_free (text);
}

/* is the URI does not specify a protocol, try to
 * guess one ourselves looking in the history before
 * giving it to mozilla */
static gchar *
guess_protocol (gchar *text)
{
	
	/* this is to check that the url does not already 
	   specify a protocol. Obviously, this check is not 
	   very good... */
	gchar *colon = strchr (text, ':');
	if (!colon || ((colon - text) < 12))
	{
		static const gchar *guess_protocols[] = {
			"", "https://", "http://", "ftp://", NULL };
		int i;
		for (i = 0; guess_protocols[i] != NULL; i++)
		{
			gchar *guess = g_strconcat 
				(guess_protocols[i], text, NULL);
			if (history_is_visited (guess))
			{
				return guess;
			}
			g_free (guess);
		}
	}
	/* nothing better can be found */
	return NULL;
}

/** 
 * window_go_button_release_cb: go button released
 */
gboolean
window_go_button_release_cb (GtkButton *button, GdkEventButton *event,
			     GaleonWindow *window)
{
	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);

	/* open location in correct place */
	window_handle_go_button (window,
		mouse_state_to_link_state (event->button, event->state));
	return TRUE;
}

/**
 * window_clear_location_button_release_cb
 */
gboolean
window_clear_location_button_release_cb (GtkButton *button,
					 GdkEventButton *event,
					 GaleonWindow *window)
{
	return_if_not_sane_click (button, event);
	return_val_if_not_window (window, FALSE);
	window_clear_url_entry (window);
	return TRUE;
}

/**
 * Changes the zoom if enough time time has passed 
 */
gint 
window_zoom_spin_timeout_cb (GaleonWindow *window)
{
	GaleonEmbed *embed;
	gint zoom;

	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	/* timer still valid? */
	if (zoom_timer == NULL)
	{
		return FALSE;
	}

	/* okay, we're ready to set */
	if (g_timer_elapsed (zoom_timer, NULL) >= ZOOM_DELAY)
	{
		/* kill off the timer */
		g_timer_destroy (zoom_timer);
		zoom_timer = NULL;

		/* get the value of the spin */
		zoom = gtk_spin_button_get_value_as_int 
			(GTK_SPIN_BUTTON (window->zoom_spin));

		/* set */
		embed_set_zoom (embed, zoom);

		/* store for this site */
		history_set_zoom (embed->site_location, zoom);

		/* done now */
		return FALSE;
	}

	/* call me again */
	return TRUE;
}

/** 
 * window_zoom_spin_changed_cb: zoom spin value changed. Starts the zoom timer
 */
void 
window_zoom_spin_changed_cb (GtkEditable *editable, GaleonWindow *window)
{
	gint zoom;

	/* get the value of the spin */
	zoom = gtk_spin_button_get_value_as_int 
		(GTK_SPIN_BUTTON (window->zoom_spin));

	/* check we haven't already registered the change */
	if (window->active_embed != NULL && window->active_embed->zoom == zoom)
	{
		return;
	}

	/* destroy any existing timer */
	if (zoom_timer != NULL)
	{
		g_timer_destroy (zoom_timer);
	}

	/* start the new one */
	zoom_timer = g_timer_new();
	g_timer_start (zoom_timer);
	g_timeout_add (50, (GSourceFunc) window_zoom_spin_timeout_cb, window);
}

/** 
 * window_zoom_spin_button_press_cb: stop context menu from appearing
 * (context menu here will make galeon go nuts)
 */
gboolean
window_zoom_spin_button_press_cb (GtkButton *button,
				     GdkEventButton *event,
				     GaleonWindow *window)
{
	return TRUE;
}

/** 
 * window_drag_pixmap_drag_data_get_cb:
 */
void
window_drag_pixmap_drag_data_get_cb (GtkWidget *widget, 
				     GdkDragContext *context,
				     GtkSelectionData *selection_data, 
				     guint info, guint time, 
				     GaleonWindow *window)
{
	GaleonEmbed *embed;
	BookmarkItem *b;
	gchar *mem;

	return_if_not_window (window);
	embed = window->active_embed;
	return_if_not_embed (embed);

	switch (info)
	{
	case DND_TARGET_GALEON_BOOKMARK:
		b = bookmarks_new_bookmark (BM_SITE, TRUE, embed->site_title_utf8,
					    embed->site_location,
					    NULL, NULL, NULL);
		mem = bookmarks_item_to_string (b);
		gtk_selection_data_set 
			(selection_data, selection_data->target,
			 8, mem, strlen (mem));
		g_free (mem);
		break;
	case DND_TARGET_STRING:
	case DND_TARGET_NETSCAPE_URL:
	case DND_TARGET_GALEON_URL:
		gtk_selection_data_set 
			(selection_data, selection_data->target,
			 8, embed->site_location, 
			 strlen (embed->site_location));
		break;
	default:
		g_warning ("Unknown DND type");
		break;
	}
}

/** 
 * window_location_entry_drag_data_received_cb:
 */
void
window_location_entry_drag_data_received_cb (GtkWidget *widget, 
				 GdkDragContext *drag_context, gint x, gint y,
				 GtkSelectionData *selection_data, guint info,
				 guint time, GaleonWindow *window)
{
	gchar *url = selection_data->data;
	gchar **tmp;
	gint i;

	window_clear_url_entry (window);

	switch (info) {
	case DND_TARGET_NETSCAPE_URL:
		tmp = g_strsplit(selection_data->data, "\n", 2);
		if (tmp)
		{
			gtk_editable_insert_text 
				(GTK_EDITABLE (window->location_entry),
				 tmp[0], strlen (tmp[0]), &i);
			g_strfreev (tmp);
		}
		else
		{
			gtk_editable_insert_text 
				(GTK_EDITABLE (window->location_entry),
				 url, strlen (url), &i);
		}
		break;
	case DND_TARGET_STRING:
	case DND_TARGET_GALEON_URL:
	case DND_TARGET_TEXT_URI_LIST:/* FIXME: won't work right for a list */
		gtk_editable_insert_text 
			(GTK_EDITABLE (window->location_entry),
			 url, strlen (url), &i);
		break;
	default:
		g_warning ("Unknown DND type");
		break;
	}
}

/** 
 * window_zoom_spin_key_press_cb: Ignore up/down key presses 
 */
gboolean
window_zoom_spin_key_press_cb (GtkWidget *widget, GdkEventKey *event, GaleonWindow *window)
{
	return_val_if_not_window (window, FALSE);
	if ((event->keyval == GDK_Up)
	    || (event->keyval == GDK_Down)
	    || (event->keyval == GDK_Page_Up) 
	    || (event->keyval == GDK_Page_Down)) 
	{
		event->keyval = GDK_VoidSymbol;
		embed_grab_focus (window->active_embed);
	}
	return FALSE;
}

void
window_entry_changed_cb (GtkEditable *editable, gboolean *changed)
{
	*changed = TRUE;
}

/* 
 * window_location_gnomeentry_popwin_cb:
 * Handler used to see if a selection was made in the location entry.
 * If a selection is made and the go toolbar is hidden, it will load
 * the URL.  It will also clear out the focus box that the GtkCombo
 * widget leaves behind after making a selection. 
 */
void
window_location_gnomeentry_popwin_cb (GtkWidget *widget, GaleonWindow *window)
{
	GtkCombo *combo;
	GtkContainer *container;
	GtkList *list;
	static gint selection_made;

	g_assert(widget!=NULL);
	g_assert(window->location_gnomeentry!=NULL);

	if (GTK_WIDGET_VISIBLE(widget))
	{
		combo = GTK_COMBO(window->location_gnomeentry);
		list = GTK_LIST(combo->list);
		container = GTK_CONTAINER(list);

		if (container->focus_child)
		{
			if (!GTK_LIST(container)->selection)
			{
				gtk_window_set_focus (GTK_WINDOW (GTK_COMBO 
						      (combo)->popwin), NULL);
				container->focus_child = NULL;
				list->last_focus_child = NULL;
			}

		}

		selection_made = FALSE;

		/* connect a handler to the entry's "changed" signal */
		gtk_signal_connect(GTK_OBJECT(window->location_entry),
		                   "changed",
		                   GTK_SIGNAL_FUNC(window_entry_changed_cb),
		                   &selection_made);
	}
	else
	{
		/* connect a handler to the entry's "changed" signal */
		gtk_signal_disconnect_by_func
			(GTK_OBJECT (window->location_entry),
			 GTK_SIGNAL_FUNC (window_entry_changed_cb),
			 &selection_made);

		if (selection_made)
		{
			window_handle_go_button (window, 0);
		}
	}
}

/**
 * window_drag_data_get_cb:
 */
void
window_drag_data_get_cb (GtkWidget *widget, GdkDragContext *context,
		    GtkSelectionData *selection_data, 
		    guint info, guint time, 
		    GaleonWindow *window)
{
	gchar *link;
	g_return_if_fail (window->WMain != NULL);

	link = gtk_object_get_data (GTK_OBJECT (widget), "dragging_link");

	if (link) {
		switch (info) {
		case DND_TARGET_STRING:
		case DND_TARGET_NETSCAPE_URL:
		case DND_TARGET_GALEON_URL:
			gtk_selection_data_set 
				(selection_data, selection_data->target,
				 8, link, strlen (link));
			break;
		default:
			g_warning ("Unknown DND type");
			break;
		}
	} 
}

/**
 * window_progress_action: update the status bar progress
 */
gboolean
window_progress_action (GaleonWindow *window)
{
	GtkProgress *progress;
	gfloat val;

	/* check window */
	return_val_if_not_window (window, FALSE);

	/* check window */
	g_return_val_if_fail (window->appbar != NULL, FALSE);

	/* check we're still meant to be running */
	if (!(window->progress_timeout))
	{
		return FALSE;
	}
	
	/* find the progress widget */
	progress = gnome_appbar_get_progress (GNOME_APPBAR (window->appbar));

	/* update progressive loader "activity" indicator */
	val = gtk_progress_get_value (progress);
	if (++val > 100)
	{
		val = 0;
	}
	gtk_progress_set_value (progress, val/100);

	/* continue to be called */
	return TRUE;
}

/**
 * window_history_button_clicked_cb: history button clicked, show docked
 * history view unless already shown, in which case hide it.
 */
void
window_history_button_clicked_cb (GtkButton *button, GaleonWindow *window)
{
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
	{
		if (window->dock_type != DOCK_HISTORY)
		{
			history_show_dock (window);
		}
	}
	else
	{
		if (window->dock_type != DOCK_NONE)
		{
			window_undock (window);
		}
	}
}

/**
 * window_bookmarks_button_clicked_cb: bookmarks button clicked, show docked
 * bookmarks view unless already shown, in which case hide it.
 */
void
window_bookmarks_button_clicked_cb (GtkButton *button, GaleonWindow *window)
{
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
	{
		if (window->dock_type != DOCK_BOOKMARKS)
		{
			bookmarks_editor_show_dock (window);
		}
	}
	else
	{
		if (window->dock_type != DOCK_NONE)
		{
			window_undock (window);
		}
	}
}

/**
 * window_print_button_clicked_cb: print button clicked, show dialog
 */
void
window_print_button_clicked_cb (GtkButton *button, GaleonWindow *window)
{
	print_show_dialog (window);
}

void 
window_session_history_menuitem_activate_cb (GtkMenuItem *menuitem, 
					     gint index)
{
	session_history_load_index (index);
}

/**
 * window_get_window_at_location: returns the Galeon window under the
 * location (x, y), or NULL if no Galeon window is found there
 */
GaleonWindow *
window_get_window_at_location (gint x, gint y)
{
	GdkWindow *gdk_window;
	GList *l;
	GaleonWindow *window = NULL;

	/* find out which window (if any) the embed was dropped in */
	gdk_window = gdk_window_at_pointer (&x, &y);
	if (gdk_window) gdk_window = gdk_window_get_toplevel (gdk_window);
	l = all_windows;
	while (l)
	{
		if (gdk_window == ((GaleonWindow *) l->data)->WMain->window)
		{
			window = l->data;
			break;
		}
		l = l->next;
	}

	return window;
}

/**
 * window_notebook_switch_page: called in tabbed mode when the user
 * selects a different browser tab
 */
void
window_notebook_switch_page_cb (GtkNotebook *notebook, 
			        GtkNotebookPage *page, guint page_num)
{
	GtkMozEmbed *mozEmbed;
	GaleonWindow *window;
	GaleonEmbed *old_embed;
	GaleonEmbed *embed;

	g_return_if_fail (notebook != NULL);
	g_return_if_fail (page != NULL);

	/* check we REALLY REALLY have switched page */
	if (GTK_NOTEBOOK (notebook)->cur_page != page)
	{
		return;
	}

	/* find the GtkMozEmbed from the page */
	mozEmbed = (GtkMozEmbed *)page->child;
	g_return_if_fail (GTK_IS_MOZ_EMBED (mozEmbed));

	/* find the GaleonEmbed from the GtkMozEmbed */
	embed = gtk_object_get_data (GTK_OBJECT (mozEmbed), "GaleonEmbed");
	return_if_not_embed (embed);

	/* find the GaleonWindow form the GaleonEmbed */
	window = embed->parent_window;
	return_if_not_window (window);

	/* if not viewed this before we need to change state */
	if (!(embed->has_been_viewed))
	{
		embed->has_been_viewed = TRUE;
	}

	/* set this as the active embed and save the content of the
	 * location bar of the old embed */
	old_embed = window->active_embed;
	if (old_embed != NULL)
	{
		/* save edited site location information */
		save_site_location (old_embed);

		/* no longer the active embed */
		old_embed->is_active = FALSE;
		embed_update_tab_status (old_embed);
	}

	/* this is now the active window */
	embed->is_active = TRUE;
	window->active_embed = embed;

	/* move this embed to front of tab list to maintain stacking order */
	window->embed_list = g_list_remove (window->embed_list, embed);
	window->embed_list = g_list_prepend (window->embed_list, embed);

	/* update security icon */
	if (!(old_embed && embed->secure == old_embed->secure))
		window_statusbar_set_security_icon (window, embed->secure);

	/* update tab label colour and closebutton status */
	embed_update_tab_status (embed);

	/* set the global title and location -- this is the lazy way! */
	embed_update_page_title (embed);
	embed_update_page_location (embed);

	/* update toolbar/menu status */
	window_update_nav_controls (window);
	window_update_tab_controls (window);

	/* update the statusbar */
	window_update_statusbar (window);

	/* update the zoom control */
	window_update_zoom (window);

	/* update the css menu */
	embed_init_css_menu (embed);

	/* update the spinner animation state */
	if (embed->load_started) spinner_start (window);
	else spinner_stop (window);

	/* set focus to the embed when clicking on a tab */
	/* FIXME I test for embed->wrapper to not grab on
	   new windows/tabs, it's obviously hacky */
	if (embed->wrapper && window->visible_embeds > 1)
	{
		embed_grab_focus (embed);
	}

	/* we assume the user requested this focus by default */
	embed->focus_type = FOCUS_ON_REQUEST;
}

/**
 * window_notebook_add_remove_page_cb: called when a page is either added
 * to or removed from the main notebook. Simply updates the tab controls
 * on the menus.
 */
void
window_notebook_add_remove_page_cb (GtkContainer *container,
				    GtkWidget *widget, GaleonWindow *window)
{
	return_if_not_window (window);
	window_update_tab_controls (window);
}

/**
 * window_notebook_get_page_at_location: gets the number of the page in
 * notebook that's at the location (x, y)
 */
gint
window_notebook_get_page_at_location (GtkNotebook *notebook, gint x, gint y)
{
	GList *l;
	gint nb_x, nb_y, x_rel, y_rel;
	GtkNotebookPage *page;
	GtkWidget *tab;
	gint i, page_num = 0;

	/* get the relative position of the location, with regards to the
	 * position of the notebook */
	gdk_window_get_origin (GTK_WIDGET (notebook)->window,
			       &nb_x, &nb_y);
	x_rel = x - nb_x;
	y_rel = y - nb_y;

	/* look through the tabs in the notebook to find out which one is
	 * at the location */
	for (l = notebook->children, i = 0; l; l = l->next, i++)
	{
		page = l->data;
		tab = page->tab_label;

		/* make sure that our location is at least at the point
		 * of the first tab */
		if (i == 0)
		{
			if (x_rel < tab->allocation.x)
				x_rel = tab->allocation.x;
			if (y_rel < tab->allocation.y)
				y_rel = tab->allocation.y;
		}

		/* handle top/bottom and left/right tabs
		 * appropriately */
		if (notebook->tab_pos == GTK_POS_TOP ||
		    notebook->tab_pos == GTK_POS_BOTTOM)
		{
			if (tab->allocation.x <= x_rel)
			{
				if (tab->allocation.x +
				    tab->allocation.width <= x_rel)
					page_num = i + 1;
				else page_num = i;
			}
		}
		else
		{
			if (tab->allocation.y <= y_rel)
			{
				if (tab->allocation.y +
				    tab->allocation.height <= y_rel)
					page_num = i + 1;
				else page_num = i;
			}
		}
	}

	return page_num;
}

/**
 * window_notebook_initiate_grab: grabs the cursor, initiating a tab drag
 */
void
window_notebook_initiate_grab (GaleonWindow *window, GtkWidget *widget)
{
	/* we're in a drag now */
	window->in_drag = TRUE;

	/* disconnect the leave signal */
	if (window->drag_leave_signal)
	{
		gtk_signal_disconnect (GTK_OBJECT (widget),
				       window->drag_leave_signal);
		window->drag_leave_signal = 0;
	}

	/* get a new cursor, if necessary */
	if (!drag_cursor) drag_cursor = gdk_cursor_new (GDK_FLEUR);

	/* grab the pointer */
	gtk_grab_add (window->notebook);
	gdk_pointer_grab (window->notebook->window, FALSE,
		GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
		NULL, drag_cursor, GDK_CURRENT_TIME);
}

/**
 * window_notebook_button_press_cb: attaches the notebook's motion signal
 * to the motion callback
 */
gboolean
window_notebook_button_press_cb (GtkWidget *widget, GdkEventButton *e,
				 GaleonWindow *window)
{
	GtkNotebook *notebook;
	GList *l;
	GtkNotebookPage *page;
	GtkWidget *tab;
	gint nb_x, nb_y, x_rel, y_rel;

	if ((e->button != 1 && e->button != 2) || e->type != GDK_BUTTON_PRESS)
		return FALSE;

	return_val_if_not_window (window, FALSE);

	/* middleclick clones the current tab */
	if (e->button == 2)
	{
		gint tabbed_mode, shift_modifier;
		GaleonEmbed *embed;

		embed = window->active_embed;
		tabbed_mode = eel_gconf_get_boolean (CONF_TABS_TABBED) ? 1 : 0;
		shift_modifier = (e->state & GDK_SHIFT_MASK) ? 0 : 1;
		embed_create_from_url (embed, embed->site_location, FALSE,
				       (tabbed_mode ^ shift_modifier));
		return TRUE;
	}

	if (window->in_drag)
	{
		g_print ("warning: already in the middle of a drag at "
			 "button press");
		return FALSE;
	}

	notebook = GTK_NOTEBOOK (window->notebook);

	/* get the relative position of the press event, with regards to
	 * the position of the notebook */
	gdk_window_get_origin (GTK_WIDGET (notebook)->window,
			       &nb_x, &nb_y);
	x_rel = e->x_root - nb_x;
	y_rel = e->y_root - nb_y;

	/* look through the tabs in the destination notebook to find out
	 * which one the event occurred over */
	for (l = notebook->children; l; l = l->next)
	{
		page = l->data;
		tab = page->tab_label;

		if (GTK_WIDGET_VISIBLE (tab))
		{
			/* save the borders of the tab */
			if (tab->allocation.x <= x_rel &&
			    tab->allocation.y <= y_rel)
			{
				window->drag_min_x = tab->allocation.x + nb_x;
				window->drag_max_x = tab->allocation.x +
						     tab->allocation.width +
						     nb_x;
				window->drag_min_y = tab->allocation.y + nb_y;
				window->drag_max_y = tab->allocation.y +
						     tab->allocation.height +
						     nb_y;
			}
			else break;
		}
	}

	/* make sure the click occurred in the tab, and not elsewhere (i
	 * hate you, GtkNotebook) */
	if (e->x_root < window->drag_min_x ||
	    e->x_root >= window->drag_max_x ||
	    e->y_root < window->drag_min_y ||
	    e->y_root >= window->drag_max_y)
	{
		return FALSE;
	}

	window->in_predrag = TRUE;

	/* connect the notebook motion signal */
	window->drag_motion_signal = gtk_signal_connect (
		GTK_OBJECT (widget), "motion_notify_event",
		GTK_SIGNAL_FUNC (window_notebook_motion_cb), window);
	window->drag_leave_signal = gtk_signal_connect (
		GTK_OBJECT (widget), "leave_notify_event",
		GTK_SIGNAL_FUNC (window_notebook_leave_cb), window);

	return TRUE;
}

/**
 * window_notebook_motion_cb: handles motion in the notebook
 */
gboolean
window_notebook_motion_cb (GtkWidget *widget, GdkEventMotion *e,
			   GaleonWindow *window)
{
	return_val_if_not_window (window, FALSE);

	/* if we're still in the pre-drag, make sure the user has moved the
	 * mouse far enough for the drag to be initiated */
	if (window->in_predrag)
	{
		if (e->x_root < window->drag_min_x ||
		    e->x_root >= window->drag_max_x ||
		    e->y_root < window->drag_min_y ||
		    e->y_root >= window->drag_max_y)
		{
			window->in_predrag = FALSE;
			window_notebook_initiate_grab (window, widget);
		}
	}
	/* otherwise, if we're in the drag, draw the arrows */
	else if (window->in_drag)
	{
		GaleonWindow *dest_window;
		GtkNotebook *dest_notebook;
		GList *l;
		gint nb_x, nb_y, page_num, i, last_vis_tab_loc = -1;
		gint arrow1_x, arrow1_y, arrow2_x, arrow2_y;
		GtkNotebookPage *page;
		GtkWidget *tab, *last_vis_tab = NULL;
		gboolean horiz_tabs = FALSE, tab_found = FALSE;

		/* get the galeon window the cursor is over */
		dest_window = window_get_window_at_location (e->x_root,
							     e->y_root);

		/* if we're not over a galeon window, hide the hints and
		 * return */
		if (!dest_window)
		{
			dnd_hints_hide_all ();
			return TRUE;
		}

		/* get the destination notebook */
		return_val_if_not_window (dest_window, FALSE);
		dest_notebook = GTK_NOTEBOOK (dest_window->notebook);

		/* get the location of the notebook */
		gdk_window_get_origin (GTK_WIDGET (dest_notebook)->window,
				       &nb_x, &nb_y);

		/* make the arrows appear in the upper-left corner of the
		 * notebook, by default */
		arrow1_x = arrow2_x = nb_x;
		arrow1_y = arrow2_y = nb_y;

		/* get the destination page num */
		page_num = window_notebook_get_page_at_location (
				dest_notebook, e->x_root, e->y_root);

		/* find the direction of the tabs */
		if (dest_notebook->tab_pos == GTK_POS_TOP ||
		    dest_notebook->tab_pos == GTK_POS_BOTTOM)
			horiz_tabs = TRUE;

		/* look through the tabs so we can find out where to put
		 * the arrows */
		for (l = dest_notebook->children, i = 0; l; l = l->next, i++)
		{
			page = l->data;
			tab = page->tab_label;

			/* if this is the correct tab, then record the
			 * positions for the arrows */
			if (i == page_num)
			{
				if (horiz_tabs)
				{
					arrow1_x = arrow2_x = nb_x +
						tab->allocation.x +
						tab->allocation.width / 2;
					arrow1_y = nb_y +
						tab->allocation.y;
					arrow2_y = nb_y +
						tab->allocation.y +
						tab->allocation.height;
				}
				else
				{
					arrow1_x = nb_x +
						tab->allocation.x;
					arrow2_x = nb_x +
						tab->allocation.x +
						tab->allocation.width;
					arrow1_y = arrow2_y = nb_y +
						tab->allocation.y +
						tab->allocation.height / 2;
				}

				tab_found = TRUE;
				break;
			}
			/* otherwise, keep track of the right-most tab that
			 * we see */
			else
			{
				if (horiz_tabs &&
				    tab->allocation.x > last_vis_tab_loc)
				{
					last_vis_tab = tab;
					last_vis_tab_loc = tab->allocation.x;

				}
				else if (!horiz_tabs &&
				         tab->allocation.y > last_vis_tab_loc)
				{
					last_vis_tab = tab;
					last_vis_tab_loc = tab->allocation.y;
				}
			}
		}

		/* if we didn't find the tab, then we'll just place the
		 * arrows to the right/bottom of the last visible tab */
		if (!tab_found && last_vis_tab)
		{
			if (horiz_tabs)
			{
				arrow1_x = arrow2_x = nb_x +
					last_vis_tab->allocation.x +
					last_vis_tab->allocation.width;
				arrow1_y = nb_y +
					last_vis_tab->allocation.y;
				arrow2_y = nb_y +
					last_vis_tab->allocation.y +
					last_vis_tab->allocation.height;
			}
			else
			{
				arrow1_x = nb_x +
					last_vis_tab->allocation.x;
				arrow2_x = nb_x +
					last_vis_tab->allocation.x +
					last_vis_tab->allocation.width;
				arrow1_y = arrow2_y = nb_y +
					last_vis_tab->allocation.y +
					last_vis_tab->allocation.height;
			}
		}

		if (horiz_tabs)
		{
			dnd_hints_show (HINT_ARROW_DOWN, arrow1_x, arrow1_y);
			dnd_hints_show (HINT_ARROW_UP, arrow2_x, arrow2_y);
		}
		else
		{
			dnd_hints_show (HINT_ARROW_RIGHT, arrow1_x, arrow1_y);
			dnd_hints_show (HINT_ARROW_LEFT, arrow2_x, arrow2_y);
		}
	}

	return TRUE;
}

/**
 * window_notebook_leave_cb: handles leave notify events in the notebook
 */
gboolean
window_notebook_leave_cb (GtkWidget *widget, GdkEventCrossing *e,
			  GaleonWindow *window)
{
	if (window->in_drag)
		return FALSE;

	return_val_if_not_window (window, FALSE);

	/* make sure the user has moved the mouse far enough for the drag to
	 * be initiated (this is necessary here, since we get leave notify
	 * events when the mouse moves over a closebutton too) */
	if (e->x_root < window->drag_min_x ||
	    e->x_root >= window->drag_max_x ||
	    e->y_root < window->drag_min_y ||
	    e->y_root >= window->drag_max_y)
	{
		window->in_predrag = FALSE;
		window_notebook_initiate_grab (window, widget);
	}

	return TRUE;
}

/**
 * window_notebook_button_release_cb: handles the end of drags
 */
gboolean
window_notebook_button_release_cb (GtkWidget *widget, GdkEventButton *e,
				   GaleonWindow *window)
{
	GaleonWindow *dest_window;
	GtkNotebook *dest_notebook;
	GaleonEmbed *embed;
	gint dest_page_num;

	/* don't check to make sure that the event's window matches the
	 * widget's, because we may be getting an event passed on from the
	 * closebutton */
	if (e->button != 1 || e->type != GDK_BUTTON_RELEASE)
		return FALSE;

	/* ungrab the pointer if it's grabbed */
	if (gdk_pointer_is_grabbed ())
	{
		gdk_pointer_ungrab (GDK_CURRENT_TIME);
		gtk_grab_remove (widget);
	}

	/* make sure the window is valid */
	return_val_if_not_window (window, FALSE);
	
	/* return if we're not in a predrag or a drag */
	if (!window->in_predrag && !window->in_drag)
		return TRUE;

	/* disconnect the motion signal */
	if (window->drag_motion_signal)
	{
		gtk_signal_disconnect (GTK_OBJECT (widget),
				       window->drag_motion_signal);
		window->drag_motion_signal = 0;
	}

	/* if we're in a pre-drag, we'll also need to disconnect the leave
	 * signal */
	if (window->in_predrag)
	{
		window->in_predrag = FALSE;

		if (window->drag_leave_signal)
		{
			gtk_signal_disconnect (GTK_OBJECT (widget),
					       window->drag_leave_signal);
			window->drag_leave_signal = 0;
		}
	}

	/* if we're not in a drag... */
	if (!window->in_drag)
	{
		/* we're done */
		return TRUE;
	}

	/* no longer in a drag */
	window->in_drag = FALSE;

	/* hide the dnd hints */
	dnd_hints_hide_all ();

	/* get the embed */
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);

	/* get the destination window */
	dest_window = window_get_window_at_location (e->x_root, e->y_root);

	/* if they dropped outside of a galeon window, move to a 
	 * new window */
	if (!dest_window)
	{
		/* only move if the embed isn't all by itself in the window */
		if (g_list_length (window->embed_list) != 1)
			embed_move_to_new_window (embed);

		return TRUE;
	}
			
	/* get the destination notebook */
	return_val_if_not_window (dest_window, FALSE);
	dest_notebook = GTK_NOTEBOOK (dest_window->notebook);

	/* get the destination page num */
	dest_page_num = window_notebook_get_page_at_location (dest_notebook,
		e->x_root, e->y_root);

	/* move the embed */
	embed_move_to_existing_window (embed, dest_window, dest_page_num);

	return TRUE;
}

gboolean window_key_press_event (GtkWidget *widget,
				 GdkEventKey *event,
				 GaleonWindow *window)
{
	int page;

	if ((event->state & GDK_Shift_L) || (event->state & GDK_Shift_R))
		return TRUE;

	if ((event->state & GDK_Alt_L) || (event->state & GDK_Alt_R))
	{
		page = event->keyval - GDK_0 -1;

		if (page == -1) page = 9;

		if (page>=-1 && page<=9)
		{
			gtk_notebook_set_page (GTK_NOTEBOOK (window->notebook),
					       page == -1 ? -1 : page);
			return FALSE;
		}
	}

	return TRUE;
}

void
window_move_tab_to_window_menu_cb (GtkMenuItem *menuitem, 
				   GaleonWindow *window)
{
	GtkMenu *menu;
	GList *child_list;
	GList *li;
	if (menuitem->submenu == NULL) 
	{
		menu = GTK_MENU (gtk_menu_new ());
		gtk_menu_item_set_submenu (menuitem, GTK_WIDGET (menu));
	}
	else
	{
		menu = GTK_MENU (menuitem->submenu);
	}

	/* remove old menu items */
	child_list = gtk_container_children (GTK_CONTAINER (menu));
	for (li = child_list; li != NULL; li = li->next)
	{
		gtk_widget_destroy (GTK_WIDGET (li->data));
	}
	g_list_free (child_list);

	/* create menu items */
	for (li = all_windows; li != NULL; li = li->next)
	{
		GaleonWindow *w;
		GaleonEmbed *e;
		gchar *title;
		GtkWidget *item;
		w = li->data;
		if ((w == NULL) || (w->magic != GALEON_WINDOW_MAGIC))
			continue;
		e = w->active_embed;
		if ((e == NULL) || (e->magic != GALEON_EMBED_MAGIC))
			continue;
		title = e->site_title;
		if (title == NULL)
			continue;
		item = gtk_menu_item_new_with_label (title);
		gtk_object_set_user_data (GTK_OBJECT (item), w);
		gtk_widget_show (item);
		gtk_menu_append (menu, item);
		gtk_signal_connect (GTK_OBJECT (item), "activate", 
				    window_move_tab_to_window_menu_item_cb,
				    window);
		if (w == window)
			gtk_widget_set_sensitive (item, FALSE);
	}
	gtk_menu_reposition (menu);
}

static void
window_move_tab_to_window_menu_item_cb (GtkMenuItem *mi, 
					GaleonWindow *window)
{
	GaleonEmbed *embed;
	GaleonWindow *dest_window;
	gint dest_page;

	/* get the embed */
	return_if_not_window (window);
	embed = window->active_embed;
	return_if_not_embed (embed);

	/* get the destination window and page */
	dest_window = gtk_object_get_user_data (GTK_OBJECT (mi));
	return_if_not_window (dest_window);
	dest_page = gtk_notebook_get_current_page (
				GTK_NOTEBOOK (dest_window->notebook));

	/* move the embed */
	embed_move_to_existing_window (embed, dest_window, dest_page);
}

gboolean
window_find_entry_activate_cb (GtkWidget *widget, GaleonWindow *window)
{
	gchar *text;
	GaleonEmbed *embed;

	return_val_if_not_window (window, FALSE);
	embed = window->active_embed;
	return_val_if_not_embed (embed, FALSE);
	
	/* get text and search */
	text = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);
	find_next (embed, text);
	g_free (text);

	return TRUE;
}

/**
 * window_toolbar_button_enter_event_cb: Called when the mouse enters the
 * button; show prelight icon.
 */
void
window_toolbar_button_enter_event_cb (GtkWidget *widget, ToolbarItem *item)
{
	GtkWidget *pixmap = gtk_object_get_data (GTK_OBJECT (widget), "pixmap");
	const PixmapData *data;
	gchar *filename, *tmp;
	gchar *themedir;
	
	if (!GTK_IS_PIXMAP (pixmap)) return;
	
	themedir = get_theme_dir ();

	filename = g_strconcat (item->theme_icon, "-prelight.png", NULL);
	tmp = g_concat_dir_and_file (themedir, filename);
	if (!g_file_exists (tmp))
	{
		g_free (filename);
		filename = g_strconcat (item->theme_icon, ".png", NULL);
	}
	g_free (tmp);
	g_free (themedir);

	data = get_theme_pixmap (filename, TRUE);
	g_free (filename);

	gtk_pixmap_set (GTK_PIXMAP (pixmap), data->pixmap, data->mask);
}

/**
 * window_toolbar_button_leave_event_cb: Called when the mouse leaves the
 * button; put original icon back
 */
void
window_toolbar_button_leave_event_cb (GtkWidget *widget, ToolbarItem *item)
{
	GtkWidget *pixmap = gtk_object_get_data (GTK_OBJECT (widget), "pixmap");
	const PixmapData *data;
	gchar *filename;

	if (!GTK_IS_PIXMAP (pixmap)) return;

	filename = g_strconcat (item->theme_icon, ".png", NULL);
	data = get_theme_pixmap (filename, FALSE);
	g_free (filename);

	gtk_pixmap_set (GTK_PIXMAP (pixmap), data->pixmap, data->mask);
}
