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

#include "kazehakase.h"
#include "gobject-utils.h"
#include "intl.h"
#include "kz-bookmark.h"
#include "kz-icons.h"
#include "kz-favicon.h"

#define NORMAL_COLOR  NULL
#define LOADING_COLOR "red"


enum {
	PROP_0,
	PROP_SIDEBAR
};


enum
{
	COLUMN_TERMINATOR = -1,
	COLUMN_ICON,
	COLUMN_TITLE,
	COLUMN_TITLE_COLOR,
	COLUMN_TITLE_SCALE,
	COLUMN_TITLE_SCALE_SET,
	COLUMN_URI,
	COLUMN_BOOKMARK,
	COLUMN_BOOKMARK_ITEM,
	N_COLUMNS
};


/* Object class methods */
static void kz_meta_tree_class_init   (KzMETATreeClass *klass);
static void kz_meta_tree_init         (KzMETATree      *metatree);
static void kz_meta_tree_dispose      (GObject         *obj);
static void kz_meta_tree_set_property (GObject         *object,
				       guint            prop_id,
				       const GValue    *value,
				       GParamSpec      *pspec);
static void kz_meta_tree_get_property (GObject         *object,
				       guint            prop_id,
				       GValue          *value,
				       GParamSpec      *pspec);

/* KzMETATree private methods */
static void kz_meta_tree_refresh_all     (KzMETATree *metatree);
static void kz_meta_tree_refresh_meta    (KzMETATree *metatree,
					  KzBookmark *bookmark);
static void kz_meta_tree_insert_bookmark (KzMETATree *metatree,
					  KzBookmark *bookmark);

static GtkTreeIter *find_node_from_meta     (GtkTreeStore *store,
					     KzBookmark *bookmark);
static void         remove_all_meta_signals (KzMETATree    *metatree);

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

/* signal handlers for KzMETA */
static void cb_bookmark_load_start          (KzBookmark *bookmark,
					     KzMETATree *metatree);
static void cb_bookmark_load_completed      (KzBookmark *bookmark,
					     KzMETATree *metatree);

/* signal handler for KzMETAList */
static void cb_bookmark_list_updated      (KzBookmark *bookmarklist,
					   KzMETATree *metatree);
static void cb_bookmark_list_insert_child (KzBookmark *bookmarklist,
					   KzBookmark *child, KzBookmark *sibling,
					   KzMETATree *metatree);
static void cb_bookmark_list_remove_child (KzBookmark *bookmarklist,
					   KzBookmark *child,
					   KzMETATree *metatree);

static void bookmark_connect_signal    (KzMETATree *metatree,
					KzBookmark *bookmark);
static void bookmark_disconnect_signal (KzMETATree *metatree,
					KzBookmark *bookmark);

static KzSidebarEntry kz_sidebar_meta_tree = 
{
	priority_hint: 0,
	label:         N_("Bookmark bar"),
	icon:          NULL,
	create:        kz_meta_tree_new,
};

static GtkVBoxClass *parent_class = NULL;


KzSidebarEntry *
kz_meta_tree_get_entry (gint idx)
{
	if (idx == 0)
		return &kz_sidebar_meta_tree;
	else
		return NULL;
}


KZ_OBJECT_GET_TYPE(kz_meta_tree, "KzMETATree", KzMETATree,
		   kz_meta_tree_class_init, kz_meta_tree_init,
		   GTK_TYPE_VBOX)


static void
kz_meta_tree_class_init (KzMETATreeClass *klass)
{
	GObjectClass *gobject_class;

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

	gobject_class->dispose      = kz_meta_tree_dispose;
	gobject_class->set_property = kz_meta_tree_set_property;
	gobject_class->get_property = kz_meta_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));
}


static void
kz_meta_tree_init (KzMETATree *metatree)
{
	GtkTreeStore *store;
	GtkWidget *widget, *scrwin;
	GtkTreeView *tree_view;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;

	/* create scrolled window and tree */
	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(metatree), 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_DOUBLE,
				   G_TYPE_BOOLEAN,
				   G_TYPE_STRING,
				   KZ_TYPE_BOOKMARK,
				   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));
	gtk_widget_show(GTK_WIDGET(tree_view));

	/* append a column */
	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_add_attribute(column, cell,
					   "text", COLUMN_TITLE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "foreground", COLUMN_TITLE_COLOR);
	gtk_tree_view_column_add_attribute(column, cell,
					   "scale", COLUMN_TITLE_SCALE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "scale-set", COLUMN_TITLE_SCALE_SET);
	gtk_tree_view_column_set_title(column, _("Title"));
	gtk_tree_view_append_column(tree_view, column);

	/* set signal handlers */
	g_signal_connect(G_OBJECT(tree_view), "cursor-changed",
			 G_CALLBACK(cb_cursor_changed), metatree);
	g_signal_connect(G_OBJECT(tree_view), "button-press-event",
			 G_CALLBACK(cb_tree_view_button_press), metatree);
	g_signal_connect(G_OBJECT(tree_view), "button-release-event",
			 G_CALLBACK(cb_tree_view_button_release), metatree);
	g_signal_connect(G_OBJECT(tree_view), "scroll-event",
			 G_CALLBACK(cb_scroll_event), metatree);

	/* initialize struct */
	metatree->sidebar   = NULL;
	metatree->tree_view = GTK_TREE_VIEW(tree_view);
	metatree->store     = store;
	metatree->bookmarklist = kz_bookmark_list_get_instance();

	/* set signal handler for KzBookmarkList */

	g_signal_connect(G_OBJECT(metatree->bookmarklist), "load_completed",
			 G_CALLBACK(cb_bookmark_list_updated), metatree);
	g_signal_connect(G_OBJECT(metatree->bookmarklist), "insert-child",
			 G_CALLBACK(cb_bookmark_list_insert_child), metatree);
	g_signal_connect(G_OBJECT(metatree->bookmarklist), "remove-child",
			 G_CALLBACK(cb_bookmark_list_remove_child), metatree);
	/* set nodes */
	kz_meta_tree_refresh_all(metatree);

	gtk_tree_view_expand_all(metatree->tree_view);
}


static void
kz_meta_tree_dispose (GObject *obj)
{
	KzMETATree *metatree = KZ_META_TREE(obj);

	if (metatree->bookmarklist)
	{
		remove_all_meta_signals(metatree);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(metatree->bookmarklist),
			 G_CALLBACK(cb_bookmark_list_updated), metatree);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(metatree->bookmarklist),
			 G_CALLBACK(cb_bookmark_list_insert_child), metatree);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(metatree->bookmarklist),
			 G_CALLBACK(cb_bookmark_list_remove_child), metatree);
		g_object_unref(metatree->bookmarklist);
		metatree->bookmarklist = NULL;
	}

	if (metatree->sidebar)
		g_object_unref(metatree->sidebar);
	metatree->sidebar = NULL;

	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose(obj);
}


static void
kz_meta_tree_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
{
	KzMETATree *metatree = KZ_META_TREE(object);
  
	switch (prop_id)
	{
	case PROP_SIDEBAR:
		metatree->sidebar = g_object_ref(g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_meta_tree_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
{
	KzMETATree *metatree = KZ_META_TREE(object);

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


GtkWidget *
kz_meta_tree_new (KzSidebar *sidebar)
{
	KzMETATree *metatree;

	metatree = KZ_META_TREE(g_object_new(KZ_TYPE_META_TREE,
					     "sidebar", sidebar,
					     NULL));

	return GTK_WIDGET(metatree);
}


/*****************************************************************************
 *                                                                           *
 *                       KzMETATree private methods                           *
 *                                                                           *
 *****************************************************************************/
static void
kz_meta_tree_refresh_all (KzMETATree *metatree)
{
	GList *children, *node;

	g_return_if_fail (KZ_IS_META_TREE(metatree));

	remove_all_meta_signals(metatree);
	gtk_tree_store_clear(metatree->store);

	children = kz_bookmark_get_children(metatree->bookmarklist);
	for (node = children; node; node = g_list_next(node))
	{
		KzBookmark *bookmark = node->data;
		kz_meta_tree_insert_bookmark(metatree, bookmark);
	}
	g_list_free(children);
}


static void
kz_meta_tree_refresh_meta (KzMETATree *metatree, KzBookmark *bookmark)
{
	GtkTreeIter *parent_iter, iter;
	GtkTreePath *treepath;
	GList *children, *node;
	gboolean expand;

	g_return_if_fail(metatree);
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	parent_iter = find_node_from_meta(metatree->store, bookmark);
	if (!parent_iter) return;

	treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(metatree->store),
					   parent_iter);
	expand = gtk_tree_view_row_expanded(metatree->tree_view, treepath);

	/* clean children */
	while (gtk_tree_model_iter_children(GTK_TREE_MODEL(metatree->store),
					    &iter, parent_iter))
	{
		gtk_tree_store_remove(metatree->store, &iter);
	}

	/* append children */
	children = kz_bookmark_get_children(KZ_BOOKMARK(bookmark));
	for (node = children; node; node = g_list_next(node))
	{
		GdkPixbuf *favicon = NULL;
		KzBookmark *item = node->data;
		/* 
		 * FIXME! Do not use kz-favicon_get_instance,
		 * Use sidebar->kz->kzfav. 
		 */
		KzFavicon *kzfav = kz_favicon_get_instance();
		const gchar *link;

		if (!item) continue;
		
		link = kz_bookmark_get_link(item);
		favicon = kz_favicon_get_pixbuf(kzfav,
						link,
						GTK_ICON_SIZE_MENU);
		g_object_unref(kzfav);
		gtk_tree_store_append(metatree->store, &iter, parent_iter);
		gtk_tree_store_set(metatree->store,    &iter,
				   COLUMN_ICON,        favicon,
				   COLUMN_TITLE,       kz_bookmark_get_title(item),
				   COLUMN_TITLE_COLOR, NORMAL_COLOR,
				   COLUMN_TITLE_SCALE, 1.0,
				   COLUMN_TITLE_SCALE_SET, FALSE,
				   COLUMN_URI,         link,
				   COLUMN_BOOKMARK,      bookmark,
				   COLUMN_BOOKMARK_ITEM, item,
				   COLUMN_TERMINATOR);
		if (favicon)
			g_object_unref(favicon);
	}
	g_list_free(children);

	gtk_tree_view_expand_row(metatree->tree_view, treepath, TRUE);

	gtk_tree_path_free(treepath);
	gtk_tree_iter_free(parent_iter);
}


static void
kz_meta_tree_insert_bookmark(KzMETATree *metatree, KzBookmark *bookmark)
{
	GdkPixbuf *favicon = NULL;
	const gchar *link;
	KzBookmarkState state;
	GtkTreeIter iter;
	const gchar *stock, *title, *location, *color;
	/* 
	 * FIXME! Do not use kz-favicon_get_instance,
	 * Use sidebar->kz->kzfav. 
	 */
	KzFavicon *kzfav = kz_favicon_get_instance();

	if (!bookmark) return;

	state = kz_bookmark_get_state(bookmark);
	if (state == KZ_BOOKMARK_STATE_LOADING)
	{
		color = LOADING_COLOR;
		stock = KZ_STOCK_RED;
	}
	else
	{
		color = NORMAL_COLOR;
		stock = KZ_STOCK_GREEN;
	}

	title = kz_bookmark_get_title(KZ_BOOKMARK(bookmark));
	/* use location? or link? */
	location = kz_bookmark_get_location(KZ_BOOKMARK(bookmark));
	link = kz_bookmark_get_link(bookmark);
	favicon = kz_favicon_get_pixbuf(kzfav,
					link,
					GTK_ICON_SIZE_MENU);
	g_object_unref(kzfav);
	gtk_tree_store_append(metatree->store, &iter, NULL);
	/* FIXME!! increase refcount? or weakref? */
	gtk_tree_store_set(metatree->store, &iter,
			   COLUMN_ICON,        favicon,
			   COLUMN_TITLE,       title,
			   COLUMN_TITLE_COLOR, color,
			   COLUMN_TITLE_SCALE, 1.2,
			   COLUMN_TITLE_SCALE_SET, TRUE,
			   COLUMN_URI,         location,
			   COLUMN_BOOKMARK,    bookmark,
			   COLUMN_TERMINATOR);
	if (favicon)
		g_object_unref(favicon);
	bookmark_connect_signal(metatree, bookmark);
	kz_meta_tree_refresh_meta(metatree, bookmark);
}


typedef struct _FindNodeMETA
{
	KzBookmark *bookmark;
	GtkTreeIter *iter;
} FindNodeMETA;


static gboolean
find_node_func (GtkTreeModel *model,
		GtkTreePath *path, GtkTreeIter *iter,
		FindNodeMETA *data)
{
	KzBookmark *bookmark = NULL;
	KzBookmark *item = NULL;

	if (!iter) return FALSE;

	gtk_tree_model_get(model, iter,
			   COLUMN_BOOKMARK,      &bookmark,
			   COLUMN_BOOKMARK_ITEM, &item,
			   COLUMN_TERMINATOR);
	if (bookmark && data->bookmark && bookmark == data->bookmark && !item)
	{
		data->iter = gtk_tree_iter_copy(iter);
		return TRUE;
	}

	return FALSE;
}


static GtkTreeIter *
find_node_from_meta (GtkTreeStore *store, KzBookmark *bookmark)
{
	FindNodeMETA data;

	data.iter = NULL;
	data.bookmark  = bookmark;

	gtk_tree_model_foreach(GTK_TREE_MODEL(store),
			       (GtkTreeModelForeachFunc) find_node_func,
			       &data);

	return data.iter;
}


static gboolean
remove_meta_signal_func (GtkTreeModel *model,
			 GtkTreePath *path, GtkTreeIter *iter,
			 gpointer data)
{
	KzBookmark *bookmark = NULL;
	KzMETATree *metatree;

	g_return_val_if_fail(KZ_IS_META_TREE(data), FALSE);
	metatree = KZ_META_TREE(data);

	g_return_val_if_fail(iter, FALSE);

	gtk_tree_model_get(model, iter,
			   COLUMN_BOOKMARK, &bookmark,
			   COLUMN_TERMINATOR);

	if (bookmark)
		bookmark_disconnect_signal(metatree, bookmark);

	return FALSE;
}


static void
remove_all_meta_signals (KzMETATree *metatree)
{
	g_return_if_fail(KZ_IS_META_TREE(metatree));

	gtk_tree_model_foreach(GTK_TREE_MODEL(metatree->store),
			       remove_meta_signal_func,
			       metatree);
}


/*****************************************************************************
 *                                                                           *
 *                               Callbacks                                   *
 *                                                                           *
 *****************************************************************************/
static void
cb_cursor_changed(GtkTreeView *tree_view, KzMETATree *metatree)
{
}


static gboolean
cb_tree_view_button_press (GtkWidget *widget, GdkEventButton *event,
			   KzMETATree *metatree)
{
	GtkTreePath *treepath = NULL;
	GtkTreeIter iter;
	GtkTreeViewColumn *column;
	gboolean success;
	KzWindow *kz;
	KzBookmark *bookmark = NULL;
	KzBookmark *item = NULL;
	const gchar *link = NULL;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_META_TREE(metatree), FALSE);

	kz = metatree->sidebar->kz;

	/* get node  */
	success = gtk_tree_view_get_path_at_pos(metatree->tree_view,
						event->x, event->y,
						&treepath, &column,
						NULL, NULL);
	/* get URI */
	if (success)
	{
		gtk_tree_model_get_iter(GTK_TREE_MODEL(metatree->store),
					&iter, treepath);
		gtk_tree_model_get(GTK_TREE_MODEL(metatree->store), &iter,
				   COLUMN_BOOKMARK,      &bookmark,
				   COLUMN_BOOKMARK_ITEM, &item,
				   COLUMN_TERMINATOR);
		if (item)
			link = kz_bookmark_get_link(item);
		else if (bookmark)
			link = kz_bookmark_get_link(KZ_BOOKMARK(bookmark));
	}

	/* open URI or show popup menu */
	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
	{
		if (link && *link)
			kz_window_load_url(kz, link);
	}
	else if (event->button == 2)
	{
		if (link && *link)
			kz_window_open_new_tab(kz, link);
	}
	else if (event->button == 3)
	{
		g_print("show popup menu.\n");
	}

	if (treepath)
		gtk_tree_path_free(treepath);

	return FALSE;
}


static gboolean
cb_tree_view_button_release (GtkWidget *widget, GdkEventButton *event,
			     KzMETATree *metatree)
{
	return FALSE;
}


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

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), FALSE);
	g_return_val_if_fail(KZ_IS_META_TREE(metatree), 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_bookmark_load_start (KzBookmark *bookmark,
			KzMETATree *metatree)
{
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_META_TREE(metatree));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	/* update icon */
	iter = find_node_from_meta(metatree->store, bookmark);
	if (!iter) return;

	gtk_tree_store_set(metatree->store, iter,
			   COLUMN_TITLE_COLOR, LOADING_COLOR,
			   COLUMN_TERMINATOR);

	gtk_tree_iter_free(iter);
}


static void
cb_bookmark_load_completed (KzBookmark *bookmark,
			    KzMETATree *metatree)
{
	GtkTreeIter *iter;

	g_return_if_fail(KZ_IS_META_TREE(metatree));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	iter = find_node_from_meta(metatree->store, bookmark);
	if (!iter) return;

	/* update icon */
	gtk_tree_store_set(metatree->store, iter,
			   COLUMN_TITLE_COLOR, NORMAL_COLOR,
			   COLUMN_TERMINATOR);

	gtk_tree_iter_free(iter);

	/* update children */
	kz_meta_tree_refresh_meta (metatree, bookmark);
}


static void
cb_bookmark_list_updated (KzBookmark *bookmarklist, KzMETATree *metatree)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmarklist));
	g_return_if_fail(KZ_IS_META_TREE(metatree));

	kz_meta_tree_refresh_all(metatree);
}

static void
cb_bookmark_list_insert_child (KzBookmark *bookmarklist,
			       KzBookmark *child, KzBookmark *sibling,
			       KzMETATree *metatree)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmarklist));
	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(KZ_IS_META_TREE(metatree));

	kz_meta_tree_insert_bookmark(metatree, child);
}


static void
cb_bookmark_list_remove_child (KzBookmark *bookmarklist,
			       KzBookmark *child,
			       KzMETATree *metatree)
{
	GtkTreeIter *parent_iter, iter;
	GtkTreePath *treepath;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmarklist));
	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(KZ_IS_META_TREE(metatree));

	parent_iter = find_node_from_meta(metatree->store, child);
	if (!parent_iter) return;

	bookmark_disconnect_signal(metatree, child);
	
	/* remove children */
	while (gtk_tree_model_iter_children(GTK_TREE_MODEL(metatree->store),
					    &iter, parent_iter))
	{
		gtk_tree_store_remove(metatree->store, &iter);
	}
	
	/* remove parent */
	treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(metatree->store),
					   parent_iter);
	gtk_tree_store_remove(metatree->store, parent_iter);
	gtk_tree_path_free(treepath);
	gtk_tree_iter_free(parent_iter);
}


static void
bookmark_connect_signal(KzMETATree *metatree, KzBookmark *bookmark)
{
	g_signal_connect(G_OBJECT(bookmark), "load-start",
			 G_CALLBACK(cb_bookmark_load_start),
			 metatree);
	g_signal_connect(G_OBJECT(bookmark), "load-completed",
			 G_CALLBACK(cb_bookmark_load_completed),
			 metatree);
}


static void
bookmark_disconnect_signal(KzMETATree *metatree, KzBookmark *bookmark)
{
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_load_start),
		 metatree);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_load_completed),
		 metatree);
}
