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

/*
 *  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-tabtree.h"

#include <glib/gi18n.h>
#include "gobject-utils.h"
#include "gtk-utils.h"
#include "kz-tab-label.h"
#include "kz-actions-tab.h"

#define NORMAL_COLOR  NULL
#define LOADING_COLOR "red"


enum {
	PROP_0,
	PROP_SIDEBAR,
	PROP_TREE
};


enum
{
	COLUMN_TERMINATOR = -1,
	COLUMN_ICON,
	COLUMN_TITLE,
	COLUMN_TITLE_COLOR,
	COLUMN_EMBEDER,
	N_COLUMNS
};


static GtkWidget *kz_tab_list_new (KzSidebar *sidebar);
static GtkWidget *kz_tab_tree_new (KzSidebar *sidebar);


/* Object class methods */
static void     kz_tab_tree_class_init   (KzTabTreeClass *klass);
static void     kz_tab_tree_init         (KzTabTree      *tabtree);
static GObject *kz_tab_tree_constructor  (GType           type,
					  guint           n_props,
					  GObjectConstructParam *props);
static void     kz_tab_tree_dispose      (GObject        *obj);
static void     kz_tab_tree_set_property (GObject        *object,
					  guint           prop_id,
					  const GValue   *value,
					  GParamSpec     *pspec);
static void     kz_tab_tree_get_property (GObject        *object,
					  guint           prop_id,
					  GValue         *value,
					  GParamSpec     *pspec);

/* KzTabTree private methods */
static void         build_tab_tree         (KzTabTree    *tabtree,
					    GNode        *parent);
static void         build_tab_list         (KzTabTree    *tabtree);
static GtkTreeIter *find_node              (GtkTreeStore *store,
					    KzEmbed      *kzembed);
static void         remove_all_embed_signals
					   (KzTabTree    *tabtree);

/* signal handlers for TreeView */
static void     cb_cursor_changed           (GtkTreeView     *tree_view,
					     KzTabTree       *tabtree);
static gboolean cb_tree_view_button_release (GtkWidget       *widget,
					     GdkEventButton  *event,
					     KzTabTree       *tabtree);
static gboolean cb_scroll_event             (GtkWidget       *widget,
					     GdkEventScroll  *event,
					     KzTabTree       *tabtree);

/* signal handlers for Notebook on KzWindow */
static void     cb_switch_page    (GtkNotebook     *notebook,
				   GtkNotebookPage *page,
				   guint            page_num,
				   KzTabTree       *tabtree);

/* signal handlers for KzWindow */
static void     cb_append_tab     (KzWindow        *kz,
				   GtkWidget       *widget,
				   GtkWidget       *parent,
				   KzTabTree       *tabtree);
static void     cb_remove_tab     (KzWindow        *kz,
				   GtkWidget       *widget,
				   KzTabTree       *tabtree);
static void     cb_reorder_tab    (KzWindow        *kz,
				   GtkWidget       *widget,
				   gint             pos,
				   KzTabTree       *tabtree);

/* signal handlers for embed */
static void     cb_net_start      (KzEmbed         *embed,
				   KzTabTree       *tabtree);
static void     cb_net_stop       (KzEmbed         *embed,
				   KzTabTree       *tabtree);
static void     cb_title_changed  (KzEmbed         *embed,
				   KzTabTree       *tabtree);


static KzSidebarEntry kz_sidebar_tab_tree[] = 
{
	{
		priority_hint: 0,
		label:         N_("Tab List"),
		icon:          NULL,
		create:        kz_tab_list_new,
	},
	{
		priority_hint: 0,
		label:         N_("Tab Tree"),
		icon:          NULL,
		create:        kz_tab_tree_new,
	},
};


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


#if 0
static GtkTargetEntry dnd_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 guint n_dnd_types = G_N_ELEMENTS (dnd_types);
#endif


static GtkVBoxClass *parent_class = NULL;


KzSidebarEntry *
kz_tab_tree_get_entry (gint idx)
{
	switch (idx)
	{
	case 0:
		return &kz_sidebar_tab_tree[0];
	case 1:
		return &kz_sidebar_tab_tree[1];
	default:
		return NULL;
	}

	return NULL;
}


KZ_OBJECT_GET_TYPE(kz_tab_tree, "KzTabTree", KzTabTree,
		   kz_tab_tree_class_init, kz_tab_tree_init,
		   GTK_TYPE_VBOX)


static void
kz_tab_tree_class_init (KzTabTreeClass *klass)
{
	GObjectClass *gobject_class;

	parent_class = g_type_class_peek_parent(klass);
	gobject_class = (GObjectClass *) klass;

	gobject_class->constructor  = kz_tab_tree_constructor;
	gobject_class->dispose      = kz_tab_tree_dispose;
	gobject_class->set_property = kz_tab_tree_set_property;
	gobject_class->get_property = kz_tab_tree_get_property;

	g_object_class_install_property
		(gobject_class,
		 PROP_SIDEBAR,
		 g_param_spec_object ("sidebar",
				      _("Sidebar"),
				      _("The parent sidebar"),
				      KZ_TYPE_SIDEBAR,
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property
		(gobject_class,
		 PROP_TREE,
		 g_param_spec_boolean ("tree",
				       _("Tree"),
				       _("Whether use Tree view or List view"),
				       FALSE,
				       G_PARAM_READWRITE |
				       G_PARAM_CONSTRUCT_ONLY));
}


static void
kz_tab_tree_init (KzTabTree *tabtree)
{
	GtkTreeStore *store;
	GtkWidget *widget, *scrwin;
	GtkTreeView *tree_view;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;

	scrwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin),
					    GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(tabtree), scrwin, TRUE, TRUE, 0);
	gtk_widget_show(scrwin);

	store = gtk_tree_store_new(N_COLUMNS,
				   GDK_TYPE_PIXBUF,
				   G_TYPE_STRING,
				   G_TYPE_STRING,
				   G_TYPE_POINTER);

	widget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	tree_view = GTK_TREE_VIEW(widget);
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_view), TRUE);
	gtk_container_add(GTK_CONTAINER(scrwin), GTK_WIDGET(tree_view));
#if 0
	gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(tree_view),
					       GDK_BUTTON1_MASK
					       | GDK_BUTTON2_MASK
					       | GDK_BUTTON3_MASK,
					       dnd_types,
					       n_dnd_types,
					       GDK_ACTION_ASK  | GDK_ACTION_COPY
					       | GDK_ACTION_MOVE | GDK_ACTION_LINK);
	gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW (tree_view),
					     dnd_types,
					     n_dnd_types,
					     GDK_ACTION_ASK  | GDK_ACTION_COPY
					     | GDK_ACTION_MOVE | GDK_ACTION_LINK);
#endif
	gtk_widget_show(GTK_WIDGET(tree_view));

	column = gtk_tree_view_column_new();

	cell = gtk_cell_renderer_pixbuf_new();
	gtk_tree_view_column_pack_start(column, cell, FALSE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "pixbuf", COLUMN_ICON);

	cell = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(column, cell, TRUE);
	gtk_tree_view_column_set_title(column, _("Title"));
	gtk_tree_view_column_add_attribute(column, cell,
					   "text", COLUMN_TITLE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "foreground", COLUMN_TITLE_COLOR);
	gtk_tree_view_append_column(tree_view, column);

	g_signal_connect(G_OBJECT(tree_view), "cursor-changed",
			 G_CALLBACK(cb_cursor_changed), tabtree);
	g_signal_connect(G_OBJECT(tree_view), "button-release-event",
			 G_CALLBACK(cb_tree_view_button_release), tabtree);
	g_signal_connect(G_OBJECT(tree_view), "scroll-event",
			 G_CALLBACK(cb_scroll_event), tabtree);

	tabtree->sidebar   = NULL;
	tabtree->tree_view = GTK_TREE_VIEW(tree_view);
	tabtree->store     = store;
	tabtree->tree      = FALSE;
	tabtree->switching = FALSE;
}


static GObject*
kz_tab_tree_constructor (GType                  type,
			 guint                  n_props,
			 GObjectConstructParam *props)
{
	KzTabTree *tabtree;

	GObject *object;
	GObjectClass *klass = G_OBJECT_CLASS(parent_class);

	object = klass->constructor(type, n_props, props);

	tabtree = KZ_TAB_TREE(object);

	if (tabtree->tree)
	{
		GNode *tree = kz_window_get_tree(tabtree->sidebar->kz);
		if (tree && g_node_first_child(tree))
			build_tab_tree(tabtree, tree);
	}
	else
	{
		build_tab_list(tabtree);
	}

	gtk_tree_view_expand_all(tabtree->tree_view);

	g_signal_connect(G_OBJECT(tabtree->sidebar->kz->notebook), "switch-page",
			 G_CALLBACK(cb_switch_page), tabtree);
	g_signal_connect(G_OBJECT(tabtree->sidebar->kz), "append-tab",
			 G_CALLBACK(cb_append_tab), tabtree);
	g_signal_connect(G_OBJECT(tabtree->sidebar->kz), "remove-tab",
			 G_CALLBACK(cb_remove_tab), tabtree);
	g_signal_connect(G_OBJECT(tabtree->sidebar->kz), "reorder-tab",
			 G_CALLBACK(cb_reorder_tab), tabtree);

	return object;
}


static void
kz_tab_tree_dispose (GObject *obj)
{
	KzTabTree *tabtree = KZ_TAB_TREE(obj);

	if (tabtree->sidebar)
	{
		KzWindow *kz = tabtree->sidebar->kz;

		if (kz)
		{
			g_signal_handlers_disconnect_by_func
				(G_OBJECT(kz->notebook),
				 G_CALLBACK(cb_switch_page),
				 tabtree);
			g_signal_handlers_disconnect_by_func
				(G_OBJECT(kz),
				 G_CALLBACK(cb_append_tab),
				 tabtree);
			g_signal_handlers_disconnect_by_func
				(G_OBJECT(kz),
				 G_CALLBACK(cb_remove_tab),
				 tabtree);
			g_signal_handlers_disconnect_by_func
				(G_OBJECT(kz),
				 G_CALLBACK(cb_reorder_tab),
				 tabtree);
		}

		g_object_unref(tabtree->sidebar);
		tabtree->sidebar = NULL;
	}

	if (tabtree->store)
	{
		remove_all_embed_signals(tabtree);
		g_object_unref(tabtree->store);
		tabtree->store = NULL;
	}
	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose(obj);
}


static void
kz_tab_tree_set_property (GObject         *object,
			  guint            prop_id,
			  const GValue    *value,
			  GParamSpec      *pspec)
{
	KzTabTree *tabtree = KZ_TAB_TREE(object);
  
	switch (prop_id)
	{
	case PROP_SIDEBAR:
		tabtree->sidebar = g_object_ref(g_value_get_object(value));
		break;
	case PROP_TREE:
		tabtree->tree = g_value_get_boolean(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_tab_tree_get_property (GObject         *object,
			  guint            prop_id,
			  GValue          *value,
			  GParamSpec      *pspec)
{
	KzTabTree *tabtree = KZ_TAB_TREE(object);

	switch (prop_id)
	{
	case PROP_SIDEBAR:
		g_value_set_object(value, tabtree->sidebar);
		break;
	case PROP_TREE:
		g_value_set_boolean(value, tabtree->tree);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static GtkWidget *
kz_tab_list_new (KzSidebar *sidebar)
{
	KzTabTree *tabtree;

	tabtree = g_object_new(KZ_TYPE_TAB_TREE,
			       "sidebar", sidebar,
			       "tree",    FALSE,
			       NULL);

	return GTK_WIDGET(tabtree);
}


static GtkWidget *
kz_tab_tree_new (KzSidebar *sidebar)
{
	KzTabTree *tabtree;

	tabtree = g_object_new(KZ_TYPE_TAB_TREE,
			       "sidebar", sidebar,
			       "tree",    TRUE,
			       NULL);

	return GTK_WIDGET(tabtree);
}



/*****************************************************************************
 *                                                                           *
 *                       KzTabTree private methods                           *
 *                                                                           *
 *****************************************************************************/
static void
build_tab_tree (KzTabTree *tabtree, GNode *parent)
{
	GtkTreeIter iter, *parent_iter = NULL;
	GNode *node;

	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));
	g_return_if_fail(parent);

	if (parent->data)
		parent_iter = find_node(tabtree->store, parent->data);

	node = g_node_first_child(parent);
	for (; node; node = g_node_next_sibling(node))
	{
		KzEmbed *kzembed = node->data;
		GNode *child = g_node_first_child(node);

		if (KZ_IS_EMBED(kzembed))
		{
			gchar *title = kz_embed_ensure_title(kzembed);
			const gchar *uri   = kz_embed_get_location(kzembed);
			GdkPixbuf *favicon = kz_favicon_get_pixbuf(tabtree->sidebar->kz->kzfav,
								   uri,
								   GTK_ICON_SIZE_MENU);
			const gchar *color;
			color = kz_embed_is_loading(kzembed)
				? LOADING_COLOR : NORMAL_COLOR;

			gtk_tree_store_append(tabtree->store,
					      &iter, parent_iter);
			gtk_tree_store_set(tabtree->store, &iter,
					   COLUMN_ICON,        favicon,
					   COLUMN_TITLE,       title,
					   COLUMN_TITLE_COLOR, color,
					   COLUMN_EMBEDER,     kzembed,
					   COLUMN_TERMINATOR);
			g_signal_connect(G_OBJECT(kzembed), "kz-title",
					 G_CALLBACK(cb_title_changed),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "kz-net-start",
					 G_CALLBACK(cb_net_start),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "kz-net-stop",
					 G_CALLBACK(cb_net_stop),
					 tabtree);

			g_free(title);
			if (favicon)
				g_object_unref(favicon);
		}
		else
		{
			g_warning("KzTabTree: Invalid tree item!");
		}

		if (child)
			build_tab_tree(tabtree, node);
	}

	if (parent_iter)
		gtk_tree_iter_free(parent_iter);
}


static void
build_tab_list (KzTabTree *tabtree)
{
	KzWindow *kz;
	GtkTreeIter iter;
	gint i, num;

	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (!tabtree->sidebar) return;

	kz = tabtree->sidebar->kz;
	if (!kz) return;

	num = gtk_notebook_get_n_pages(GTK_NOTEBOOK(kz->notebook));

	for (i = 0; i < num; i++)
	{
		KzEmbed *kzembed = KZ_EMBED(KZ_WINDOW_NTH_PAGE(kz, i));

		if (KZ_IS_EMBED(kzembed))
		{
			gchar *title = kz_embed_ensure_title(kzembed);
			const gchar *color;
			const gchar *uri   = kz_embed_get_location(kzembed);
			GdkPixbuf *favicon = kz_favicon_get_pixbuf(tabtree->sidebar->kz->kzfav,
								   uri,
								   GTK_ICON_SIZE_MENU);
			color = kz_embed_is_loading(kzembed)
				? LOADING_COLOR : NORMAL_COLOR;

			gtk_tree_store_append(tabtree->store, &iter, NULL);
			gtk_tree_store_set(tabtree->store, &iter,
					   COLUMN_ICON,        favicon,
					   COLUMN_TITLE,       title,
					   COLUMN_TITLE_COLOR, color,
					   COLUMN_EMBEDER,     kzembed,
					   COLUMN_TERMINATOR);
			g_signal_connect(G_OBJECT(kzembed), "kz-title",
					 G_CALLBACK(cb_title_changed),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "kz-net-start",
					 G_CALLBACK(cb_net_start),
					 tabtree);
			g_signal_connect(G_OBJECT(kzembed), "kz-net-stop",
					 G_CALLBACK(cb_net_stop),
					 tabtree);

			g_free(title);
			if (favicon)
				g_object_unref(favicon);
		}
		else
		{
			g_warning("KzTabTree: Invalid tree item!");
		}
	}
}


typedef struct _FindNode
{
	KzEmbed  *kzembed;
	GtkTreeIter *iter;
} FindNode;


static gboolean
find_node_func (GtkTreeModel *model,
		GtkTreePath *path, GtkTreeIter *iter,
		FindNode *data)
{
	KzEmbed *kzembed = NULL;

	if (!iter) return FALSE;

	gtk_tree_model_get(model, iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (kzembed && data->kzembed && kzembed == data->kzembed)
	{
		data->iter = gtk_tree_iter_copy(iter);
		return TRUE;
	}

	return FALSE;
}


static GtkTreeIter *
find_node (GtkTreeStore *store, KzEmbed *kzembed)
{
	FindNode data;
	data.iter = NULL;
	data.kzembed = kzembed;
	gtk_tree_model_foreach(GTK_TREE_MODEL(store),
			       (GtkTreeModelForeachFunc) find_node_func,
			       &data);
	return data.iter;
}


static gboolean
remove_embed_signal_func (GtkTreeModel *model,
			  GtkTreePath *path, GtkTreeIter *iter,
			  gpointer data)
{
	KzEmbed *kzembed = NULL;
	KzTabTree *tabtree;

	g_return_val_if_fail(KZ_IS_TAB_TREE(data), FALSE);
	tabtree = KZ_TAB_TREE(data);

	g_return_val_if_fail(iter, FALSE);

	gtk_tree_model_get(model, iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	g_return_val_if_fail(kzembed, FALSE);

	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_title_changed),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_net_start),
					     tabtree);
	g_signal_handlers_disconnect_by_func(G_OBJECT(kzembed),
					     G_CALLBACK(cb_net_stop),
					     tabtree);

	return FALSE;
}


static void
remove_all_embed_signals (KzTabTree *tabtree)
{
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));
	g_return_if_fail(GTK_IS_TREE_MODEL(tabtree->store));

	gtk_tree_model_foreach(GTK_TREE_MODEL(tabtree->store),
			       remove_embed_signal_func,
			       tabtree);
}



/*****************************************************************************
 *                                                                           *
 *                               Callbacks                                   *
 *                                                                           *
 *****************************************************************************/
static void
cb_cursor_changed(GtkTreeView *tree_view, KzTabTree *tabtree)
{
	GtkTreePath *treepath;
	GtkTreeIter iter;
	KzWindow *kz;
	KzEmbed *kzembed = NULL;
	gint num;

	g_return_if_fail(GTK_IS_TREE_VIEW(tree_view));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (tabtree->switching) return;

	kz = tabtree->sidebar->kz;

	gtk_tree_view_get_cursor(tabtree->tree_view, &treepath, NULL);
	if (!treepath) return;

	gtk_tree_model_get_iter(GTK_TREE_MODEL(tabtree->store),
				&iter, treepath);
	gtk_tree_path_free(treepath);
	gtk_tree_model_get(GTK_TREE_MODEL(tabtree->store), &iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (!kzembed) return;

	num = gtk_notebook_page_num(GTK_NOTEBOOK(kz->notebook),
				    GTK_WIDGET(kzembed));
	g_return_if_fail(num >= 0);

	tabtree->switching = TRUE;
	gtk_notebook_set_current_page(GTK_NOTEBOOK(kz->notebook), num);
	tabtree->switching = FALSE;
}


static gboolean
cb_tree_view_button_release (GtkWidget *widget, GdkEventButton *event,
			     KzTabTree *tabtree)
{
	GtkTreePath *treepath;
	GtkTreeIter iter;
	KzWindow *kz;
	KzEmbed *kzembed = NULL;
	GtkWidget *label;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_TAB_TREE(tabtree), FALSE);

	kz = tabtree->sidebar->kz;

	gtk_tree_view_get_cursor(tabtree->tree_view, &treepath, NULL);
	if (!treepath) return FALSE;

	gtk_tree_model_get_iter(GTK_TREE_MODEL(tabtree->store),
				&iter, treepath);
	gtk_tree_path_free(treepath);
	gtk_tree_model_get(GTK_TREE_MODEL(tabtree->store), &iter,
			   COLUMN_EMBEDER, &kzembed,
			   COLUMN_TERMINATOR);
	if (!kzembed) return FALSE;

	switch (event->button) {
	case 3:
		label = kz_window_get_tab_label(kz, GTK_WIDGET(kzembed));
		g_return_val_if_fail(KZ_IS_TAB_LABEL(label), FALSE);

		kz_actions_tab_popup_menu_modal(KZ_TAB_LABEL(label),
						event->button, event->time);
		return TRUE;
	default:
		break;
	}

	return FALSE;
}


static gboolean
cb_scroll_event (GtkWidget *widget, GdkEventScroll *event, KzTabTree *tabtree)
{
	gboolean retval = FALSE;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_TAB_TREE(tabtree), FALSE);

	switch (event->direction) {
	case GDK_SCROLL_UP:
		g_signal_emit_by_name(GTK_TREE_VIEW(widget), "move-cursor",
				      GTK_MOVEMENT_DISPLAY_LINES, -1, &retval);
		break;
	case GDK_SCROLL_DOWN:
		g_signal_emit_by_name(GTK_TREE_VIEW(widget), "move-cursor",
				      GTK_MOVEMENT_DISPLAY_LINES, 1, &retval);
		break;
	case GDK_SCROLL_LEFT:
	case GDK_SCROLL_RIGHT:
		break;
	default:
		g_warning ("Invalid scroll direction!");
		break;
	}

	return retval;
}


static void
cb_switch_page (GtkNotebook *notebook, GtkNotebookPage *page,
		guint page_num, KzTabTree *tabtree)
{
	GtkWidget *widget;
	GtkTreeIter *iter;
	GtkTreePath *treepath;

	g_return_if_fail(GTK_IS_NOTEBOOK(notebook));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (tabtree->switching) return;

	widget = gtk_notebook_get_nth_page(notebook, page_num);
	iter = find_node(tabtree->store, KZ_EMBED(widget));
	/* g_return_if_fail(iter); */
	if (!iter) return;

	treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(tabtree->store),
					   iter);
	g_return_if_fail(treepath);

	tabtree->switching = TRUE;
	gtk_tree_view_set_cursor(tabtree->tree_view, treepath, NULL, FALSE);
	tabtree->switching = FALSE;

	gtk_tree_path_free(treepath);
	gtk_tree_iter_free(iter);
}


static void
cb_append_tab (KzWindow *kz, GtkWidget *widget, GtkWidget *parent,
	       KzTabTree *tabtree)
{
	GNode *tree;

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

	tree = kz_window_get_tree(tabtree->sidebar->kz);

	/* FIXME!! It's a quick hack */
	remove_all_embed_signals(tabtree);
	gtk_tree_store_clear(tabtree->store);

	if (tabtree->tree)
	{
		if (tree && g_node_first_child(tree))
			build_tab_tree(tabtree, tree);
		gtk_tree_view_expand_all(tabtree->tree_view);
	}
	else
	{
		build_tab_list(tabtree);
	}
}


static void
cb_remove_tab (KzWindow *kz, GtkWidget *widget,
	       KzTabTree *tabtree)
{
	GNode *tree;

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

	tree = kz_window_get_tree(tabtree->sidebar->kz);

	/* FIXME!! It's a quick hack */
	remove_all_embed_signals(tabtree);
	gtk_tree_store_clear(tabtree->store);

	if (tabtree->tree)
	{
		if (tree && g_node_first_child(tree))
			build_tab_tree(tabtree, tree);
		gtk_tree_view_expand_all(tabtree->tree_view);
	}
	else
	{
		build_tab_list(tabtree);
	}
}


static void
cb_reorder_tab (KzWindow *kz,
		GtkWidget *widget, gint pos,
		KzTabTree *tabtree)
{
	g_return_if_fail(KZ_IS_WINDOW(kz));
	g_return_if_fail(GTK_IS_WIDGET(widget));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	if (!tabtree->tree)
	{
		gtk_tree_store_clear(tabtree->store);
		build_tab_list(tabtree);
	}
}



/*****************************************************************************
 *                                                                           *
 *                         Callbacks for Embed                               *
 *                                                                           *
 *****************************************************************************/
static void
cb_net_start (KzEmbed *embed, KzTabTree *tabtree)
{
	GtkTreeIter *iter;
	gchar *title;

	g_return_if_fail(KZ_IS_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_embed_ensure_title(KZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_EMBED(embed));
	if (iter)
	{
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE,       title,
				   COLUMN_TITLE_COLOR, LOADING_COLOR,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}


static void
cb_net_stop (KzEmbed *embed, KzTabTree *tabtree)
{
	GtkTreeIter *iter;
	gchar *title;

	g_return_if_fail(KZ_IS_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_embed_ensure_title(KZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_EMBED(embed));
	if (iter)
	{
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE,       title,
				   COLUMN_TITLE_COLOR, NORMAL_COLOR,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}


static void
cb_title_changed (KzEmbed *embed, KzTabTree *tabtree)
{
	gchar *title;
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_EMBED(embed));
	g_return_if_fail(KZ_IS_TAB_TREE(tabtree));

	title = kz_embed_ensure_title(KZ_EMBED(embed));

	iter = find_node (tabtree->store, KZ_EMBED(embed));
	if (iter)
	{
		const gchar *color;
		color = kz_embed_is_loading(KZ_EMBED(embed))
			? LOADING_COLOR : NORMAL_COLOR;
		gtk_tree_store_set(tabtree->store, iter,
				   COLUMN_TITLE, title,
				   COLUMN_TITLE_COLOR, color,
				   COLUMN_TERMINATOR);
		gtk_tree_iter_free(iter);
	}

	g_free(title);
}
