/* -*- 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.
 *
 *  $Id: kz-bookmark-editor.c 626 2004-01-19 13:41:12Z ikezoe $
 */

#include "kz-bookmark-editor.h"

#include <string.h>

#include "kazehakase.h"
#include "gobject-utils.h"
#include "intl.h"
#include "egg-toggle-action.h"
#include "kz-window.h"
#include "kz-bookmark-edit.h"
#include "kz-actions-bookmark.h"
#include "kz-icons.h"

struct _KzBookmarkEditorPriv
{
	gboolean tree_mode;
};

enum {
	PROP_0,
	PROP_ROOT_FOLDER,
	PROP_TREE_MODE
};

enum {
	TARGET_KAZEHAKASE_BOOKMARKS,
};

#define TERMINATOR -1
enum {
	COLUMN_BOOKMARK,
	COLUMN_ICON,
	COLUMN_TITLE,
	COLUMN_URI,
	N_COLUMNS
};

#define	FOLDER_N_COLUMNS 3


/* object class */
static void     kz_bookmark_editor_class_init   (KzBookmarkEditorClass *klass);
static void     kz_bookmark_editor_init         (KzBookmarkEditor *editor);
static void     kz_bookmark_editor_dispose      (GObject          *object);
static void     kz_bookmark_editor_set_property (GObject          *object,
						 guint             prop_id,
						 const GValue     *value,
						 GParamSpec       *pspec);
static void     kz_bookmark_editor_get_property (GObject          *object,
						 guint             prop_id,
						 GValue           *value,
						 GParamSpec       *pspec);

/* widget event */
static gboolean kz_bookmark_editor_delete_event (GtkWidget        *widget,
						 GdkEventAny      *event);

/* private methods */
static void     kz_bookmark_editor_set_folder_tree   (KzBookmarkEditor *editor);
static void     kz_bookmark_editor_set_bookmark_tree (KzBookmarkEditor *editor,
						      KzBookmark       *parent);

static GtkTreePath *folder_view_find_row        (KzBookmarkEditor *editor,
						 KzBookmark       *folder);
static GtkTreePath *bookmarks_view_find_row     (KzBookmarkEditor *editor,
						 KzBookmark       *folder);


static void     cb_menu_merge_add_widget        (EggMenuMerge     *merge,
						 GtkWidget        *widget,
						 GtkBox           *box);
static void     cb_folder_view_cursor_changed   (GtkTreeView      *treeview,
						 KzBookmarkEditor *editor);
static void     cb_bookmarks_view_cursor_changed(GtkTreeView      *treeview,
						 KzBookmarkEditor *editor);
static void     cb_bookmarks_view_selection_changed
						(GtkTreeSelection *selection,
						 KzBookmarkEditor *editor);
static gboolean cb_bookmarks_view_button_press  (GtkWidget        *widget,
						 GdkEventButton   *button,
						 KzBookmarkEditor *editor);
gboolean        cb_folder_view_drag_motion      (GtkWidget *widget,
						 GdkDragContext *drag_context,
						 gint x,
						 gint y,
						 guint time,
						 KzBookmarkEditor *editor);
static void     cb_drag_data_get                (GtkWidget        *widget,
						 GdkDragContext   *context,
						 GtkSelectionData *seldata,
						 guint             info,
						 guint             time,
						 KzBookmarkEditor *editor);
static void     cb_drag_data_received           (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint x, gint y,
						 GtkSelectionData *seldata,
						 guint             info,
						 guint32           time,
						 KzBookmarkEditor *editor);

static void     insert_bookmark                 (KzBookmarkEditor *editor,
						 gboolean folder_view,
						 KzBookmark *bookmark,
						 KzBookmark *parent,
						 KzBookmark *sibling);
static void     remove_bookmark                 (KzBookmarkEditor *editor,
						 gboolean folder_view,
						 KzBookmark *bookmark);
static void     connect_bookmark_signals        (KzBookmarkEditor *editor,
						 KzBookmark       *bookmark);
static void     disconnect_bookmark_signals     (KzBookmarkEditor *editor,
						 KzBookmark       *bookmark);


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_bookmark_editor, "KzBookmarkEditor", KzBookmarkEditor,
		   kz_bookmark_editor_class_init, kz_bookmark_editor_init,
		   GTK_TYPE_WINDOW)
KZ_OBJECT_FINALIZE(kz_bookmark_editor, KzBookmarkEditor)


static void
kz_bookmark_editor_class_init (KzBookmarkEditorClass *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_bookmark_editor_dispose;
	gobject_class->finalize     = kz_bookmark_editor_finalize;
	gobject_class->set_property = kz_bookmark_editor_set_property;
	gobject_class->get_property = kz_bookmark_editor_get_property;

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

	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));
}


static void
kz_bookmark_editor_init (KzBookmarkEditor *editor)
{
	GtkTreeStore *store;
	GtkWidget *hpaned, *vpaned, *main_vbox;
	GtkWidget *scrwin[3], *treeview[2], *edit_view, *statusbar;
	GtkCellRenderer *cell;
	GtkTreeSelection *selection;
	GtkTreeViewColumn *column;
	GtkAccelGroup  *accels;
	EggActionGroup *actions;
	EggMenuMerge   *menu_merge;
	EggAction *action;
	GError *err = NULL;

	g_object_set(G_OBJECT(editor), "allow-shrink", TRUE, NULL);

	gtk_window_set_title(GTK_WINDOW(editor),
			     _("- Kazehakase Bookmark Editor -"));
	gtk_window_set_position(GTK_WINDOW(editor),
				GTK_WIN_POS_CENTER_ON_PARENT);

	accels     = gtk_accel_group_new();
	actions    = kz_actions_bookmark_create_group(editor, accels);
	menu_merge = egg_menu_merge_new();

	gtk_window_add_accel_group(GTK_WINDOW(editor), accels);

	/* toplevel vbox */
	main_vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(editor), main_vbox);
	gtk_widget_show(main_vbox);

	/* menu and toolbar */
	egg_menu_merge_set_accel_group(menu_merge, accels);
	egg_menu_merge_insert_action_group(menu_merge, actions, 0);
	g_signal_connect(menu_merge, "add_widget",
			 G_CALLBACK(cb_menu_merge_add_widget), main_vbox);
	egg_menu_merge_add_ui_from_file(menu_merge,
					KZ_SYSCONFDIR"/kz-ui-bookmarks.xml",
					&err);
	if (err)
	{
		g_warning("%s", err->message);
		g_error_free(err);
	}
        egg_menu_merge_ensure_update(menu_merge);

	/* paned for main contents */
	hpaned = gtk_hpaned_new();
	gtk_box_pack_start(GTK_BOX(main_vbox), hpaned, TRUE, TRUE, 0);
	gtk_widget_show(hpaned);

	/* folder view */
	scrwin[0] = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin[0]),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin[0]),
					    GTK_SHADOW_IN);

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

	gtk_paned_add1(GTK_PANED(hpaned), scrwin[0]);

	treeview[0] = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	/* FIXME */
	g_object_set_data(G_OBJECT(treeview[0]),
			  "KzBookmarkEditor::KzBookmarkEditor",
			  editor);
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(treeview[0]), FALSE);
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(treeview[0]), TRUE);
	gtk_tree_view_enable_model_drag_source
		(GTK_TREE_VIEW(treeview[0]),
		 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 (treeview[0]),
		 dnd_types,
		 dnd_types_num,
		 GDK_ACTION_ASK  | GDK_ACTION_COPY |
		 GDK_ACTION_MOVE | GDK_ACTION_LINK);
	gtk_container_add(GTK_CONTAINER(scrwin[0]), treeview[0]);
	gtk_widget_show(treeview[0]);

	g_signal_connect(G_OBJECT(treeview[0]), "drag-motion",
			 G_CALLBACK(cb_folder_view_drag_motion), editor);
	g_signal_connect(G_OBJECT(treeview[0]), "drag-data-get",
			 G_CALLBACK(cb_drag_data_get), editor);
	g_signal_connect(G_OBJECT(treeview[0]), "drag-data-received",
			 G_CALLBACK(cb_drag_data_received), editor);
	g_signal_connect(G_OBJECT(treeview[0]), "cursor-changed",
			 G_CALLBACK(cb_folder_view_cursor_changed), editor);

	/* 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_set_title(column, _("Title"));
	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview[0]), column);

	/* vpaned */
	vpaned = gtk_vpaned_new();
	gtk_paned_add2(GTK_PANED(hpaned), vpaned);
	gtk_widget_show(vpaned);

	/* bookmark view */
	scrwin[1] = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin[1]),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin[1]),
					    GTK_SHADOW_IN);
	gtk_paned_add1(GTK_PANED(vpaned), scrwin[1]);
	gtk_widget_show(scrwin[1]);

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

	treeview[1] = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	/* FIXME */
	g_object_set_data(G_OBJECT(treeview[1]),
			  "KzBookmarkEditor::KzBookmarkEditor",
			  editor);
	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview[1]));
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview[1]), TRUE);
	gtk_tree_view_enable_model_drag_source
		(GTK_TREE_VIEW(treeview[1]),
		 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 (treeview[1]),
		 dnd_types,
		 dnd_types_num,
		 GDK_ACTION_ASK  | GDK_ACTION_COPY |
		 GDK_ACTION_MOVE | GDK_ACTION_LINK);
	gtk_container_add(GTK_CONTAINER(scrwin[1]), treeview[1]);
	gtk_widget_show(treeview[1]);

	g_signal_connect(G_OBJECT(treeview[1]), "drag-data-get",
			 G_CALLBACK(cb_drag_data_get), editor);
	g_signal_connect(G_OBJECT(treeview[1]), "drag-data-received",
			 G_CALLBACK(cb_drag_data_received), editor);
	g_signal_connect(G_OBJECT(treeview[1]), "cursor-changed",
			 G_CALLBACK(cb_bookmarks_view_cursor_changed), editor);
	g_signal_connect(G_OBJECT(selection), "changed",
			 G_CALLBACK(cb_bookmarks_view_selection_changed),
			 editor);
	g_signal_connect(G_OBJECT(treeview[1]), "button-press-event",
			 G_CALLBACK(cb_bookmarks_view_button_press),
			 editor);

	/* 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();
	gtk_tree_view_column_pack_start(column, cell, TRUE);
	gtk_tree_view_column_add_attribute(column, cell,
					   "text", COLUMN_TITLE);
	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(treeview[1]), column);

	/* URI column */
	cell = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes
			(_("URI"), cell,
			 "text",     COLUMN_URI,
			 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(treeview[1]), column);

	/* Content view */
	/* edit_view = kz_bookmark_edit_view_new(); */
	edit_view = kz_bookmark_edit_new();
	gtk_paned_add2(GTK_PANED(vpaned), edit_view);

	/* Status bar */
	statusbar = gtk_statusbar_new();
	gtk_box_pack_start(GTK_BOX(main_vbox), statusbar, FALSE, FALSE, 0);
	gtk_widget_show(statusbar);

	/* set members of instance */
	editor->vbox           = main_vbox;
	editor->hpaned         = hpaned;
	editor->vpaned         = vpaned;
	editor->scrolledwin[0] = scrwin[0];
	editor->scrolledwin[1] = scrwin[1];
	editor->folder_view    = treeview[0];
	editor->bookmarks_view = treeview[1];
	editor->statusbar      = statusbar;
	editor->content_view   = edit_view;

	editor->root_folder    = NULL;
	editor->current_folder = NULL;

	editor->action_group   = actions;
	editor->menu_merge     = menu_merge;

	editor->kz_favicon     = kz_favicon_get_instance();

	editor->priv = g_new0(KzBookmarkEditorPriv, 1);
	editor->priv->tree_mode = TRUE;

	/* set default state */
	kz_bookmark_editor_restore_state(editor);

	action = egg_action_group_get_action(editor->action_group,
					     "RemoveBookmarkItem");
	egg_action_set_sensitive(action, FALSE);
}


static void
kz_bookmark_editor_dispose (GObject *object)
{
	KzBookmarkEditor *editor = KZ_BOOKMARK_EDITOR(object);

	if (editor->root_folder)
		disconnect_bookmark_signals(editor, editor->root_folder);
	editor->root_folder = NULL;

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

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

	if (editor->kz_favicon)
		g_object_unref(editor->kz_favicon);
	editor->kz_favicon = NULL;

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


static void
kz_bookmark_editor_set_property (GObject         *object,
				 guint            prop_id,
				 const GValue    *value,
				 GParamSpec      *pspec)
{
	KzBookmarkEditor *editor = KZ_BOOKMARK_EDITOR(object);
  
	switch (prop_id)
	{
	case PROP_ROOT_FOLDER:
		if (editor->root_folder)
		{
			disconnect_bookmark_signals(editor, editor->root_folder);
			g_object_unref(editor->root_folder);
		}
		editor->root_folder = g_object_ref(g_value_get_object(value));
		if (editor->root_folder)
			connect_bookmark_signals(editor, editor->root_folder);

		kz_bookmark_editor_set_folder_tree(editor);
		kz_bookmark_editor_set_bookmark_tree(editor, editor->root_folder);
		break;
	case PROP_TREE_MODE:
		editor->priv->tree_mode = g_value_get_boolean(value);
		kz_bookmark_editor_set_bookmark_tree(editor,
						     editor->current_folder);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_bookmark_editor_get_property (GObject         *object,
				 guint            prop_id,
				 GValue          *value,
				 GParamSpec      *pspec)
{
	KzBookmarkEditor *editor = KZ_BOOKMARK_EDITOR(object);

	switch (prop_id)
	{
	case PROP_ROOT_FOLDER:
		g_value_set_object(value, editor->root_folder);
		break;
	case PROP_TREE_MODE:
		g_value_set_boolean(value, editor->priv->tree_mode);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static gboolean
kz_bookmark_editor_delete_event (GtkWidget *widget, GdkEventAny *event)
{
	KzBookmarkEditor *editor;

	g_return_val_if_fail(KZ_IS_BOOKMARK_EDITOR(widget), FALSE);

	editor = KZ_BOOKMARK_EDITOR(widget);
	kz_bookmark_editor_store_state(editor);

	return FALSE;
}


static void
kz_bookmark_editor_set_folder_tree (KzBookmarkEditor *editor)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;

	treeview = GTK_TREE_VIEW(editor->folder_view);
	model = gtk_tree_view_get_model(treeview);
	gtk_tree_store_clear(GTK_TREE_STORE(model));

	insert_bookmark(editor, TRUE, editor->root_folder, NULL, NULL);
}


static void
kz_bookmark_editor_set_bookmark_tree (KzBookmarkEditor *editor,
				      KzBookmark *parent)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;

	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));
	g_return_if_fail(!parent || kz_bookmark_is_folder(parent));

	treeview = GTK_TREE_VIEW(editor->bookmarks_view);
	model = gtk_tree_view_get_model(treeview);
	gtk_tree_store_clear(GTK_TREE_STORE(model));

	editor->current_folder = parent;

	/* append children */
	if (parent)
	{
		GList *children, *node;

		children = kz_bookmark_get_children(parent);
		for (node = children; node; node = g_list_next(node))
		{
			insert_bookmark(editor, FALSE,
					node->data, parent, NULL);
		}
		g_list_free(children);

#if 0
		/* expand? */
#endif
	}
}


GtkWidget *
kz_bookmark_editor_new (KzBookmark *bookmark)
{
	KzBookmarkEditor *editor;

	editor = KZ_BOOKMARK_EDITOR(g_object_new(KZ_TYPE_BOOKMARK_EDITOR,
						 "type", GTK_WINDOW_TOPLEVEL,
						 "root-folder", bookmark,
						 NULL));
	return GTK_WIDGET(editor);
}


void
kz_bookmark_editor_set_current (KzBookmarkEditor *editor,
				KzBookmark *bookmark)
{
	kz_bookmark_editor_folder_view_select(editor, bookmark);
}


void
kz_bookmark_editor_set_tree_mode (KzBookmarkEditor *editor,
				  gboolean tree_mode)
{
	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	g_object_set(G_OBJECT(editor), "tree-mode", tree_mode, NULL);
}


void
kz_bookmark_editor_store_state (KzBookmarkEditor *editor)
{
	EggAction *action;
	gint client_x, client_y, width, height;
	gboolean show_folder_view, show_edit_view;

	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	gdk_window_get_geometry (GTK_WIDGET(editor)->window,
				 &client_x, &client_y,
				 &width, &height, NULL);

	action = egg_action_group_get_action(editor->action_group,
					     "ShowHideFolderView");
	show_folder_view
		= egg_toggle_action_get_active(EGG_TOGGLE_ACTION(action));

	action = egg_action_group_get_action(editor->action_group,
					     "ShowHideContentView");
	show_edit_view
		= egg_toggle_action_get_active(EGG_TOGGLE_ACTION(action));

	KZ_CONF_SET("BookmarkEditorWindow", "width",  width,  INT);
	KZ_CONF_SET("BookmarkEditorWindow", "height", height, INT);
	KZ_CONF_SET("BookmarkEditorWindow", "show_folder_view",
		    show_folder_view, BOOL);
	KZ_CONF_SET("BookmarkEditorWindow", "show_content_view",
		    show_edit_view, BOOL);

	width = editor->scrolledwin[0]->allocation.width;
	if (width > 8)
		KZ_CONF_SET("BookmarkEditorWindow", "folder_view_width",
			    width, INT);
	height = editor->content_view->allocation.height;
	if (height > 8)
	{
		height = editor->vpaned->allocation.height - height;
		KZ_CONF_SET("BookmarkEditorWindow",
			    "bookmarks_view_height",
			    height, INT);
	}

	/* mode */
	action = egg_action_group_get_action(editor->action_group,
					     "TreeMode");
	if (egg_toggle_action_get_active(EGG_TOGGLE_ACTION(action)))
		KZ_CONF_SET("BookmarkEditorWindow", "mode",
			    "TreeMode", STRING);
	else
		KZ_CONF_SET("BookmarkEditorWindow", "mode",
			    "ListMode", STRING);
}


void
kz_bookmark_editor_restore_state (KzBookmarkEditor *editor)
{
	EggAction *action;
	gint width = 600, height = 450;
	gint folder_view_width = 150, bookmarks_view_height = 230;
	gboolean show_folder_view = TRUE, show_edit_view = FALSE;
	gchar *mode;

	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	KZ_CONF_GET("BookmarkEditorWindow", "width",  width, INT);
	KZ_CONF_GET("BookmarkEditorWindow", "height", height, INT);
	KZ_CONF_GET("BookmarkEditorWindow", "folder_view_width",
		    folder_view_width, INT);
	KZ_CONF_GET("BookmarkEditorWindow", "bookmarks_view_height",
		    bookmarks_view_height, INT);
	KZ_CONF_GET("BookmarkEditorWindow", "show_folder_view",
		    show_folder_view, BOOL);
	KZ_CONF_GET("BookmarkEditorWindow", "show_content_view",
		    show_edit_view, BOOL);
	mode = KZ_CONF_GET_STR("BookmarkEditorWindow", "mode");

	/* size */
	gtk_window_set_default_size(GTK_WINDOW(editor), width, height);

	action = egg_action_group_get_action(editor->action_group,
					     "ShowHideFolderView");
	egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action),
				     show_folder_view);

	action = egg_action_group_get_action(editor->action_group,
					     "ShowHideContentView");
	egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action),
				     show_edit_view);

	gtk_widget_set_size_request(GTK_WIDGET(editor->scrolledwin[0]),
				    folder_view_width, -1);
	gtk_widget_set_size_request(GTK_WIDGET(editor->scrolledwin[1]),
				    -1, bookmarks_view_height);

	/* mode */
	if (mode && !strcmp(mode, "TreeMode"))
	{
		action = egg_action_group_get_action(editor->action_group,
						     "TreeMode");
		egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action), TRUE);
	}
	else
	{
		action = egg_action_group_get_action(editor->action_group,
						     "ListMode");
		egg_toggle_action_set_active(EGG_TOGGLE_ACTION(action), TRUE);
	}

	g_free(mode);
}


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_folder_view_cursor_changed (GtkTreeView *treeview, KzBookmarkEditor *editor)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkTreePath *path = NULL;
	KzBookmark *folder;

	g_return_if_fail(treeview);
	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	kz_bookmark_edit_clear(KZ_BOOKMARK_EDIT(editor->content_view));

	model = gtk_tree_view_get_model(treeview);

	gtk_tree_view_get_cursor (treeview, &path, NULL);
	if (!path) {
		kz_bookmark_edit_clear(KZ_BOOKMARK_EDIT(editor->content_view));
		return;
	}

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

	kz_bookmark_editor_set_bookmark_tree(editor, folder);

	gtk_tree_path_free(path);
}


static void
cb_bookmarks_view_cursor_changed (GtkTreeView *treeview,
				  KzBookmarkEditor *editor)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkTreePath *path = NULL;
	KzBookmark *bookmark = NULL;

	g_return_if_fail(GTK_IS_TREE_VIEW(treeview));
	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	model = gtk_tree_view_get_model(treeview);

	gtk_tree_view_get_cursor (treeview, &path, NULL);
	if (!path) {
		kz_bookmark_edit_clear(KZ_BOOKMARK_EDIT(editor->content_view));
		return;
	}

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

	if (bookmark)
		kz_bookmark_edit_set(KZ_BOOKMARK_EDIT(editor->content_view),
				     bookmark);

	gtk_tree_path_free(path);
}


static void
cb_bookmarks_view_selection_changed (GtkTreeSelection *selection,
				     KzBookmarkEditor *editor)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean selected;
	EggAction *action;

	g_return_if_fail(GTK_IS_TREE_SELECTION(selection));
	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));

	selected = gtk_tree_selection_get_selected(selection, &model, &iter);

	action = egg_action_group_get_action(editor->action_group,
					     "RemoveBookmarkItem");

	if (selected)
	{
		egg_action_set_sensitive(action, TRUE);
		kz_bookmark_edit_clear(KZ_BOOKMARK_EDIT(editor->content_view));
	}
	else
	{
		egg_action_set_sensitive(action, FALSE);

#if 0
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_model_get(model, &iter,
				   COLUMN_BOOKMARK, &bookmark,
				   TERMINATOR);
		if (bookmark)
			kz_bookmark_edit_set(KZ_BOOKMARK_EDIT(editor->content_view),
					     bookmark);
#endif
	}
}


gboolean
cb_folder_view_drag_motion (GtkWidget *widget,
			    GdkDragContext *drag_context,
			    gint x, gint y, guint time,
			    KzBookmarkEditor *editor)
{
	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_BOOKMARK_EDITOR(editor), 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))
	{
		gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
	}
#if 0 /* FIXME */
	else if (!kz_bookmark_is_folder(src_bookmark))
	{
		gdk_drag_status(drag_context, 0, time);
		retval = TRUE;
	}
#endif
	else if ((bookmark == editor->root_folder) &&
		 (pos == GTK_TREE_VIEW_DROP_BEFORE ||
		  pos == GTK_TREE_VIEW_DROP_AFTER))
	{
		gdk_drag_status(drag_context, 0, time);
		retval = TRUE;
	}

	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,
		  KzBookmarkEditor *editor)
{
	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,
                       KzBookmarkEditor *editor)
{
	KzBookmarkEditor *src_editor;
	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);
	src_editor = g_object_get_data(G_OBJECT(src_widget),
				       "KzBookmarkEditor::KzBookmarkEditor");
	if (!src_editor || !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);

		break;
	default:
		break;
	}

	gtk_tree_path_free(src_path);
ERROR:
	gtk_tree_path_free(dest_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 *folder;

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

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

	return FALSE;
}


static GtkTreePath *
folder_view_find_row (KzBookmarkEditor *editor, KzBookmark *folder)
{
	GtkTreeModel *model;
	FindRow findrow;

	g_return_val_if_fail(KZ_IS_BOOKMARK_EDITOR(editor), NULL);
	if (!folder) return NULL;
	g_return_val_if_fail(KZ_IS_BOOKMARK(folder), NULL);
	g_return_val_if_fail(kz_bookmark_is_folder(folder), NULL);

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(editor->folder_view));
	findrow.bookmark = folder;
	findrow.path = NULL;
	gtk_tree_model_foreach(model, find_row_func, &findrow);

	return findrow.path;
}


static GtkTreePath *
bookmarks_view_find_row (KzBookmarkEditor *editor, KzBookmark *bookmark)
{
	GtkTreeModel *model;
	FindRow findrow;

	g_return_val_if_fail(KZ_IS_BOOKMARK_EDITOR(editor), NULL);
	if (!bookmark) return NULL;
	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	model = gtk_tree_view_get_model(GTK_TREE_VIEW(editor->bookmarks_view));
	findrow.bookmark = bookmark;
	findrow.path = NULL;
	gtk_tree_model_foreach(model, find_row_func, &findrow);

	return findrow.path;
}


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);
}


void
kz_bookmark_editor_folder_view_select(KzBookmarkEditor *editor,
				      KzBookmark *folder)
{
	GtkTreePath *path;

	path = folder_view_find_row (editor, folder);
	if (!path) return;

	expand_parent(GTK_TREE_VIEW(editor->folder_view), path);
	gtk_tree_view_set_cursor(GTK_TREE_VIEW(editor->folder_view),
				 path, NULL, FALSE);
	gtk_tree_path_free(path);
}


void
kz_bookmark_editor_bookmarks_view_select(KzBookmarkEditor *editor,
					 KzBookmark *bookmark)
{
	GtkTreePath *path;

	path = bookmarks_view_find_row (editor, bookmark);
	if (!path) return;

	expand_parent(GTK_TREE_VIEW(editor->bookmarks_view), path);
	gtk_tree_view_set_cursor(GTK_TREE_VIEW(editor->bookmarks_view),
				 path, NULL, FALSE);
	gtk_tree_path_free(path);
}


static void
create_selected_list_func (GtkTreeModel *model,
			   GtkTreePath *path, GtkTreeIter *iter,
			   gpointer data)
{
	GList **list = data;
	KzBookmark *bookmark;

	g_return_if_fail(list);

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

	*list = g_list_append(*list, bookmark);
}


GList *
kz_bookmark_editor_get_selected_list (KzBookmarkEditor *editor)
{
	GtkTreeView *treeview = GTK_TREE_VIEW(editor->bookmarks_view);
	GtkTreeSelection *selection;
	GList *list = NULL;

	g_return_val_if_fail(KZ_IS_BOOKMARK_EDITOR(editor), NULL);

	selection = gtk_tree_view_get_selection(treeview);
	gtk_tree_selection_selected_foreach(selection,
					    create_selected_list_func,
					    &list);

	return list;
}


static gboolean
cb_bookmarks_view_button_press (GtkWidget *widget, GdkEventButton *event,
				KzBookmarkEditor *editor)
{
	GtkWindow *kz;
	GtkTreeView *treeview = GTK_TREE_VIEW(widget);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreeIter iter;
	GtkTreePath *treepath = NULL;
	gboolean success;
	KzBookmark *bookmark = NULL;
	const gchar *uri = NULL;

	g_return_val_if_fail(KZ_IS_BOOKMARK_EDITOR(editor), FALSE);

	kz = gtk_window_get_transient_for(GTK_WINDOW(editor));
	if (!KZ_IS_WINDOW(kz))
	{
		GList *list = kz_window_get_window_list();
		if (list)
			kz = list->data;
		else
			return FALSE;
	}

	success = gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
						event->x, event->y,
						&treepath, NULL, NULL, NULL);
	if (success)
	{
		gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget),
					 treepath, NULL, FALSE);
		gtk_tree_model_get_iter(model, &iter, treepath);
		gtk_tree_path_free(treepath);
		gtk_tree_model_get(model, &iter,
				   COLUMN_BOOKMARK, &bookmark,
				   TERMINATOR);
		uri = kz_bookmark_get_link(bookmark);
	}

	if (success && event->type == GDK_2BUTTON_PRESS)
	{
		if (kz_bookmark_is_pure_folder(bookmark))
		{
			kz_bookmark_edit_clear(KZ_BOOKMARK_EDIT(editor->content_view));
			kz_bookmark_editor_set_bookmark_tree(editor, bookmark);
			kz_bookmark_editor_folder_view_select(editor, bookmark);
		}
		else if (kz_bookmark_is_separator(bookmark))
		{
		}
		else if (uri)
		{
			kz_window_open_new_tab(KZ_WINDOW(kz), uri);
		}
	}
	else if (event->button == 3)
	{
		kz_actions_bookmark_popup_menu_modal
			(editor, event->button, event->time);
		return TRUE;
	}

	return FALSE;
}


static gboolean
bookmarks_view_needs_refresh (KzBookmarkEditor *editor, KzBookmark *bookmark)
{
	KzBookmark *parent;

	if (!editor->current_folder)
		return FALSE;

	if (editor->current_folder == bookmark)
		return TRUE;

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

	for (parent = bookmark;
	     parent;
	     parent = kz_bookmark_get_parent(parent))
	{
		if (parent == editor->current_folder)
			return TRUE;
	}

	return FALSE;
}


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 void 
insert_bookmark(KzBookmarkEditor *editor,
		gboolean folder_view,
		KzBookmark *bookmark,
		KzBookmark *parent, KzBookmark *sibling)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter, tmp1, tmp2;
	GtkTreeIter *parent_iter = NULL, *sibling_iter = NULL;
	const gchar *title, *uri;
	GdkPixbuf *favicon;

	if (folder_view)
	{
		if (!kz_bookmark_is_folder(bookmark))
			return;
		treeview = GTK_TREE_VIEW(editor->folder_view);
	}
	else
	{
		treeview = GTK_TREE_VIEW(editor->bookmarks_view);
	}
	model = gtk_tree_view_get_model(treeview);

	/* get parent iter */
	if (parent)
	{
		if (folder_view)
			path = folder_view_find_row(editor, parent);
		else
			path = bookmarks_view_find_row(editor, 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_view)
		{
			sibling = find_folder_sibling(parent, sibling);
			path = folder_view_find_row(editor, sibling);
		}
		else
		{
			path = bookmarks_view_find_row(editor, 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_get_link(bookmark);

	if (kz_bookmark_is_folder(bookmark))
	{
		GtkWidget *image;
		image = gtk_image_new_from_stock(KZ_STOCK_FOLDER,
						 GTK_ICON_SIZE_MENU);
		favicon = gtk_widget_render_icon(image, KZ_STOCK_FOLDER,
						 GTK_ICON_SIZE_MENU,
						 NULL);
		gtk_widget_destroy(image);
	}
	else if (kz_bookmark_is_separator(bookmark))
		favicon = NULL;
	else
	{
		favicon = kz_favicon_get_pixbuf(editor->kz_favicon, uri,
						GTK_ICON_SIZE_MENU);
		
		if (!favicon)
		{
			GtkWidget *image;
			image = gtk_image_new_from_stock(GTK_STOCK_NEW,
							 GTK_ICON_SIZE_MENU);
			favicon = gtk_widget_render_icon(image, GTK_STOCK_NEW,
							 GTK_ICON_SIZE_MENU,
							 NULL);
			gtk_widget_destroy(image);
		}
	}

	/* 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_ICON,     favicon,
			   COLUMN_TITLE,    title,
			   COLUMN_BOOKMARK, bookmark,
			   TERMINATOR);
	if (favicon)
		g_object_unref(favicon);

	if (!folder_view)
		gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
				   COLUMN_URI, uri,
				   TERMINATOR);

	/* append children */
	if (kz_bookmark_is_folder(bookmark) &&
	    (folder_view ||
	     bookmarks_view_needs_refresh(editor, bookmark)))
	{
		GList *children, *node;

		children = kz_bookmark_get_children(bookmark);
		for (node = children; node; node = g_list_next(node))
		{
			insert_bookmark(editor, folder_view,
					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(treeview, path, FALSE);
		gtk_tree_path_free(path);
	}
}


static void 
remove_bookmark(KzBookmarkEditor *editor,
		gboolean folder_view,
		KzBookmark *bookmark)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path;
	GtkTreeIter iter;

	if (folder_view)
	{
		if (!kz_bookmark_is_folder(bookmark))
			return;
		treeview = GTK_TREE_VIEW(editor->folder_view);
	}
	else
	{
		treeview = GTK_TREE_VIEW(editor->bookmarks_view);
	}
	model = gtk_tree_view_get_model(treeview);

	/* get iter */
	if (bookmark)
	{
		if (folder_view)
			path = folder_view_find_row(editor, bookmark);
		else
			path = bookmarks_view_find_row(editor, 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 void
cb_bookmark_insert_child (KzBookmark *bookmark,
			  KzBookmark *child, KzBookmark *sibling,
			  KzBookmarkEditor *editor)
{
	connect_bookmark_signals (editor, child);

	/* add into folder view */
	if (kz_bookmark_is_folder(child))
		insert_bookmark(editor, TRUE, child, bookmark, sibling);
	if (bookmarks_view_needs_refresh(editor, bookmark))
		insert_bookmark(editor, FALSE, child, bookmark, sibling);
}


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 (KzBookmarkEditor *editor, KzBookmark *bookmark)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreePath *path = NULL;

	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	/* set folder view's cursor */
	treeview = GTK_TREE_VIEW(editor->folder_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_bookmark_editor_folder_view_select
					(editor, folder);
				return;
			}
		}
	}

	/* set bookmarks view's cursor */
	treeview = GTK_TREE_VIEW(editor->bookmarks_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_bookmark_editor_bookmarks_view_select
					(editor, selected);
		}
	}
}


static void
cb_bookmark_remove_child (KzBookmark *bookmark, KzBookmark *child,
			  KzBookmarkEditor *editor)
{
	disconnect_bookmark_signals (editor, child);

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

	/* remove from folder view */
	if (kz_bookmark_is_folder(child))
		remove_bookmark(editor, TRUE, child);
	if (bookmarks_view_needs_refresh(editor, bookmark))
		remove_bookmark(editor, FALSE, child);
}


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

	g_return_if_fail(KZ_IS_BOOKMARK_EDITOR(editor));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	title = kz_bookmark_get_title(bookmark);
	uri   = kz_bookmark_get_link(bookmark);

	treeview = GTK_TREE_VIEW(editor->folder_view);
	model = gtk_tree_view_get_model(treeview);
	if (kz_bookmark_is_folder(bookmark))
		path = folder_view_find_row(editor, bookmark);
	if (path)
	{
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
				   COLUMN_TITLE, title,
				   TERMINATOR);
		gtk_tree_path_free(path);
		path = NULL;
	}

	treeview = GTK_TREE_VIEW(editor->bookmarks_view);
	model = gtk_tree_view_get_model(treeview);
	path = bookmarks_view_find_row(editor, bookmark);
	if (path)
	{
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
				   COLUMN_TITLE, title,
				   COLUMN_URI,   uri,
				   TERMINATOR);
		gtk_tree_path_free(path);
		path = NULL;
	}
}


static void
cb_bookmark_notify (GObject *object, GParamSpec *pspec,
		    KzBookmarkEditor *editor)
{
	KzBookmark *bookmark;

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

	sync_bookmark_properties(editor, bookmark);
}


static void
connect_bookmark_signals (KzBookmarkEditor *editor,
			  KzBookmark *bookmark)
{
	GList *children, *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

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

	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 (editor, node->data);
}


static void
disconnect_bookmark_signals (KzBookmarkEditor *editor,
			     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),
		 editor);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_remove_child),
		 editor);
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_notify),
		 editor);

	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 (editor, node->data);
}
