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

/*
 *  Copyright (C) 2003-2004 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.
 *
 *  $Id: kz-bookmarks-view.c 1249 2004-07-23 03:20:55Z ikezoe $
 */

#include "kz-bookmarks-view.h"

#include "intl.h"
#include "gobject-utils.h"
#include "kz-favicon.h"


enum {
	COLUMN_TITLE_EDITABLE,
	COLUMN_LOCATION_EDITABLE,
	COLUMN_BOOKMARK,
	COLUMN_ICON,
	COLUMN_TITLE,
	COLUMN_LOCATION,
	N_COLUMNS
};
#define TERMINATOR -1


enum {
	TARGET_KAZEHAKASE_BOOKMARKS,
};


struct _KzBookmarksViewPriv
{
	gboolean include_top;
	gboolean folder_only;
	gboolean tree_mode;
	gboolean editable;
};


static void     kz_bookmarks_view_class_init (KzBookmarksViewClass *klass);
static void     kz_bookmarks_view_init       (KzBookmarksView      *view);
static void     kz_bookmarks_view_dispose    (GObject              *object);

static void     connect_bookmark_signals     (KzBookmarksView *view,
					      KzBookmark *bookmark);
static void     disconnect_bookmark_signals  (KzBookmarksView *view,
					      KzBookmark *bookmark);

static GtkTreePath *find_row                 (GtkTreeModel  *model,
					      KzBookmark    *bookmark);
static void     insert_bookmark              (KzBookmarksView *view,
					      gboolean         folder_only,
					      KzBookmark      *bookmark,
					      KzBookmark      *parent,
					      KzBookmark      *sibling);
static void     remove_bookmark              (KzBookmarksView *view,
					      KzBookmark      *bookmark);
static void     expand_parent                (GtkTreeView     *treeview,
					      GtkTreePath     *path);

static void     cb_bookmark_title_edited     (GtkCellRendererText *cell,
					      const gchar      *path_str,
					      const gchar      *new_text,
					      KzBookmarksView  *view);
static void     cb_bookmark_location_edited  (GtkCellRendererText *cell,
					      const gchar      *path_str,
					      const gchar      *new_text,
					      KzBookmarksView  *view);

static gboolean cb_drag_motion               (GtkWidget *widget,
					      GdkDragContext *drag_context,
					      gint x,
					      gint y,
					      guint time,
					      KzBookmarksView  *view);
static void     cb_drag_data_get             (GtkWidget        *widget,
					      GdkDragContext   *context,
					      GtkSelectionData *seldata,
					      guint             info,
					      guint             time,
					      KzBookmarksView  *view);
static void     cb_drag_data_received        (GtkWidget        *widget,
					      GdkDragContext   *context,
					      gint x, gint y,
					      GtkSelectionData *seldata,
					      guint             info,
					      guint32           time,
					      KzBookmarksView  *view);

static GtkTargetEntry dnd_types[] = {
	{"_KAZEHAKASE_BOOKMARKS", 0, TARGET_KAZEHAKASE_BOOKMARKS},
};
static const gint dnd_types_num = G_N_ELEMENTS(dnd_types);


static GtkWindowClass *parent_class = NULL;


KZ_OBJECT_GET_TYPE(kz_bookmarks_view, "KzBookmarksView", KzBookmarksView,
		   kz_bookmarks_view_class_init, kz_bookmarks_view_init,
		   GTK_TYPE_TREE_VIEW)
KZ_OBJECT_FINALIZE(kz_bookmarks_view, KzBookmarksView)


static void
kz_bookmarks_view_class_init (KzBookmarksViewClass *klass)
{
	GObjectClass *gobject_class;
	GtkWidgetClass *widget_class;

	parent_class = g_type_class_peek_parent (klass);

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

	gobject_class->dispose      = kz_bookmarks_view_dispose;
	gobject_class->finalize     = kz_bookmarks_view_finalize;
#if 0
	gobject_class->set_property = kz_bookmarks_view_set_property;
	gobject_class->get_property = kz_bookmarks_view_get_property;

	g_object_class_install_property
		(gobject_class,
		 PROP_ROOT_FOLDER,
		 g_param_spec_object (
			 "root-folder",
			 _("Root Folder"),
			 _("The root bookmark folder to show"),
			 KZ_TYPE_BOOKMARK,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		gobject_class,
		PROP_TREE_MODE,
		g_param_spec_boolean(
			"tree-mode",
			_("Tree mode"),
			_("Whether use tree mode or not "
			  "for bookmark view"),
			TRUE,
			G_PARAM_READWRITE));
#endif
}


static void
kz_bookmarks_view_init (KzBookmarksView *view)
{
	view->root_folder = NULL;

	view->priv = g_new0(KzBookmarksViewPriv, 1);
	view->priv->include_top = TRUE;
	view->priv->folder_only = FALSE;
	view->priv->tree_mode   = TRUE;
	view->priv->editable    = TRUE;
}


static void
kz_bookmarks_view_dispose (GObject *object)
{
	KzBookmarksView *view = KZ_BOOKMARKS_VIEW(object);

	if (view->root_folder)
	{
		disconnect_bookmark_signals(view, view->root_folder);
		g_object_unref(view->root_folder);
		view->root_folder = NULL;
	}

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


GtkWidget *
kz_bookmarks_view_new (void)
{
	KzBookmarksView *view;
	GtkTreeStore *store;
	GtkCellRenderer *cell;
	GtkTreeViewColumn *column;

	view = KZ_BOOKMARKS_VIEW(g_object_new(KZ_TYPE_BOOKMARKS_VIEW,
					      NULL));

	store = gtk_tree_store_new(N_COLUMNS,
				   G_TYPE_BOOLEAN,
				   G_TYPE_BOOLEAN,
				   G_TYPE_POINTER,
				   GDK_TYPE_PIXBUF,
				   G_TYPE_STRING,
				   G_TYPE_STRING);
	gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
	g_object_unref(store);

	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);

	/* Title 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();
	g_signal_connect(G_OBJECT(cell), "edited",
			 G_CALLBACK(cb_bookmark_title_edited), view);
	gtk_tree_view_column_pack_start(column, cell, TRUE);
	gtk_tree_view_column_set_attributes(column, cell,
					    "editable", COLUMN_TITLE_EDITABLE,
					    "text",     COLUMN_TITLE,
					    NULL);
	gtk_tree_view_column_set_title(column, _("Title"));

	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width (column, 200);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);

	/* Location column */
	cell = gtk_cell_renderer_text_new();
	g_signal_connect(G_OBJECT(cell), "edited",
			 G_CALLBACK(cb_bookmark_location_edited), view);
	column = gtk_tree_view_column_new_with_attributes
			(_("Location"), cell,
			 "editable", COLUMN_LOCATION_EDITABLE,
			 "text",     COLUMN_LOCATION,
			 NULL);
	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width (column, 250);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);

	/* DnD */
	gtk_tree_view_enable_model_drag_source
		(GTK_TREE_VIEW(view),
		 GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK,
		 dnd_types,
		 dnd_types_num,
		 GDK_ACTION_ASK  | GDK_ACTION_COPY |
		 GDK_ACTION_MOVE | GDK_ACTION_LINK);
	gtk_tree_view_enable_model_drag_dest
		(GTK_TREE_VIEW(view),
		 dnd_types,
		 dnd_types_num,
		 GDK_ACTION_ASK  | GDK_ACTION_COPY |
		 GDK_ACTION_MOVE | GDK_ACTION_LINK);
	g_signal_connect(G_OBJECT(view), "drag-motion",
			 G_CALLBACK(cb_drag_motion), view);
	g_signal_connect(G_OBJECT(view), "drag-data-get",
			 G_CALLBACK(cb_drag_data_get), view);
	g_signal_connect(G_OBJECT(view), "drag-data-received",
			 G_CALLBACK(cb_drag_data_received), view);

	return GTK_WIDGET(view);
}


void
kz_bookmarks_view_set_root_folder (KzBookmarksView *view,
				   KzBookmark *top_folder,
				   gboolean tree_mode,
				   gboolean include_top,
				   gboolean folder_only,
				   gboolean editable)
{
	GtkTreeModel *model;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));
	g_return_if_fail(!top_folder || kz_bookmark_is_folder(top_folder));

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
	gtk_tree_store_clear(GTK_TREE_STORE(model));

	if (view->root_folder)
	{
		disconnect_bookmark_signals(view, view->root_folder);
		g_object_unref(view->root_folder);
		view->root_folder = NULL;
	}

	view->priv->tree_mode   = tree_mode;
	view->priv->include_top = include_top;
	view->priv->folder_only = folder_only;
	view->priv->editable    = editable;

	if (!top_folder) return;

	view->root_folder = top_folder;
	g_object_ref(view->root_folder);
	connect_bookmark_signals(view, view->root_folder);

	if (include_top)
	{
		insert_bookmark(view, folder_only, top_folder,
				NULL, NULL);
	}
	else
	{
		GList *children, *node;

		children = kz_bookmark_get_children(top_folder);
		for (node = children; node; node = g_list_next(node))
		{
			insert_bookmark(view, folder_only,
					node->data, top_folder, NULL);
		}
		g_list_free(children);

		/* FIXME! expand? */
	}
}


KzBookmark *
kz_bookmarks_view_get_bookmark(GtkTreeModel *model, GtkTreeIter *iter)
{
	KzBookmark *bookmark = NULL;

	g_return_val_if_fail(GTK_IS_TREE_MODEL(model), NULL);
	g_return_val_if_fail(iter, NULL);

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

	return bookmark;
}


void
kz_bookmarks_view_select(KzBookmarksView *view,
			 KzBookmark *bookmark)
{
	GtkTreeModel *model;
	GtkTreePath *path;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
	path = find_row (model, bookmark);
	if (!path) return;

	expand_parent(GTK_TREE_VIEW(view), path);
	gtk_tree_view_set_cursor(GTK_TREE_VIEW(view),
				 path, NULL, FALSE);
	gtk_tree_path_free(path);
}


typedef struct _FindRow
{
	KzBookmark *bookmark;
	GtkTreePath *path;
} FindRow;


static gboolean
find_row_func (GtkTreeModel *model,
	       GtkTreePath *path, GtkTreeIter *iter,
	       gpointer data)
{
	FindRow *findrow = data;
	KzBookmark *bookmark;

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

	if (findrow->bookmark == bookmark)
	{
		findrow->path = gtk_tree_path_copy(path);
		return TRUE;
	}

	return FALSE;
}


static GtkTreePath *
find_row (GtkTreeModel *model, KzBookmark *bookmark)
{
	FindRow findrow;

	g_return_val_if_fail(GTK_IS_TREE_MODEL(model), NULL);
	if (!bookmark) return NULL;
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	findrow.bookmark = bookmark;
	findrow.path = NULL;
	gtk_tree_model_foreach(model, find_row_func, &findrow);

	return findrow.path;
}


static KzBookmark *
find_folder_sibling (KzBookmark *parent, KzBookmark *sibling)
{
	GList *children, *node;

	g_return_val_if_fail(KZ_IS_BOOKMARK(parent), NULL);
	g_return_val_if_fail(kz_bookmark_is_folder(parent), NULL);

	if (!sibling) return NULL;

	children = kz_bookmark_get_children(parent);

	node = g_list_find(children, sibling);
	g_return_val_if_fail(node, NULL);

	for (; node; node = g_list_next(node))
	{
		KzBookmark *bookmark = node->data;

		if (!bookmark) continue;

		if (kz_bookmark_is_folder(bookmark))
			return bookmark;
	}

	g_list_free(children);

	return NULL;
}


static gboolean
needs_refresh (KzBookmarksView *view, KzBookmark *bookmark)
{
	KzBookmark *parent;

	if (!view->root_folder)
		return FALSE;

	if (view->root_folder == bookmark)
		return TRUE;

	/* FIXME! */
	if (view->priv->tree_mode)
		return TRUE;
	else
		return FALSE;

	for (parent = bookmark;
	     parent;
	     parent = kz_bookmark_get_parent(parent))
	{
		if (parent == view->root_folder)
			return TRUE;
	}

	return FALSE;
}


static void 
insert_bookmark(KzBookmarksView *view,
		gboolean folder_only,
		KzBookmark *bookmark,
		KzBookmark *parent, KzBookmark *sibling)
{
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter, tmp1, tmp2;
	GtkTreeIter *parent_iter = NULL, *sibling_iter = NULL;
	const gchar *title, *uri;
	GdkPixbuf *favicon;
	gboolean title_is_editable = TRUE, location_is_editable = TRUE;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

	/* get parent iter */
	if (parent)
	{
		path = find_row(model, parent);
		if (path)
		{
			gtk_tree_model_get_iter(model, &tmp1, path);
			parent_iter = &tmp1;
			gtk_tree_path_free(path);
		}
	}

	/* get sibling iter */
	if (sibling)
	{
		if (folder_only)
			sibling = find_folder_sibling(parent, sibling);

		path = find_row(model, sibling);
		if (path)
		{
			gtk_tree_model_get_iter(model, &tmp2, path);
			sibling_iter = &tmp2;
			gtk_tree_path_free(path);
		}
	}

	/* get attrs */
	title = kz_bookmark_get_title(bookmark);
	uri = kz_bookmark_is_file(bookmark)
		? kz_bookmark_get_location(bookmark)
		: kz_bookmark_get_link(bookmark);

	if (kz_bookmark_is_file(bookmark) ||
	    kz_bookmark_is_folder(bookmark))
	{
		GtkWidget *image;
		const gchar *icon;

		icon = kz_bookmark_is_file(bookmark)
			? KZ_STOCK_REMOTE_BOOKMARK : KZ_STOCK_FOLDER;
		image = gtk_image_new_from_stock(icon,
						 GTK_ICON_SIZE_MENU);
		favicon = gtk_widget_render_icon(image, icon,
						 GTK_ICON_SIZE_MENU,
						 NULL);
		gtk_widget_destroy(image);
	}
	else if (kz_bookmark_is_smart(bookmark))
	{
		GtkWidget *image;

		image = gtk_image_new_from_stock(KZ_STOCK_SMART_BOOKMARK,
						 GTK_ICON_SIZE_MENU);
		favicon = gtk_widget_render_icon(image, KZ_STOCK_SMART_BOOKMARK,
						 GTK_ICON_SIZE_MENU,
						 NULL);
		gtk_widget_destroy(image);
	}
	else if (kz_bookmark_is_separator(bookmark))
	{
		favicon = NULL;
	}
	else
	{
		KzFavicon *kz_favicon = kz_favicon_get_instance();

		favicon = kz_favicon_get_pixbuf(kz_favicon, uri,
						GTK_ICON_SIZE_MENU);
		
		if (!favicon)
		{
			GtkWidget *image;
			image = gtk_image_new_from_stock(KZ_STOCK_BOOKMARK,
							 GTK_ICON_SIZE_MENU);
			favicon = gtk_widget_render_icon(image, KZ_STOCK_BOOKMARK,
							 GTK_ICON_SIZE_MENU,
							 NULL);
			gtk_widget_destroy(image);
		}
		g_object_unref(kz_favicon);
	}

	if (view->priv->editable)
	{
		if (!kz_bookmark_is_editable(bookmark))
		{
			title_is_editable    = FALSE;
			location_is_editable = FALSE;
		}
		else if (kz_bookmark_is_separator(bookmark))
		{
			title_is_editable    = FALSE;
			location_is_editable = FALSE;
		}
		else if (!kz_bookmark_is_file(bookmark) &&
			 kz_bookmark_is_pure_folder(bookmark))
		{
			location_is_editable = FALSE;
		}
	}
	else
	{
		title_is_editable    = FALSE;
		location_is_editable = FALSE;
	}

	/* insert */
	gtk_tree_store_insert_before(GTK_TREE_STORE(model), &iter,
				     parent_iter, sibling_iter);
	gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
			   COLUMN_TITLE_EDITABLE,    title_is_editable,
			   COLUMN_LOCATION_EDITABLE, location_is_editable,
			   COLUMN_ICON,              favicon,
			   COLUMN_TITLE,             title,
			   COLUMN_LOCATION,          uri,
			   COLUMN_BOOKMARK,          bookmark,
			   TERMINATOR);
	if (favicon)
		g_object_unref(favicon);

	/* append children */
	if (kz_bookmark_is_folder(bookmark) &&
	    (folder_only ||
	     needs_refresh(view, bookmark)))
	{
		GList *children, *node;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			if (!KZ_IS_BOOKMARK(node->data))
				continue;
			if (folder_only && !kz_bookmark_is_folder(node->data))
				continue;

			insert_bookmark(view, folder_only,
					node->data, bookmark, NULL);
		}
		g_list_free(children);
	}

	/* expand */
	if (!kz_bookmark_get_folded(bookmark))
	{
		path = gtk_tree_model_get_path(model, &iter);
		gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, FALSE);
		gtk_tree_path_free(path);
	}
}


static void 
remove_bookmark(KzBookmarksView *view,
		KzBookmark *bookmark)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;
	
	treeview = GTK_TREE_VIEW(view);
	model = gtk_tree_view_get_model(treeview);
	gtk_widget_grab_focus(GTK_WIDGET(treeview));
	/* get iter */
	if (bookmark)
	{
		path = find_row(model, bookmark);
		if (!path) return;

		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_path_free(path);
	}	
	
	/* remove row */
	gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
}


static KzBookmark *
find_next_current_folder (KzBookmark *bookmark)
{
	KzBookmark *next;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	for (next = kz_bookmark_next(bookmark);
	     next;
	     next = kz_bookmark_next(next))
	{
		if (kz_bookmark_is_folder(next))
			return next;
	}

	for (next = kz_bookmark_prev(bookmark);
	     next;
	     next = kz_bookmark_prev(next))
	{
		if (kz_bookmark_is_folder(next))
			return next;
	}

	return kz_bookmark_get_parent(bookmark);
}


static void
ensure_cursor (KzBookmarksView *view, KzBookmark *bookmark)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path = NULL;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	/* set folder view's cursor */
	treeview = GTK_TREE_VIEW(view);
	model = gtk_tree_view_get_model(treeview);

	gtk_tree_view_get_cursor (treeview, &path, NULL);
	if (path)
	{
		GtkTreeIter iter;
		KzBookmark *folder = NULL;

		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_model_get (model, &iter,
				    COLUMN_BOOKMARK, &folder,
				    TERMINATOR);
		gtk_tree_path_free(path);

		if (folder == bookmark)
		{
			folder = find_next_current_folder(bookmark);
			if (folder)
			{
				kz_bookmarks_view_select(view, folder);
				return;
			}
		}
	}

	/* set bookmarks view's cursor */
	treeview = GTK_TREE_VIEW(view);
	model = gtk_tree_view_get_model(treeview);

	gtk_tree_view_get_cursor (treeview, &path, NULL);
	if (path)
	{
		GtkTreeIter iter;
		KzBookmark *selected = NULL;

		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_model_get (model, &iter,
				    COLUMN_BOOKMARK, &selected,
				    TERMINATOR);
		gtk_tree_path_free(path);

		if (selected == bookmark)
		{
			selected = kz_bookmark_next(bookmark);
			if (!selected)
				selected = kz_bookmark_prev(bookmark);
			if (selected)
				kz_bookmarks_view_select(view, selected);
		}
	}
}


static void
sync_bookmark_properties (KzBookmarksView *view, KzBookmark *bookmark)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path = NULL;
	GtkTreeIter iter;
	const gchar *title, *uri;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	title = kz_bookmark_get_title(bookmark);
	uri = kz_bookmark_is_file(bookmark)
		? kz_bookmark_get_location(bookmark)
		: kz_bookmark_get_link(bookmark);

	treeview = GTK_TREE_VIEW(view);
	model = gtk_tree_view_get_model(treeview);
	path = find_row(model, bookmark);
	if (path)
	{
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
				   COLUMN_TITLE,    title,
				   COLUMN_LOCATION, uri,
				   TERMINATOR);
		gtk_tree_path_free(path);
		path = NULL;
	}
}


static void
expand_parent(GtkTreeView *treeview, GtkTreePath *path)
{
	GtkTreePath *parent = gtk_tree_path_copy(path);

	if (gtk_tree_path_up(parent))
	{
		expand_parent(treeview, parent);
		gtk_tree_view_expand_row(treeview, parent, FALSE);
	}

	gtk_tree_path_free(parent);
}


static void
cb_bookmark_insert_child (KzBookmark *bookmark,
			  KzBookmark *child, KzBookmark *sibling,
			  KzBookmarksView *view)
{
	if (!needs_refresh(view, bookmark))
		return;

	connect_bookmark_signals (view, child);

	if (!view->priv->folder_only || kz_bookmark_is_folder(child))
		insert_bookmark(view, view->priv->folder_only,
				child, bookmark, sibling);
}


static void
cb_bookmark_remove_child (KzBookmark *bookmark, KzBookmark *child,
			  KzBookmarksView *view)
{
	disconnect_bookmark_signals (view, child);

	/* set selection */
	ensure_cursor(view, child);

	remove_bookmark(view, child);
}


static void
cb_bookmark_notify (GObject *object, GParamSpec *pspec,
		    KzBookmarksView *view)
{
	KzBookmark *bookmark;

	g_return_if_fail(KZ_IS_BOOKMARK(object));
	bookmark = KZ_BOOKMARK(object);

	sync_bookmark_properties(view, bookmark);
}


static void
connect_bookmark_signals (KzBookmarksView *view,
			  KzBookmark *bookmark)
{
	GList *children, *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	g_signal_connect_after(G_OBJECT(bookmark), "insert-child",
			       G_CALLBACK(cb_bookmark_insert_child), view);
	g_signal_connect_after(G_OBJECT(bookmark), "remove-child",
			       G_CALLBACK(cb_bookmark_remove_child), view);
	g_signal_connect(G_OBJECT(bookmark), "notify",
			 G_CALLBACK(cb_bookmark_notify), view);

	if (!kz_bookmark_is_folder(bookmark)) return;

	children = kz_bookmark_get_children(bookmark);
	for (node = children; node; node = g_list_next(node))
		connect_bookmark_signals (view, node->data);
	g_list_free(children);
}


static void
disconnect_bookmark_signals (KzBookmarksView *view,
			     KzBookmark *bookmark)
{
	GList *children, *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_insert_child), view);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_remove_child), view);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_notify), view);

	if (!kz_bookmark_is_folder(bookmark)) return;

	children = kz_bookmark_get_children(bookmark);
	for (node = children; node; node = g_list_next(node))
		disconnect_bookmark_signals (view, node->data);
	g_list_free(children);
}


static void
cb_bookmark_title_edited (GtkCellRendererText *cell,
			  const gchar *path_str,
			  const gchar *new_text,
			  KzBookmarksView *view)
{
	GtkTreeModel *model;
        GtkTreeIter  iter;
	KzBookmark *bookmark = NULL;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
        gtk_tree_model_get_iter_from_string(model, &iter, path_str);
	gtk_tree_model_get(model, &iter,
			   COLUMN_BOOKMARK, &bookmark,
			   TERMINATOR);
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	kz_bookmark_set_title(bookmark, new_text);
}


static void
cb_bookmark_location_edited (GtkCellRendererText *cell,
			     const gchar *path_str,
			     const gchar *new_text,
			     KzBookmarksView *view)
{
	GtkTreeModel *model;
        GtkTreeIter  iter;
	KzBookmark *bookmark = NULL;

	g_return_if_fail(KZ_IS_BOOKMARKS_VIEW(view));

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
        gtk_tree_model_get_iter_from_string(model, &iter, path_str);
	gtk_tree_model_get(model, &iter,
			   COLUMN_BOOKMARK, &bookmark,
			   TERMINATOR);
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (kz_bookmark_is_file(bookmark))
	{
		kz_bookmark_set_location(bookmark, new_text);
		kz_bookmark_load_start(bookmark);
	}
	else
	{
		kz_bookmark_set_link(bookmark, new_text);
	}
}


static gboolean
cb_drag_motion (GtkWidget *widget,
		GdkDragContext *drag_context,
		gint x, gint y, guint time,
		KzBookmarksView *view)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkTreePath *dest_path = NULL;
	GtkTreeViewDropPosition pos;
	KzBookmark *bookmark = NULL;
	gboolean success, retval = FALSE;

	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), TRUE);
	g_return_val_if_fail(KZ_IS_BOOKMARKS_VIEW(view), TRUE);

	success = gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
						    x, y,
						    &dest_path, &pos);
	if (!success) return FALSE;

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
	gtk_tree_model_get_iter(model, &iter, dest_path);
	gtk_tree_model_get(model, &iter,
			   COLUMN_BOOKMARK, &bookmark,
			   TERMINATOR);

	if (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE ||
	    pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
	{
		if ((kz_bookmark_is_file(bookmark) &&
		     !kz_bookmark_file_is_editable(bookmark)) ||
		    (!kz_bookmark_is_file(bookmark) &&
		     !kz_bookmark_is_editable(bookmark)))
		{
			gdk_drag_status(drag_context, 0, time);
			retval = TRUE;
		}
		else
		{
			gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
		}
	}
	else if (pos == GTK_TREE_VIEW_DROP_BEFORE ||
		 pos == GTK_TREE_VIEW_DROP_AFTER)
	{
		if (bookmark == view->root_folder ||
		    !kz_bookmark_is_editable(bookmark))
		{
			gdk_drag_status(drag_context, 0, time);
			retval = TRUE;
		}
	}
#if 0 /* FIXME */
	else if (!kz_bookmark_is_folder(src_bookmark))
	{
		gdk_drag_status(drag_context, 0, time);
		retval = TRUE;
	}
#endif

	if (dest_path)
		gtk_tree_path_free(dest_path);

	return retval;
}


static void
cb_drag_data_get (GtkWidget *widget,
		  GdkDragContext *context,
		  GtkSelectionData *seldata,
		  guint info,
		  guint time,
		  KzBookmarksView *view)
{
	switch (info)
	{
	case TARGET_KAZEHAKASE_BOOKMARKS:
		gtk_selection_data_set(seldata, seldata->target,
				       8, "dummy", strlen("dummy"));
		break;
	}
}


static void
cb_drag_data_received (GtkWidget *widget,
                       GdkDragContext *context,
                       gint x, gint y,
                       GtkSelectionData *seldata,
                       guint info,
                       guint32 time,
                       KzBookmarksView *view)
{
	KzBookmarksView *src_view = NULL;
	GtkTreeView *treeview = GTK_TREE_VIEW(widget);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreePath *src_path = NULL, *dest_path = NULL;
	GtkTreeIter src_iter, dest_iter;
	GtkTreeViewDropPosition pos;
	GtkWidget *src_widget;
	KzBookmark *src, *dest, *parent;

	/* get dest bookmark */
	gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
					  x, y,
					  &dest_path, &pos);
	if (!dest_path) return;
	gtk_tree_model_get_iter(model, &dest_iter, dest_path);
	gtk_tree_model_get(model, &dest_iter,
			   COLUMN_BOOKMARK, &dest,
			   TERMINATOR);
	if (!dest) goto ERROR;

	/* get src bookmark */
	/* FIXME */
	src_widget = gtk_drag_get_source_widget(context);
	if (KZ_IS_BOOKMARKS_VIEW(src_widget))
		src_view = KZ_BOOKMARKS_VIEW(src_widget);
	if (!src_view || !GTK_IS_TREE_VIEW(src_widget)) goto ERROR;

	/* FIXME! detect src widget type */
	/* FIXME! enable multiple dragging */
	model = gtk_tree_view_get_model(GTK_TREE_VIEW(src_widget));
	gtk_tree_view_get_cursor (GTK_TREE_VIEW(src_widget), &src_path, NULL);
	if (src_path)
	{
		gtk_tree_model_get_iter(model, &src_iter, src_path);
		gtk_tree_model_get(model, &src_iter,
				   COLUMN_BOOKMARK, &src,
				   TERMINATOR);
	}
	if (!src_path || !src) goto ERROR;
	if (src == dest) goto ERROR;

	/* move the bookmark(s?) */
	switch (info)
	{
	case TARGET_KAZEHAKASE_BOOKMARKS:
		parent = kz_bookmark_get_parent(src);
		if (!parent) goto ERROR;
		g_object_ref(src);
		kz_bookmark_remove(parent, src);

		if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE ||
		     pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) &&
		    kz_bookmark_is_folder(dest))
		{
			/* move the src bookmark into the dest folder */
			parent = dest;
			dest = NULL;
		}
		else
		{
			/* move the bookmark before or after the dest bookmark */
			parent = kz_bookmark_get_parent(dest);

			if ((pos == GTK_TREE_VIEW_DROP_AFTER ||
			     pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER))
			{
				GList *children, *node;

				children = kz_bookmark_get_children(parent);
				node = g_list_find(children, dest);
				node = g_list_next(node);
				if (node)
					dest = node->data;
				else
					dest = NULL;
				g_list_free(children);
			}
		}

		if (!parent) goto ERROR;

		kz_bookmark_insert_before(parent, src, dest);
		gtk_drag_finish(context, TRUE, FALSE, time);
		break;
	default:
		break;
	}

	gtk_tree_path_free(src_path);
ERROR:
	gtk_tree_path_free(dest_path);
}
