/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2002 Hiroyuki Ikezoe
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  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 "kz-window.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gtkmozembed.h>

#include "egg-toggle-action.h"
#include "egg-history-action.h"
#include "egg-toolbar.h"
#include "egg-marshalers.h"
#include "gobject-utils.h"
#include "gtk-utils.h"
#include "kz-actions.h"
#include "kz-actions-popup.h"
#include "kz-bookmarkbar.h"
#include "kz-mozembed.h"
#include "kz-tablabel.h"
#include "kz-home.h"
#include "google.h"
#include "kazehakase.h"
#include "intl.h"
#include "utils.h"
#include "mozilla.h"
#include "kz-sidebar.h"

enum {
	APPEND_TAB_SIGNAL,
	REMOVE_TAB_SIGNAL,
	LAST_SIGNAL
};

struct _KzWindowPriv
{
	KzHome     *kzhome;

	const char *status_msg;
	char       *temp_msg;

	/* for find keyword */
	gboolean    did_find;

	KzEmbedEventMouse *event;

	/* for popup & gesture */
	KzGesture  *gesture;
	gint start_x, start_y;
	gboolean is_gesture;

	/* sidebar */
	gboolean sidebar_was_shown;

	/* tab tree */
	GNode *tab_tree;
};

enum {
	STATUS_LINK_MESSAGE,
	STATUS_GESTURE
};

enum {
	TARGET_KAZEHAKASE_TAB,
	TARGET_NETSCAPE_URL,
	TARGET_TEXT_URI_LIST,
	TARGET_TEXT_PLAIN,
	TARGET_STRING
};

static GtkTargetEntry url_drag_types [] =
{
        { "_KAZEHAKASE_TAB", 0, TARGET_KAZEHAKASE_TAB},
        { "_NETSCAPE_URL",   0, TARGET_NETSCAPE_URL},
	{ "text/uri-list",   0, TARGET_TEXT_URI_LIST},
	{ "text/plain",      0, TARGET_TEXT_PLAIN},
	{ "STRING",          0, TARGET_STRING}
};

static struct {
	const gchar *pos;
	const gchar *action;
} tabpositems[] = {
	{"top",    "TabPosTop"},
	{"bottom", "TabPosBottom"},
	{"left",   "TabPosLeft"},
	{"right",  "TabPosRight"},
};


static void     kz_window_class_init     (KzWindowClass *klass);
static void     kz_window_init           (KzWindow *kz);
static gboolean kz_window_delete_event   (GtkWidget *widget,
					  GdkEventAny *event);
static void     kz_window_destroy        (GtkObject *object);
static void     kz_window_finalize       (GObject *object);

static void     kz_window_store_state           (KzWindow *kz);
static void     kz_window_restore_state         (KzWindow *kz);
static void     kz_window_menu_merge_set_widget (KzWindow *kz);
static void     kz_window_gesture_update        (KzWindow *kz);
static void     kz_window_set_moz_callbacks     (KzWindow *kz,
					         KzMozEmbed *kzembed,
						 gboolean all);
static void     kz_window_unset_moz_callbacks   (KzWindow *kz,
					         KzMozEmbed *kzembed,
						 gboolean all);

/* callbacks */
static void     cb_menu_merge_add_widget (EggMenuMerge *merge,
					  GtkWidget *widget,
					  GtkBox *box);
static void     cb_notebook_switch_page  (GtkNotebook *notebook,
					  GtkNotebookPage *page,
					  guint page_num, KzWindow *kz);
static void     cb_find_keyword          (GtkWidget *widget, KzWindow *kz);
static void     cb_gesture_stack_motion  (KzGesture *gesture,
					  KzGestureMotion motion,
					  KzWindow *kz);
static gboolean cb_notebook_scroll_event (GtkWidget *widget,
					  GdkEventScroll *event,
					  KzWindow *kz);
static void     cb_sidebar_map           (GtkWidget *widget,
					  EggToggleAction *action);
static void     cb_sidebar_unmap         (GtkWidget *widget,
					  EggToggleAction *action);


/* callbacks for mozilla */
static void     title_changed_cb         (GtkMozEmbed *embed, KzWindow *kz);
static void     location_changed_cb      (GtkMozEmbed *embed, KzWindow *kz);
static void     link_message_cb          (GtkMozEmbed *embed, KzWindow *kz);
#if 0
static void     visibility_cb            (GtkMozEmbed *embed,
					  gboolean visibility,
					  KzWindow *kz);
#endif
static void     load_started_cb          (GtkMozEmbed *embed, KzWindow *kz);
static void     load_finished_cb         (GtkMozEmbed *embed, KzWindow *kz);
static void     moz_new_window_cb        (GtkMozEmbed *embed,
					  GtkMozEmbed **newEmbed,
					  guint chromemask,
					  KzWindow *kz);
static void     close_tab_cb             (GtkObject *obj, KzWindow *kz);
static gint     dom_key_down_cb          (GtkMozEmbed *embed, gpointer event,
					  KzWindow *kz);
static gint     dom_key_up_cb            (GtkMozEmbed *embed, gpointer event,
					  KzWindow *kz);
static gint     dom_mouse_click_cb       (GtkMozEmbed *embed, gpointer event,
					  KzWindow *kz);
static gint     dom_mouse_dbl_click_cb   (GtkMozEmbed *embed, gpointer event,
					  KzWindow *kz);
static gint     dom_mouse_down_cb        (GtkMozEmbed *embed,
					  gpointer event,
					  KzWindow *kz);
static gint     dom_mouse_up_cb          (GtkMozEmbed *embed,
					  gpointer event,
					  KzWindow *kz);

/*
 * mozilla doesn't accept these signals, so we connect these funtions to
 * KzWindow instead of KzMozEmbed.
 */
static gboolean motion_notify_event_cb  (GtkWidget *widget,
					 GdkEventMotion *event,
					 KzMozEmbed *kzembed);
static gboolean button_release_event_cb (GtkWidget *widget,
					 GdkEventButton *event,
					 KzMozEmbed *kzembed);

/* notebook received dropped url */
static void drag_data_recieved_cb (GtkWidget *widget,
				   GdkDragContext *drag_context,
				   gint x, gint y,
				   GtkSelectionData *data,
				   guint info,
				   guint time,
				   KzWindow *kz);


static GtkWindowClass *parent_class = NULL;
static gint kz_window_signals[LAST_SIGNAL] = {0};
static GList *window_list = NULL;


KZ_OBJECT_GET_TYPE(kz_window, "KzWindow", KzWindow,
		   kz_window_class_init, kz_window_init,
		   GTK_TYPE_WINDOW)


static void
kz_window_append_tab (KzWindow *kz, GtkWidget *widget, GtkWidget *parent)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));
}


static void
kz_window_remove_tab (KzWindow *kz, GtkWidget *widget)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));
}


static void
kz_window_class_init (KzWindowClass *klass)
{
	GObjectClass *gobject_class;
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;

	parent_class = g_type_class_peek_parent (klass);

	gobject_class = (GObjectClass *)   klass;
	object_class  = (GtkObjectClass *) klass;
	widget_class  = (GtkWidgetClass *) klass;

	kz_window_signals[APPEND_TAB_SIGNAL]
		= g_signal_new ("append-tab",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzWindowClass, append_tab),
				NULL, NULL,
				_egg_marshal_VOID__OBJECT_OBJECT,
				G_TYPE_NONE, 2,
				GTK_TYPE_WIDGET, GTK_TYPE_WIDGET);

	kz_window_signals[REMOVE_TAB_SIGNAL]
		= g_signal_new ("remove-tab",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzWindowClass, remove_tab),
				NULL, NULL,
				g_cclosure_marshal_VOID__OBJECT,
				G_TYPE_NONE, 1,
				GTK_TYPE_WIDGET);

	/* GObjectClass */
	gobject_class->finalize = kz_window_finalize;

	/* GtkObject signals */
	object_class->destroy = kz_window_destroy;

	/* GtkWidget signals */
	widget_class->delete_event = kz_window_delete_event;

	/* KzWindow signals */
	klass->append_tab = kz_window_append_tab;
	klass->remove_tab = kz_window_remove_tab;
}


#include "kz-actions-tab.h"
static void
kz_window_init (KzWindow *kz)
{
	GtkWidget *menu_box;
	GtkSettings *setting;
	GError *err = NULL;

	g_return_if_fail(KZ_IS_WINDOW(kz));
	
	g_object_set(G_OBJECT(kz), "allow-shrink", TRUE, NULL);

	kz->top_vbox           = gtk_vbox_new(FALSE, 0);

        kz->url_entry          = NULL;
	kz->progress_area_hbox = NULL;
        kz->progressbar        = NULL;
	kz->status_align       = gtk_alignment_new(0, 0, 1, 1);
	kz->statusbar          = gtk_statusbar_new();
	kz->notebook           = gtk_notebook_new();
	kz->find_area_hbox     = gtk_hbox_new(FALSE, 0);
	kz->find_area          = gtk_entry_new();
	kz->kztoolbar          = GTK_WIDGET(kz_bookmark_bar_new(kz));

	kz->accel_group        = gtk_accel_group_new();
	kz->action_group       = kz_actions_create_group(kz, kz->accel_group);
	kz->popup_action_group = kz_actions_popup_create_group(kz, kz->accel_group);
	kz->menu_merge         = egg_menu_merge_new();
	kz->gesture_items      = NULL;

	kz->priv               = g_new0(KzWindowPriv, 1);
	kz->priv->kzhome       = kz_home_new(kz);
	kz->priv->status_msg   = NULL;
	kz->priv->temp_msg     = NULL;

	kz->priv->did_find     = FALSE;

	kz->priv->event        = NULL;

	kz->priv->gesture      = kz_gesture_new();
	kz->priv->start_x      = 0;
	kz->priv->start_y      = 0;
	kz->priv->is_gesture   = FALSE;

	kz->priv->sidebar_was_shown = FALSE;

	kz->priv->tab_tree     = NULL;

	/* gesture definition */
	kz_window_gesture_update(kz);
	kz_gesture_set_items(kz->priv->gesture, kz->gesture_items);
	g_signal_connect(G_OBJECT(kz->priv->gesture), "stack_motion",
			 G_CALLBACK(cb_gesture_stack_motion), kz);

	/* top level vbox */
	gtk_container_add(GTK_CONTAINER(kz),
			  kz->top_vbox);
	gtk_widget_show(kz->top_vbox);

	/* menu & toolbar */
	menu_box = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start(GTK_BOX(kz->top_vbox), menu_box,
			   FALSE, FALSE, 0);   
	gtk_widget_show(menu_box);

	egg_menu_merge_set_accel_group(kz->menu_merge, kz->accel_group);
	egg_menu_merge_insert_action_group(kz->menu_merge, kz->action_group, 0);
	egg_menu_merge_insert_action_group(kz->menu_merge, kz->popup_action_group, 0);
	g_signal_connect(kz->menu_merge, "add_widget",
			 G_CALLBACK(cb_menu_merge_add_widget), menu_box);
	egg_menu_merge_add_ui_from_file(kz->menu_merge,
					SYSCONFDIR"/kz-ui-menu.xml",
					&err);
	if (err)
	{
		g_warning("%s", err->message);
		g_error_free(err);
	}
        egg_menu_merge_ensure_update(kz->menu_merge);

	/* FIXME */
	kz_window_menu_merge_set_widget(kz);

	setting = gtk_settings_get_default();
	if (setting)
	{
		/* FIXME */
		gtk_settings_set_long_property (setting,
						"gtk-toolbar-style",
						GTK_TOOLBAR_ICONS,
						"");
	}

	gtk_window_add_accel_group(GTK_WINDOW(kz), kz->accel_group);

	/* KzWindow tool bar */
	gtk_box_pack_start(GTK_BOX(kz->top_vbox), 
			   kz->kztoolbar, FALSE, FALSE, 0);    
	gtk_widget_show(kz->kztoolbar);

	/* paned widget to separate sidebar and main contents */
	kz->pane = gtk_hpaned_new();
	gtk_container_add(GTK_CONTAINER(kz->top_vbox), kz->pane);
	gtk_widget_show(kz->pane);

	/* main notebook widget */
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(kz->notebook), TRUE);
	gtk_notebook_set_scrollable(GTK_NOTEBOOK(kz->notebook), TRUE);
	g_signal_connect(G_OBJECT(kz->notebook), "switch-page", 
			 G_CALLBACK(cb_notebook_switch_page), kz);
	g_signal_connect(G_OBJECT(kz->notebook), "scroll-event",
			 G_CALLBACK(cb_notebook_scroll_event), kz);

	gtk_drag_dest_set(GTK_WIDGET(kz->notebook),
			  GTK_DEST_DEFAULT_ALL,
                          url_drag_types, G_N_ELEMENTS (url_drag_types),
			  GDK_ACTION_MOVE);
	g_signal_connect(G_OBJECT(kz->notebook), "drag-data-received",
			 G_CALLBACK(drag_data_recieved_cb), kz);

	gtk_paned_add2(GTK_PANED(kz->pane), kz->notebook);
	gtk_widget_show(kz->notebook);

	/* sidebar */
	kz->sidebar = kz_sidebar_new(kz);
	gtk_widget_set_size_request(kz->sidebar, 150, -1);
	gtk_paned_add1(GTK_PANED(kz->pane), kz->sidebar);
	/* gtk_widget_show(kz->sidebar); */
	{
		EggAction *action;
		action = egg_action_group_get_action(kz->action_group,
						     "ShowHideSidebar");
		g_signal_connect(G_OBJECT(kz->sidebar), "map",
				 G_CALLBACK(cb_sidebar_map), action);
		g_signal_connect(G_OBJECT(kz->sidebar), "unmap",
				 G_CALLBACK(cb_sidebar_unmap), action);
	}

	/* mozembed tab */
	kz_window_open_new_tab(kz, NULL);

	/* search area */
	gtk_box_pack_start(GTK_BOX(kz->top_vbox), kz->find_area_hbox,
			   FALSE, FALSE, 0);   
	gtk_widget_show(kz->find_area_hbox);

	/* find area */
	gtk_box_pack_start(GTK_BOX(kz->find_area_hbox), kz->find_area,
			   FALSE, FALSE, 0); 
	g_signal_connect(G_OBJECT(GTK_ENTRY(kz->find_area)), "activate",
			 G_CALLBACK(cb_find_keyword), kz);
	gtk_widget_show(kz->find_area);

	/* create our status area and the alignment object that will keep it */
	/* from expanding */
	gtk_widget_set_size_request(kz->status_align, 1, -1);
	gtk_widget_show(kz->status_align);
	/* create the status bar */
	gtk_container_add(GTK_CONTAINER(kz->status_align), kz->statusbar);
	gtk_box_pack_start(GTK_BOX(kz->find_area_hbox), kz->status_align,
			   TRUE, TRUE, 0);
	gtk_widget_show(kz->statusbar);

	KZ_WINDOW_SET_SENSITIVE(kz, "Back",    FALSE);
	KZ_WINDOW_SET_SENSITIVE(kz, "Stop",    FALSE);
	KZ_WINDOW_SET_SENSITIVE(kz, "Forward", FALSE);
	KZ_WINDOW_SET_SENSITIVE(kz, "Reload",  FALSE);
	KZ_WINDOW_SET_VISIBLE  (kz, "Stop",    FALSE);
	KZ_WINDOW_SET_VISIBLE  (kz, "Reload",  TRUE);

	window_list = g_list_append(window_list, kz);

	kz_window_restore_state(kz);

	gtk_widget_show(GTK_WIDGET(kz));
}


GList *
kz_window_get_window_list(void)
{
	return window_list;
}


GtkWidget *
kz_window_new(const gchar *url)
{
	KzWindow *kz = KZ_WINDOW(g_object_new(KZ_TYPE_WINDOW,
					      "type", GTK_WINDOW_TOPLEVEL,
					      NULL));
	GtkWidget *widget = KZ_WINDOW_CURRENT_PAGE(kz);

	if (GTK_IS_MOZ_EMBED(widget))
		gtk_moz_embed_load_url(GTK_MOZ_EMBED(widget), url);

	return GTK_WIDGET (kz);
}


GtkWidget *
kz_window_get_from_tab (GtkWidget *tab_widget)
{
	GList *node;

	for (node = window_list; node; node = g_list_next (node))
	{
		KzWindow *kz = node->data;
		GtkWidget *label;

		if (!KZ_IS_WINDOW(kz)) continue;

		label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(kz->notebook),
						   tab_widget);
		if (label)
			return GTK_WIDGET(kz);
	}

	return NULL;
}


GtkWidget *
kz_window_open_new_tab (KzWindow *kz, const gchar *url)
{
	return kz_window_open_new_tab_with_parent (kz, url, NULL);
}


GtkWidget *
kz_window_open_new_tab_with_parent (KzWindow *kz, const gchar *url,
				    GtkWidget *parent)
{
	KzMozEmbed *kzembed;
	KzTabLabel *kztab;
	GNode *node, *parent_node;

	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);

	kzembed = KZ_MOZ_EMBED(kz_moz_embed_new(url));
	kztab = KZ_TAB_LABEL(kz_tab_label_new(kz, kzembed));

	gtk_notebook_append_page(GTK_NOTEBOOK(kz->notebook),
				 GTK_WIDGET(kzembed),
				 GTK_WIDGET (kztab));

	gtk_widget_show(GTK_WIDGET(kzembed));
	gtk_widget_show(GTK_WIDGET(kztab));
	kz_window_set_moz_callbacks(kz, kzembed, TRUE);

	/* root node */
	if (!kz->priv->tab_tree)
		kz->priv->tab_tree = g_node_new(NULL);

	/* insret node */
	node = g_node_new(kzembed);
	parent_node = g_node_find(kz->priv->tab_tree,
				  G_IN_ORDER, G_TRAVERSE_ALL, parent);
	if (parent_node)
		g_node_append(parent_node, node);
	else
		g_node_append(kz->priv->tab_tree, node);

	g_signal_emit(G_OBJECT(kz), kz_window_signals[APPEND_TAB_SIGNAL],
		      0, kzembed, parent);

	return GTK_WIDGET(kzembed);
}


void
kz_window_close_tab (KzWindow *kz, GtkWidget *widget)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(GTK_IS_WIDGET(widget));

	gtk_widget_destroy(widget);
}


typedef struct _CloseTab
{
	KzWindow  *kz;
	GtkWidget *widget;
} CloseTab;


static gboolean
idle_close_tab (gpointer data)
{
	CloseTab *close_tab = data;
	kz_window_close_tab(close_tab->kz, close_tab->widget);
	g_free(close_tab);

	return FALSE;
}


void
kz_window_close_tab_idle (KzWindow *kz, GtkWidget *widget)
{
	CloseTab *close_tab;

	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(GTK_IS_WIDGET(widget));

	close_tab = g_new(CloseTab, 1);
	close_tab->kz = kz;
	close_tab->widget = widget;

	gtk_idle_add(idle_close_tab, close_tab);
}


/* FIXME!! It's a hacky way. */
void
kz_window_move_tab (KzWindow *kz, GtkWidget *widget)
{
	GtkNotebook *src_notebook;
	GtkWidget *label;
	KzWindow *src_kz;
	KzMozEmbed *kzembed;
	KzTabLabel *new_kztab;
	GNode *node, *child;

	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(KZ_IS_MOZ_EMBED(widget));

	kzembed = KZ_MOZ_EMBED(widget);

	src_kz = KZ_WINDOW(kz_window_get_from_tab(GTK_WIDGET(kzembed)));
	if (!src_kz) return;

	label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(kz->notebook),
					   GTK_WIDGET(kzembed));

	/* the kzembed is already the kz's child */
	if (label) return;

	src_notebook = GTK_NOTEBOOK(src_kz->notebook);
	label = gtk_notebook_get_tab_label(src_notebook,
					   GTK_WIDGET(kzembed));
	g_return_if_fail(label);

	/* create new tab label */
	new_kztab = KZ_TAB_LABEL(kz_tab_label_new(kz, kzembed));

	/* move the page to this window */
	kz_window_unset_moz_callbacks(src_kz, kzembed, TRUE);
	gtk_widget_reparent(GTK_WIDGET(kzembed), kz->notebook);
	gtk_notebook_set_tab_label(GTK_NOTEBOOK(kz->notebook),
				   GTK_WIDGET(kzembed),
				   GTK_WIDGET(new_kztab));
	kz_window_set_moz_callbacks(kz, kzembed, TRUE);
	gtk_widget_destroy(GTK_WIDGET(label));


	/* move tab tree */
	node = g_node_find(src_kz->priv->tab_tree,
			   G_IN_ORDER, G_TRAVERSE_ALL, widget);
	if (node)
	{
		/* move children */
		child = g_node_first_child(node);
		while (child)
		{
			GNode *next = g_node_next_sibling(child);
			g_node_unlink(child);
			g_node_append(src_kz->priv->tab_tree,
				      child);
			child = next;
		}

		/* mode node */
		g_node_unlink(node);
		g_node_append(kz->priv->tab_tree, node);

		g_signal_emit(G_OBJECT(src_kz),
			      kz_window_signals[REMOVE_TAB_SIGNAL],
			      0, kzembed);
		g_signal_emit(G_OBJECT(kz),
			      kz_window_signals[APPEND_TAB_SIGNAL],
			      0, kzembed, NULL);
	}
	else
	{
		g_warning("KzWindow: cannot find tab node!");
	}
}


static gboolean
kz_window_delete_event (GtkWidget *widget, GdkEventAny *event)
{
	KzWindow *kz;
	gint n_pages, i;

	g_return_val_if_fail(KZ_IS_WINDOW(widget), FALSE);

	kz = KZ_WINDOW(widget);

	kz_window_store_state(kz);

	n_pages = gtkutil_notebook_get_n_pages(GTK_NOTEBOOK(kz->notebook));

	for (i = n_pages - 1; i >= 0; i--)
	{
		GtkWidget *widget = GTK_WIDGET(KZ_WINDOW_NTH_PAGE(kz, i));
		gtk_widget_destroy(widget);
	}

	return FALSE;
}


static void
kz_window_destroy (GtkObject *object)
{
	KzWindow *kz = KZ_WINDOW(object);

	g_return_if_fail (KZ_IS_WINDOW(kz));

	window_list = g_list_remove(window_list, kz);

	if (kz->priv->kzhome)
		g_object_unref(kz->priv->kzhome);
	kz->priv->kzhome = NULL;

	if (kz->priv->gesture)
		g_object_unref(kz->priv->gesture);
	kz->priv->gesture = NULL;

	g_free(kz->priv->temp_msg);
        kz->priv->temp_msg = NULL;

	if (kz->priv->event)
		kz_embed_event_free((KzEmbedEvent *) kz->priv->event);
	kz->priv->event = NULL;

	if (kz->menu_merge)
		g_object_unref(kz->menu_merge);
	kz->menu_merge = NULL;

	if (kz->action_group)
		g_object_unref(kz->action_group);
	kz->action_group = NULL;

	if (kz->popup_action_group)
		g_object_unref(kz->popup_action_group);
	kz->popup_action_group = NULL;

	if (kz->gesture_items)
		kz_gesture_items_unref(kz->gesture_items);
	kz->gesture_items = NULL;

	if (GTK_OBJECT_CLASS (parent_class)->destroy)
		GTK_OBJECT_CLASS (parent_class)->destroy(object);

	if (!kz_window_get_window_list())
	{
		static gboolean main_quited = FALSE;
		if (!main_quited)
		{
			gtk_main_quit();
			main_quited = TRUE;
		}
	}
}


KZ_OBJECT_FINALIZE(kz_window, KzWindow)


static void
kz_window_gesture_update (KzWindow *kz)
{
	EggAction *action;
	GList *list, *node;

	g_return_if_fail(KZ_IS_WINDOW(kz));

	if (!kz->gesture_items)
		kz->gesture_items = kz_gesture_items_new();

	list = kz_profile_enum_key(kz_global_profile, "Gesture");

	for (node = list; node; node = g_list_next(node))
	{
		const gchar *action_name = node->data;
		gchar gesture[256] = {0};
		gboolean exist;

		exist = kz_profile_get_value(kz_global_profile,
					     "Gesture", action_name,
					     gesture, sizeof(gesture),
					     KZ_PROFILE_VALUE_TYPE_STRING);
		if (!exist) continue;
		if (!*gesture) continue;

		action = egg_action_group_get_action(kz->action_group,
						     action_name);
		if (!action) continue;

		kz_gesture_items_set_action(kz->gesture_items, action,
					    0, gesture);
	}

	g_list_free(list);
}


static void
cb_window_map (KzWindow *kz, EggAction *action)
{
	egg_action_activate(action);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(kz), G_CALLBACK(cb_window_map), action);
}


static void
kz_window_store_state (KzWindow *kz)
{
	EggAction *action;
	gint i, client_x, client_y, width, height;
	const gchar *label;
	gboolean active, save_session;

	g_return_if_fail(KZ_IS_WINDOW(kz));


	/*
	 * window size
	 */

	gdk_window_get_geometry (GTK_WIDGET(kz)->window,
				 &client_x, &client_y,
				 &width, &height, NULL);
	kz_profile_set_value(kz_global_profile, "MainWindow", "width",
			     &width, sizeof(width), KZ_PROFILE_VALUE_TYPE_INT);
	kz_profile_set_value(kz_global_profile, "MainWindow", "height",
			     &height, sizeof(height), KZ_PROFILE_VALUE_TYPE_INT);


	/*
	 * sidebar
	 */

	/* content */
	label = kz_sidebar_get_current(KZ_SIDEBAR(kz->sidebar));
	if (label && *label)
		kz_profile_set_value(kz_global_profile, "MainWindow", "sidebar",
				     label, strlen(label) + 1,
				     KZ_PROFILE_VALUE_TYPE_STRING);

	/* width */
	if (kz->priv->sidebar_was_shown)
	{
		width = kz->sidebar->allocation.width;
		kz_profile_set_value(kz_global_profile,
				     "MainWindow", "sidebar_width",
				     &width, sizeof(width),
				     KZ_PROFILE_VALUE_TYPE_INT);
	}

	/* visible */
	action = egg_action_group_get_action(kz->action_group,
					     "ShowHideSidebar");
	active = egg_toggle_action_get_active(EGG_TOGGLE_ACTION(action));
	kz_profile_set_value(kz_global_profile, "MainWindow", "show_sidebar",
			     &active, sizeof(active),
			     KZ_PROFILE_VALUE_TYPE_BOOL);


	/*
	 * tab position
	 */

	for (i = 0; i < G_N_ELEMENTS(tabpositems); i++)
	{
		action = egg_action_group_get_action(kz->action_group,
						     tabpositems[i].action);
		active = egg_toggle_action_get_active(EGG_TOGGLE_ACTION(action));
		if (active)
		{
			kz_profile_set_value(kz_global_profile,
					     "MainWindow", "tab_pos",
					     tabpositems[i].pos,
					     strlen(tabpositems[i].pos) + 1,
					     KZ_PROFILE_VALUE_TYPE_STRING);
			break;
		}
	}


	/*
	 * history of location entry
	 */

	action = egg_action_group_get_action(kz->action_group,
					     "LocationEntry");
	if (EGG_IS_HISTORY_ACTION(action))
	{
		const GList *list, *node;
		gint i = 0;

		list = kz_profile_enum_key(kz_global_profile, "LocationEntry");
		for (node = list; node; node = g_list_next(node))
		{
			const gchar *key = node->data;
			if (key && *key && key_seems_sequential(key, "history"))
				kz_profile_delete_key(kz_global_profile,
						      "LocationEntry", "history");
		}

		list = egg_history_action_get_history(EGG_HISTORY_ACTION(action));
		for (node = list; node; node = g_list_next((GList *) node))
		{
			const gchar *uri = node->data;
			gchar key[256];

			if (!uri || !*uri) continue;

			g_snprintf(key, G_N_ELEMENTS(key), "history%d", i);

			kz_profile_set_value(kz_global_profile,
					     "LocationEntry", key,
					     uri, strlen(uri),
					     KZ_PROFILE_VALUE_TYPE_STRING);
			i++;
		}
	}


	/*
	 *  Session
	 */

	kz_profile_get_value(kz_global_profile,
			     "Session", "save_session_on_exit",
			     &save_session, sizeof(save_session),
			     KZ_PROFILE_VALUE_TYPE_BOOL);
	if (save_session && g_list_length(window_list) == 1)
	{
		action = egg_action_group_get_action(kz->action_group,
						     "SaveSession");
		egg_action_activate(action);
	}
}


static void
kz_window_restore_state (KzWindow *kz)
{
	EggAction *action;
	gint width = 640, height = 450, sidebar_width = 150;
	gboolean active = FALSE, restore_session = FALSE;
	gchar *tab_pos, *label;
	gint i;
	GList *list, *node, *history = NULL;

	g_return_if_fail(KZ_IS_WINDOW(kz));


	/*
	 * window size
	 */

	kz_profile_get_value(kz_global_profile, "MainWindow", "width",
			     &width, sizeof(width),
			     KZ_PROFILE_VALUE_TYPE_INT);
	kz_profile_get_value(kz_global_profile, "MainWindow", "height",
			     &height, sizeof(height),
			     KZ_PROFILE_VALUE_TYPE_INT);

	gtk_window_set_default_size(GTK_WINDOW(kz), width, height);


	/*
	 * sidebar
	 */

	/* content */
	label = kz_profile_get_string(kz_global_profile, "MainWindow", "sidebar");
	if (label && *label)
		kz_sidebar_set_current(KZ_SIDEBAR(kz->sidebar), label);
	g_free(label);

	/* width */
	kz_profile_get_value(kz_global_profile, "MainWindow", "sidebar_width",
			     &sidebar_width, sizeof(sidebar_width),
			     KZ_PROFILE_VALUE_TYPE_INT);
	gtk_widget_set_size_request(GTK_WIDGET(kz->sidebar), sidebar_width, -1);

	/* visible */
	kz_profile_get_value(kz_global_profile, "MainWindow", "show_sidebar",
			     &active, sizeof(active),
			     KZ_PROFILE_VALUE_TYPE_BOOL);
	action = egg_action_group_get_action(kz->action_group,
					     "ShowHideSidebar");
	egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action), active);


	/*
	 * tab position
	 */

	tab_pos = kz_profile_get_string(kz_global_profile, "MainWindow", "tab_pos");

	for (i = 0; tab_pos && i < G_N_ELEMENTS(tabpositems); i++)
	{
		if (strcasecmp(tab_pos, tabpositems[i].pos)) continue;

		action = egg_action_group_get_action(kz->action_group,
						     tabpositems[i].action);
		egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action),
					     TRUE);
	}

	g_free(tab_pos);


	/*
	 * history of location entry
	 */

	list = kz_profile_enum_key(kz_global_profile, "LocationEntry");
	for (node = list; node; node = g_list_next(node))
	{
		const gchar *key = node->data;
		gchar *value;

		if (!key_seems_sequential(key, "history")) continue;

		value = kz_profile_get_string(kz_global_profile,
					      "LocationEntry",
					      key);
		if (value && *value)
			history = g_list_append(history, value);

	}

	action = egg_action_group_get_action(kz->action_group, "LocationEntry");
	if (EGG_IS_HISTORY_ACTION(action))
	{
		gint max_history = 32;
		gboolean success;

		success = kz_profile_get_value(kz_global_profile,
					       "LocationEntry", "max_history",
					       &max_history, sizeof(max_history),
					       KZ_PROFILE_VALUE_TYPE_INT);
		if (success)
			egg_history_action_set_max_history
				(EGG_HISTORY_ACTION(action), max_history);

		egg_history_action_set_history(EGG_HISTORY_ACTION(action), history);
	}

	g_list_foreach(history, (GFunc) g_free, NULL);
	g_list_free(history);


	/*
	 *  Session
	 */

	kz_profile_get_value(kz_global_profile,
			     "Session", "restore_session_on_start_up",
			     &restore_session, sizeof(restore_session),
			     KZ_PROFILE_VALUE_TYPE_BOOL);
	if (restore_session && g_list_length(window_list) == 1)
	{
		action = egg_action_group_get_action(kz->action_group,
						     "RestoreSession");
		if (GTK_WIDGET_MAPPED(kz))
			egg_action_activate(action);
		else
			g_signal_connect(G_OBJECT(kz), "map",
					 G_CALLBACK(cb_window_map), action);
	}
}


/* FIXME */
static void
kz_window_menu_merge_set_widget (KzWindow *kz)
{
	GtkWidget *toolbar;
	EggAction *action;
	GSList *node;

	/*
	 *  set Google bar
	 */
	toolbar = egg_menu_merge_get_widget(kz->menu_merge, "/MainToolBar");
	if (EGG_IS_TOOLBAR(toolbar))
	{
		EggToolItem *toolitem = egg_tool_item_new();
		GtkWidget *button = google_new(kz);

		gtk_container_add(GTK_CONTAINER(toolitem), button);
		gtk_widget_show(button);
		gtk_widget_show(GTK_WIDGET(toolitem));
		egg_toolbar_append_tool_item (EGG_TOOLBAR(toolbar),
					      toolitem);
	}

	/*
	 *  set Home action
	 */
	action = egg_action_group_get_action(kz->action_group, "Home");
	for (node = action->proxies; action && node; node = g_slist_next (node))
	{
		GtkWidget *widget = node->data;

		kz_home_set_widget (kz->priv->kzhome, widget);
	}
}


static void
kz_window_set_moz_callbacks(KzWindow *kz, KzMozEmbed *kzembed, gboolean all)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(KZ_IS_MOZ_EMBED(kzembed));

	g_signal_connect(G_OBJECT(kz),
			 "motion_notify_event",
			 G_CALLBACK(motion_notify_event_cb), kzembed);
	g_signal_connect(G_OBJECT(kz),
			 "button_release_event",
			 G_CALLBACK(button_release_event_cb), kzembed);

	if (!all) return;

	/*
	 * key event signal
	 */
	g_signal_connect(G_OBJECT(kzembed),
			 "dom_key_down",
			 G_CALLBACK(dom_key_down_cb), kz);
	g_signal_connect(G_OBJECT(kzembed),
			 "dom_key_up",
			 G_CALLBACK(dom_key_up_cb), kz);

	/*
	 * mouse event signal
	 */
	g_signal_connect(G_OBJECT(kzembed),
			 "dom_mouse_down",
			 G_CALLBACK(dom_mouse_down_cb), kz);
	g_signal_connect(G_OBJECT(kzembed),
			 "dom_mouse_up",
			 G_CALLBACK(dom_mouse_up_cb), kz);

	/*
	 * other callbacks
	 */
	g_signal_connect(G_OBJECT(kzembed), "title",
			 G_CALLBACK(title_changed_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "location",
			 G_CALLBACK(location_changed_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "link_message",
			 G_CALLBACK(link_message_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "net_start",
			 G_CALLBACK(load_started_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "net_stop",
			 G_CALLBACK(load_finished_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "new_window",
			 G_CALLBACK(moz_new_window_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "dom_mouse_click",
			 G_CALLBACK(dom_mouse_click_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "dom_mouse_dbl_click",
			 G_CALLBACK(dom_mouse_dbl_click_cb), kz);
	g_signal_connect(G_OBJECT(kzembed), "destroy",
			 G_CALLBACK(close_tab_cb), kz);
}


static void
kz_window_unset_moz_callbacks(KzWindow *kz, KzMozEmbed *kzembed, gboolean all)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(KZ_IS_MOZ_EMBED(kzembed));

	g_signal_handlers_disconnect_by_func(G_OBJECT(kz),
					     G_CALLBACK(motion_notify_event_cb),
					     kzembed);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kz),
					     G_CALLBACK(button_release_event_cb),
					     kzembed);

	if (!all) return;

	/*
	 * key event signal
	 */
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_key_down_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_key_up_cb),
					     kz);

	/*
	 * mouse event signal
	 */
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_mouse_down_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_mouse_up_cb),
					     kz);

	/*
	 * other callbacks
	 */
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(title_changed_cb), kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(location_changed_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(link_message_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(load_started_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(load_finished_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(moz_new_window_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_mouse_click_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(dom_mouse_dbl_click_cb),
					     kz);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(close_tab_cb),
					     kz);
}


void
kz_window_update_nav_buttons (KzWindow *kz, KzMozEmbed *kzembed)
{
	GtkMozEmbed *embed;
	GtkWidget *focus;
	gboolean can_go_back = FALSE;
	gboolean can_go_forward = FALSE;
	gboolean can_stop = FALSE;
	gboolean can_stop_all = FALSE;
	gint i, num, pos = 0;
	gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;

	g_return_if_fail (KZ_IS_WINDOW(kz));
	/* g_return_if_fail (KZ_IS_MOZ_EMBED (kzembed)); */

	focus = gtk_window_get_focus(GTK_WINDOW(kz));
	num = gtkutil_notebook_get_n_pages(GTK_NOTEBOOK(kz->notebook));

	if (kzembed)
	{
		embed = GTK_MOZ_EMBED(kzembed);

		can_go_back = gtk_moz_embed_can_go_back(embed);
		can_go_forward = gtk_moz_embed_can_go_forward(embed);
		can_stop = kz_moz_embed_is_loading(KZ_MOZ_EMBED(embed));

		pos = gtk_notebook_page_num(GTK_NOTEBOOK(kz->notebook),
					    GTK_WIDGET(embed));
		for (i = 0; i< num; i++)
		{
			can_stop_all
				= kz_moz_embed_is_loading(KZ_MOZ_EMBED(embed));
			if (can_stop_all) break;
		}
	}

	if (num > 0)
	{
		KZ_WINDOW_SET_SENSITIVE(kz, "Back",      can_go_back);
		KZ_WINDOW_SET_SENSITIVE(kz, "Forward",   can_go_forward);
		KZ_WINDOW_SET_VISIBLE  (kz, "Stop",      can_stop);
		KZ_WINDOW_SET_VISIBLE  (kz, "Reload",    !can_stop);
		KZ_WINDOW_SET_SENSITIVE(kz, "Stop",      can_stop);
		KZ_WINDOW_SET_SENSITIVE(kz, "Reload",    !can_stop);
		KZ_WINDOW_SET_SENSITIVE(kz, "StopAll",   can_stop_all);
		KZ_WINDOW_SET_SENSITIVE(kz, "ReloadAll", TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CloseTab",  TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "Search",    TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "ExtractLinks", TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CopyTitle",    TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CopyURL",      TRUE);
		if (pos == 0)
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "LeftTab", FALSE);
		}
		else
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "LeftTab", TRUE);
		}
		if (pos == num - 1)
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "RightTab", FALSE);
		}
		else
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "RightTab", TRUE);
		}
	}
	else
	{
		KZ_WINDOW_SET_SENSITIVE(kz, "Back",      FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "Forward",   FALSE);
		KZ_WINDOW_SET_VISIBLE  (kz, "Stop",      FALSE);
		KZ_WINDOW_SET_VISIBLE  (kz, "Reload",    TRUE);
		KZ_WINDOW_SET_SENSITIVE(kz, "Stop",      FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "Reload",    FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "StopAll",   FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "ReloadAll", FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CloseTab",  FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "LeftTab",   FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "RightTab",  FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "Search",    FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "ExtractLinks", FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CopyTitle",    FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "CopyURL",      FALSE);
	}

	if (num > 1)
	{
		if (pos == 0)
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseLeftAll", FALSE);
		}
		else
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseLeftAll", TRUE);
		}

		if (pos == num - 1)
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseRightAll", FALSE);
		}
		else
		{
			KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseRightAll", TRUE);
		}

		KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseAllWithoutActive", TRUE);
	}
	else
	{
		KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseLeftAll",          FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseRightAll",         FALSE);
		KZ_WINDOW_SET_SENSITIVE(kz, "TabCloseAllWithoutActive", FALSE);
	}

#warning FIXME! /* should track all GtkEditable's event and Mozilla's selection */
	if (kzembed && !kz_moz_embed_selection_is_collapsed(kzembed))
	{
		KZ_WINDOW_SET_SENSITIVE(kz, "OpenSelectedLinks", TRUE);
	}
	else
	{
		KZ_WINDOW_SET_SENSITIVE(kz, "OpenSelectedLinks", FALSE);
	}

	if (GTK_IS_EDITABLE(focus))
	{
		gboolean selected = FALSE;
		selected = gtk_editable_get_selection_bounds
				(GTK_EDITABLE(focus), NULL, NULL);
		if (selected)
		{
			can_cut   = TRUE;
			can_copy  = TRUE;
		}
		can_paste = TRUE;
	}
	else if (KZ_IS_MOZ_EMBED(kzembed))
	{
		can_cut   = kz_moz_embed_can_cut_selection(kzembed);
		can_copy  = kz_moz_embed_can_copy_selection(kzembed);
		can_paste = kz_moz_embed_can_paste(kzembed);
	}

	KZ_WINDOW_SET_SENSITIVE(kz, "Cut",   can_cut);
	KZ_WINDOW_SET_SENSITIVE(kz, "Copy",  can_copy);
	KZ_WINDOW_SET_SENSITIVE(kz, "Paste", can_paste);
}


void
kz_window_set_visibility (KzWindow *kz, gboolean visibility)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));

	if (!visibility)
		gtk_widget_hide(GTK_WIDGET (kz));
	else
		gtk_widget_show(GTK_WIDGET(kz));
}


void
kz_window_load_url (KzWindow *kz, const gchar *url)
{
	GtkWidget *widget = KZ_WINDOW_CURRENT_PAGE(kz);

	g_return_if_fail(KZ_IS_WINDOW(kz));

	if (gtkutil_notebook_get_n_pages(GTK_NOTEBOOK(kz->notebook)) < 1 ||
	    !GTK_IS_MOZ_EMBED(widget))
	{
		kz_window_open_new_tab(kz, url);
	}
	else
	{
		gtk_moz_embed_load_url(GTK_MOZ_EMBED(widget), url);
	}
}


void
kz_window_load_home (KzWindow *kz)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(KZ_IS_HOME(kz->priv->kzhome));

	kz_home_load_first_home(kz->priv->kzhome);
}


const gchar *
kz_window_get_title (KzWindow *kz)
{
	GtkWidget *widget = KZ_WINDOW_CURRENT_PAGE(kz);

	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);

	if (GTK_IS_MOZ_EMBED(widget))
		return kz_moz_embed_get_title(KZ_MOZ_EMBED(widget));
	else
		return NULL;
}


const gchar *
kz_window_get_uri (KzWindow *kz)
{
	GtkWidget *widget = KZ_WINDOW_CURRENT_PAGE(kz);

	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);

	if (GTK_IS_MOZ_EMBED(widget))
		return kz_moz_embed_get_location(KZ_MOZ_EMBED(widget));
	else
		return NULL;
}


GtkWidget *
kz_window_get_tab_label (KzWindow *kz, GtkWidget *widget)
{
	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);
	g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);

	return gtk_notebook_get_tab_label(GTK_NOTEBOOK(kz->notebook),
					  widget);
}


GNode *
kz_window_get_tree (KzWindow *kz)
{
	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);
	return kz->priv->tab_tree;
}


const KzEmbedEventMouse *
kz_window_get_mouse_event_info (KzWindow *kz)
{
	g_return_val_if_fail(KZ_IS_WINDOW(kz), NULL);
	return kz->priv->event;
}



/*****************************************************************************
 *                                                                           *
 *                                Callbacks                                  *
 *                                                                           *
 *****************************************************************************/
static void
cb_menu_merge_add_widget (EggMenuMerge *merge, GtkWidget *widget, GtkBox *box)
{
	gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
	gtk_widget_show (widget);
}


static void
cb_notebook_switch_page (GtkNotebook *notebook, GtkNotebookPage *page,
			 guint page_num, KzWindow *kz)
{
	KzMozEmbed *kzembed = KZ_MOZ_EMBED(KZ_WINDOW_NTH_PAGE(kz, page_num));
	gchar *title;
	const gchar *location;
	EggAction *action;

	title = kz_moz_embed_ensure_title(kzembed);
	location = kz_moz_embed_get_location(kzembed);
	if (title)
		gtk_window_set_title(GTK_WINDOW(kz), title);
	else
		gtk_window_set_title(GTK_WINDOW(kz), _("Kazehakase"));

	g_free(title);
	title = NULL;

	action = egg_action_group_get_action(kz->action_group, "LocationEntry");

	if (EGG_IS_ENTRY_ACTION(action))
	{
		if (location)
			egg_entry_action_set_text(EGG_ENTRY_ACTION(action),
						  location);
		else
			egg_entry_action_set_text(EGG_ENTRY_ACTION(action), "");
	}

	kz_window_update_nav_buttons(kz, kzembed);
}


static void
cb_find_keyword (GtkWidget *widget, KzWindow *kz)
{
	KzMozEmbed *embed = KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz));
	const gchar *text = gtk_entry_get_text(GTK_ENTRY(widget));

	kz->priv->did_find = kz_moz_embed_find(embed, text);
}


static void
cb_gesture_stack_motion (KzGesture *gesture, KzGestureMotion motion,
			 KzWindow *kz)
{
	const gchar *label;
	gchar buf1[256], buf2[256];

	g_return_if_fail(KZ_IS_GESTURE(gesture));
	g_return_if_fail(KZ_IS_WINDOW(kz));

	kz_gesture_create_gesture_string(gesture, buf1, G_N_ELEMENTS(buf1));
	label = kz_gesture_get_matched_label(gesture);
	if (label)
		g_snprintf(buf2, G_N_ELEMENTS(buf2),
			   _("Gesture: %s(Action: %s)"), buf1, label);
	else
		g_snprintf(buf2, G_N_ELEMENTS(buf2),
			   _("Gesture: %s"), buf1);

	gtk_statusbar_push(GTK_STATUSBAR(kz->statusbar),
			   STATUS_GESTURE, buf2);
}


static gboolean
cb_notebook_scroll_event (GtkWidget *widget, GdkEventScroll *event,
			  KzWindow *kz)
{
	GtkNotebook *notebook;

	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);

	notebook = GTK_NOTEBOOK(kz->notebook);

	switch (event->direction) {
	case GDK_SCROLL_UP:
	case GDK_SCROLL_LEFT:
		gtk_notebook_prev_page(notebook);
		return TRUE;
		break;
	case GDK_SCROLL_DOWN:
	case GDK_SCROLL_RIGHT:
		gtk_notebook_next_page(notebook);
		return TRUE;
		break;
	default:
		g_warning ("Invalid scroll direction!");
		break;
	}

	return FALSE;
}


static void
cb_sidebar_map (GtkWidget *widget, EggToggleAction *action)
{
	g_return_if_fail(EGG_IS_TOGGLE_ACTION(action));

	KZ_SIDEBAR(widget)->kz->priv->sidebar_was_shown = TRUE;

	egg_toggle_action_set_active(action, TRUE);
}


static void
cb_sidebar_unmap (GtkWidget *widget, EggToggleAction *action)
{
	g_return_if_fail(EGG_IS_TOGGLE_ACTION(action));

	egg_toggle_action_set_active(action, FALSE);
}



/*****************************************************************************
 *                                                                           *
 *                        Callbacks for Mozilla                              *
 *                                                                           *
 *****************************************************************************/
static void
title_changed_cb (GtkMozEmbed *embed, KzWindow *kz)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));

	if (embed == GTK_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)))
	{
		const gchar *title
			= kz_moz_embed_get_title(KZ_MOZ_EMBED(embed));
		gtk_window_set_title(GTK_WINDOW(kz), (gchar *) title);
	}
}


static void
location_changed_cb (GtkMozEmbed *embed, KzWindow *kz)
{
	const char *newLocation;

	g_return_if_fail (KZ_IS_MOZ_EMBED(embed));
	g_return_if_fail (KZ_IS_WINDOW(kz));

	newLocation = kz_moz_embed_get_location(KZ_MOZ_EMBED(embed));
	if (newLocation)
	{
		EggAction *action
			= egg_action_group_get_action(kz->action_group,
						      "LocationEntry");
		if (embed == GTK_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz))
		    && EGG_IS_ENTRY_ACTION(action))
		{
			egg_entry_action_set_text(EGG_ENTRY_ACTION(action),
						  newLocation);
		}
	}
	else
	{
		g_print("failed to get location!\n");
	}

	if (embed == GTK_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)))
		kz_window_update_nav_buttons(kz, KZ_MOZ_EMBED(embed));
}


static void
link_message_cb (GtkMozEmbed *embed, KzWindow *kz)
{
	gchar *newMessage;

	g_return_if_fail (KZ_IS_MOZ_EMBED(embed));
	g_return_if_fail (KZ_WINDOW(kz));

	newMessage = kz_moz_embed_get_link_message(KZ_MOZ_EMBED(embed));
	if(kz->statusbar)
		gtk_statusbar_push(GTK_STATUSBAR(kz->statusbar),
				   STATUS_LINK_MESSAGE, newMessage);

#if 0
	if (KZ_MOZ_EMBED(embed)->popup_window)
	{
		/* destroy popup window*/
		gtk_widget_destroy(kzembed->popup_window);
		kzembed->popup_window = NULL;
	}
	else if (newMessage != NULL)
	{
		/* fetch image */ 
		/* create popup window */
		kzembed->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
		gtk_window_set_transient_for(GTK_WINDOW(kzembed->popup_window), 
					     GTK_WINDOW(kz->topLevelWindow));
		gtk_widget_show(kzembed->popup_window);
	}
#endif

	g_free(newMessage);
}


#if 0
static void
visibility_cb (GtkMozEmbed *embed, gboolean visibility, KzWindow *kz)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	kz_window_set_visibility(kz, visibility);
}
#endif


static void
load_started_cb (GtkMozEmbed *embed, KzWindow *kz)
{
	if (embed == GTK_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)))
		kz_window_update_nav_buttons(kz, KZ_MOZ_EMBED(embed));
}


static void
load_finished_cb (GtkMozEmbed *embed, KzWindow *kz)
{
	if (embed == GTK_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)))
		kz_window_update_nav_buttons(kz, KZ_MOZ_EMBED(embed));
}


static void 
destroy_browser_cb (GtkMozEmbed *embed, GtkWidget *transient_window)
{
	gtk_widget_destroy(GTK_WIDGET(transient_window));
}


static void
moz_new_window_cb (GtkMozEmbed *embed,
		   GtkMozEmbed **newEmbed, guint chromemask,
		   KzWindow *kz)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));

	if ((chromemask & GTK_MOZ_EMBED_FLAG_OPENASCHROME) != 0)
	{
		/* FIXME! this is ad hoc. */
		GtkWidget *newWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);

		gtk_window_set_transient_for(GTK_WINDOW(newWindow),
					     GTK_WINDOW(kz));
		*newEmbed = GTK_MOZ_EMBED(kz_moz_embed_new(NULL));
		g_signal_connect(G_OBJECT(*newEmbed),"destroy_browser",
				 G_CALLBACK(destroy_browser_cb),newWindow);
		gtk_container_add(GTK_CONTAINER(newWindow),GTK_WIDGET(*newEmbed));
	}
	else
	{
		*newEmbed = GTK_MOZ_EMBED(kz_window_open_new_tab(kz, NULL));
		gtk_widget_show (GTK_WIDGET(*newEmbed));
	}
}


static void
close_tab_cb (GtkObject *obj, KzWindow *kz)
{
	KzMozEmbed *kzembed;
	GNode *node, *child;

	g_return_if_fail(KZ_IS_MOZ_EMBED(obj));
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(kz->priv->tab_tree);

	kzembed = KZ_MOZ_EMBED(obj);

	kz_window_unset_moz_callbacks(kz, kzembed, FALSE);

	node = g_node_find(kz->priv->tab_tree,
			   G_IN_ORDER, G_TRAVERSE_ALL, kzembed);
	/* probably destroy handler is already called */
	if (!node) return;

	/* move children */
	child = g_node_first_child(node);
	while (child)
	{
		GNode *next = g_node_next_sibling(child);
		g_node_unlink(child);
		g_node_append(kz->priv->tab_tree, child);
		child = next;
	}

	g_node_destroy(node);

	g_signal_emit(G_OBJECT(kz), kz_window_signals[REMOVE_TAB_SIGNAL],
		      0, kzembed);
}


static gint
dom_key_down_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{

	g_return_val_if_fail(KZ_IS_MOZ_EMBED(embed), FALSE);
	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);

	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));

	return FALSE;
}


static gint
dom_key_up_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{
	gboolean typeahead = FALSE;

	g_return_val_if_fail(KZ_IS_MOZ_EMBED(embed), FALSE);
	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);

	kz_profile_get_value(kz_global_profile,
			     "Mozilla", "typeahead",
			     &typeahead, sizeof(typeahead),
			     KZ_PROFILE_VALUE_TYPE_BOOL);

	if (!typeahead)
	{
		KzMozEmbed *kzembed;
		gulong key_code;
		glong type;
		KzEmbedEventKey *info;

		kzembed = KZ_MOZ_EMBED(embed);
		
		type = kz_moz_embed_get_key_event_info(kzembed, event, &info);
		key_code = info->key;

		if ((key_code == 191) && !(type & KZ_CONTEXT_INPUT)) /* '/' */
		{
			gtk_widget_grab_focus(kz->find_area);
		}

		kz_embed_event_free((KzEmbedEvent *) info);
	}

	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));

	return FALSE;
}


#warning FIXME
static gint
dom_mouse_click_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{
	gint button;
	guint32 time;
     	glong type;
     	KzEmbedEventMouse *info;

	g_return_val_if_fail(GTK_IS_MOZ_EMBED(embed), FALSE);
	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);

	time = GDK_CURRENT_TIME;
	
     	type = kz_moz_embed_get_mouse_event_info(KZ_MOZ_EMBED(embed), event, &info);
	button = info->button;

	switch (button) {
	case 0:
		if ((info->modifier & KZ_SHIFT_KEY) &&
		    (type & KZ_CONTEXT_LINK) &&
		    info->cinfo.link)
		{
			kz_window_open_new_tab(kz, info->cinfo.link);
			kz_embed_event_free((KzEmbedEvent *) info);
			return TRUE;
		}
		break;
	case 1:
		if ((type & KZ_CONTEXT_LINK) && info->cinfo.link)
		{
			kz_window_open_new_tab_with_parent(kz, info->cinfo.link,
							   GTK_WIDGET(embed));
		}
		break;
	default:
		break;
	}

	kz_embed_event_free((KzEmbedEvent *) info);

	return FALSE;
}


static gint
dom_mouse_dbl_click_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{
#if 0
     	MozillaMouseEvent *info;
	gint button;

	g_return_val_if_fail(GTK_IS_MOZ_EMBED(embed), FALSE);
	g_return_val_if_fail(KZ_WINDOW(kz), FALSE);

     	type = kz_moz_embed_get_mouse_event_info(embed, event, &info);	
	button = info->button;
	kz_moz_embed_event_free(info);
	switch (button) {
	 case 0:
		 gtk_notebook_prev_page(GTK_NOTEBOOK(kz->notebook));
		 break;
	 case 2:	
		 gtk_notebook_next_page(GTK_NOTEBOOK(kz->notebook));
		 break;
	}
#endif

	return FALSE;
}


static gint
dom_mouse_down_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{
	KzMozEmbed *kzembed;
	gint button;
     	glong type;
     	KzEmbedEventMouse *info;

	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);
	g_return_val_if_fail(KZ_IS_MOZ_EMBED(embed), FALSE);

	kzembed = KZ_MOZ_EMBED(embed);

     	type = kz_moz_embed_get_mouse_event_info(kzembed, event, &info);	
	button = info->button;
	if (kz->priv->event)
		kz_embed_event_free((KzEmbedEvent *) kz->priv->event);
	kz->priv->event = info;

	if (button == 2) 
	{
		static GdkCursor *cursor = NULL;
		gint x, y, win_x, win_y, win_x_pos, win_y_pos;

		/* start gesture */

		gtk_widget_get_pointer (GTK_WIDGET(kzembed), &x, &y);

		kz->priv->is_gesture = FALSE;
		kz->priv->start_x = x;
		kz->priv->start_y = y;

		gdk_window_get_root_origin (GTK_WIDGET(kzembed)->window,
					    &win_x, &win_y);
		gdk_window_get_position (GTK_WIDGET(kzembed)->window,
					 &win_x_pos, &win_y_pos);

		gtk_widget_get_pointer (GTK_WIDGET(kzembed),
					&x, &y);
		kz_gesture_start(kz->priv->gesture, 0, x, y);

		if (!cursor) cursor = gdk_cursor_new (GDK_HAND1);
		gdk_pointer_grab (GTK_WIDGET(kz)->window,
				  FALSE,
				  GDK_POINTER_MOTION_MASK |
				  GDK_BUTTON_RELEASE_MASK |
				  GDK_BUTTON_PRESS_MASK,
				  NULL, cursor, gtk_get_current_event_time ());
		gtk_statusbar_push(GTK_STATUSBAR(kz->statusbar),
				   STATUS_GESTURE, "Gesture:");
 	}

	/* kz_embed_event_free((KzEmbedEvent *) info); */

	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));

	return FALSE;
}


static gint
dom_mouse_up_cb (GtkMozEmbed *embed, gpointer event, KzWindow *kz)
{
	g_return_val_if_fail(KZ_IS_WINDOW(kz), FALSE);
	g_return_val_if_fail(KZ_IS_MOZ_EMBED(embed), FALSE);

	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));

	return FALSE;
}


static gboolean
motion_notify_event_cb (GtkWidget *widget,
			GdkEventMotion *event,
			KzMozEmbed *kzembed)
{
	KzWindow *kz;
	gint x, y;

	g_return_val_if_fail(KZ_IS_WINDOW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_MOZ_EMBED(kzembed), FALSE);

	kz = KZ_WINDOW(widget);

	gtk_widget_get_pointer (GTK_WIDGET(kzembed), &x, &y);

	if (abs(x - kz->priv->start_x) > 2 ||
	    abs(y - kz->priv->start_y) > 2)
	{
		kz->priv->is_gesture = TRUE;
	}

	if (kz_gesture_is_started(kz->priv->gesture))
	{
		kz_gesture_update_position(kz->priv->gesture, x, y);
	}

	kz_window_update_nav_buttons
		(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));

	return FALSE;
}


static gboolean
button_release_event_cb (GtkWidget *widget,
			 GdkEventButton *event,
			 KzMozEmbed *kzembed)
{
	KzWindow *kz;

	g_return_val_if_fail(KZ_IS_WINDOW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_MOZ_EMBED(kzembed), FALSE);

	kz = KZ_WINDOW(widget);

	if (kz_gesture_is_started(kz->priv->gesture))
	{
		if (event->button == 3)
			kz_gesture_perform(kz->priv->gesture);
		else
			kz_gesture_cancel(kz->priv->gesture);
		gtk_statusbar_push(GTK_STATUSBAR(KZ_WINDOW(widget)->statusbar),
				   STATUS_GESTURE, "");
	}

        if (gdk_pointer_is_grabbed ())
        {
                gdk_pointer_ungrab (gtk_get_current_event_time ());
        }

	if (!kz->priv->is_gesture && event->button == 3)
	{
		kz_actions_popup_menu_modal (kz, event->button, event->time);
	}

	if (kz->priv->event)
		kz_embed_event_free((KzEmbedEvent *) kz->priv->event);
	kz->priv->event = NULL;

	kz->priv->start_x = 0;
	kz->priv->start_y = 0;
	kz->priv->is_gesture = FALSE;

	return FALSE;
}


static void
drag_data_recieved_cb (GtkWidget *widget,
		       GdkDragContext *drag_context,
		       gint x, gint y,
		       GtkSelectionData *data,
		       guint info,
		       guint time,
		       KzWindow *kz)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));

	switch (info)
	{
	case TARGET_KAZEHAKASE_TAB:
	{
		GtkWidget *src_widget = gtk_drag_get_source_widget(drag_context);
		KzWindow *src_kz;
		KzTabLabel *src_kztab;

		if (!KZ_IS_TAB_LABEL(src_widget)) return;
		src_kztab = KZ_TAB_LABEL(src_widget);

		src_kz = KZ_WINDOW(kz_window_get_from_tab(GTK_WIDGET(src_kztab->kzembed)));
		if (kz == src_kz)
		{
			gint n;
			n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(kz->notebook));
			gtk_notebook_reorder_child(GTK_NOTEBOOK(kz->notebook), 
						   GTK_WIDGET(src_kztab->kzembed),
						   n - 1);
			kz_window_update_nav_buttons
				(kz, KZ_MOZ_EMBED(KZ_WINDOW_CURRENT_PAGE(kz)));
		}
		else
		{
			kz_window_move_tab(kz, GTK_WIDGET(src_kztab->kzembed));
		}

		break;
	}

	case TARGET_NETSCAPE_URL:
	case TARGET_TEXT_URI_LIST:
	case TARGET_TEXT_PLAIN:
	case TARGET_STRING:
	{
		gchar *string, **strings;

		if (data->length < 0) return;
		string = g_alloca (data->length + 1);

		memcpy (string, data->data, data->length);
		string[data->length] = '\0';

		strings = g_strsplit(string, "\n", 2);
		kz_window_open_new_tab(kz, strings[0]);
		g_strfreev(strings);
		break;

	}
	default:
		break;
	}
}
