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

/*
 * kz_dlist.c - Pair of reorderable stack list widget.
 * Copyright (C) 2001-2002 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 of the License, 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 3S30, Boston, MA 02111-1307, USA.
 *
 * $Id: kz-dlist.c 2644 2006-12-02 12:19:08Z ikezoe $
 */

#include "kz-dlist.h"

#include <string.h>
#include <stdlib.h>
#include <glib/gi18n.h>

#include "gtk-utils.h"

enum {
	AVAILABLE_LIST_UPDATED_SIGNAL,
	ENABLED_LIST_UPDATED_SIGNAL,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_TITLE1,
	PROP_TITLE2
};

enum {
	COLUMN_TERMINATOR = -1,
	COLUMN_LABEL,
	COLUMN_ID,
	COLUMN_INDEX
};

#define GET_ROW_NUM(widget) \
	(gtk_tree_model_iter_n_children(gtk_tree_view_get_model(GTK_TREE_VIEW(widget)), NULL))


/* object class functions */
static void       dispose                  (GObject      *object);
static void       set_property             (GObject      *object,
                                            guint         prop_id,
                                            const GValue *value,
                                            GParamSpec   *pspec);
static void       get_property             (GObject      *object,
                                            guint         prop_id,
                                            GValue       *value,
                                            GParamSpec   *pspec);

/* private functions */
static void       kz_dlist_available_list_updated   (KzDList   *dlist);
static void       kz_dlist_enabled_list_updated     (KzDList   *dlist);
static void       kz_dlist_set_sensitive            (KzDList   *dlist);
static GtkWidget *kz_dlist_create_list_widget       (KzDList   *dlist,
                                                     gboolean   reorderble);
static void       cb_add_button_pressed             (GtkButton *button,
						     gpointer   data);
static void       cb_del_button_pressed             (GtkButton *button,
						     gpointer   data);
static void       cb_up_button_pressed              (GtkButton *button,
						     gpointer   data);
static void       cb_down_button_pressed            (GtkButton *button,
						     gpointer   data);


static gint kz_dlist_signals[LAST_SIGNAL] = {0};

G_DEFINE_TYPE(KzDList, kz_dlist, GTK_TYPE_HBOX)

static void
kz_dlist_class_init (KzDListClass *klass)
{
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass *) klass;

	gobject_class->dispose      = dispose;
	gobject_class->set_property = set_property;
	gobject_class->get_property = get_property;

	g_object_class_install_property(
		gobject_class,
		PROP_TITLE1,
		g_param_spec_string(
			"title1",
			_("Title1"),
			_("The title used for left side tree view"),
			NULL,
			G_PARAM_READWRITE));

	g_object_class_install_property(
		gobject_class,
		PROP_TITLE2,
		g_param_spec_string(
			"title2",
			_("Title2"),
			_("The title used for right side tree view"),
			NULL,
			G_PARAM_READWRITE));

	kz_dlist_signals[AVAILABLE_LIST_UPDATED_SIGNAL]
		= g_signal_new("available-list-updated",
			       G_TYPE_FROM_CLASS(klass),
			       G_SIGNAL_RUN_FIRST,
			       G_STRUCT_OFFSET (KzDListClass, available_list_updated),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);

	kz_dlist_signals[ENABLED_LIST_UPDATED_SIGNAL]
		= g_signal_new("enabled-list-updated",
			       G_TYPE_FROM_CLASS(klass),
			       G_SIGNAL_RUN_FIRST,
			       G_STRUCT_OFFSET (KzDListClass, enabled_list_updated),
			       NULL, NULL,
			       g_cclosure_marshal_VOID__VOID,
			       G_TYPE_NONE, 0);
}


static void
kz_dlist_init (KzDList *dlist)
{
	GtkWidget *hbox = GTK_WIDGET(dlist);
	GtkWidget *vbox, *vbox1, *vbox2, *vbox3, *hseparator;
	GtkWidget *label, *scrollwin1, *scrollwin2, *clist, *button, *arrow;

	dlist->label1          = NULL;
	dlist->label2          = NULL;
	dlist->clist1          = NULL;
	dlist->clist2          = NULL;
	dlist->add_button      = NULL;
	dlist->del_button      = NULL;
	dlist->up_button       = NULL;
	dlist->down_button     = NULL;

	dlist->available_list  = NULL;

	/* possible columns */
	vbox1 = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox1, TRUE, TRUE, 0);
	gtk_widget_show(vbox1);

	label = gtk_label_new(NULL);
	dlist->label1 = label;
	gtk_box_pack_start(GTK_BOX(vbox1), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	scrollwin1 = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin1),
				       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin1),
					    GTK_SHADOW_IN);
	gtk_container_set_border_width(GTK_CONTAINER(scrollwin1), 5);
	gtk_box_pack_start(GTK_BOX(vbox1), scrollwin1, TRUE, TRUE, 0);
	gtk_widget_show(scrollwin1);

	clist = dlist->clist1 = kz_dlist_create_list_widget(dlist, FALSE);
	gtk_container_add(GTK_CONTAINER(scrollwin1), clist);
	gtk_widget_show(clist);


	/* add/delete buttons */
	vbox3 = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox3, FALSE, FALSE, 0);
	gtk_widget_show(vbox3);

	vbox = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start (GTK_BOX(vbox3), vbox, FALSE, FALSE, 0);
	gtk_widget_show(vbox);

	button = dlist->add_button = gtk_button_new();
	arrow = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2);
	gtk_widget_show(button);

	button = dlist->del_button = gtk_button_new();
	arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2);
	gtk_widget_show(button);

	hseparator = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX (vbox), hseparator, FALSE, FALSE, 2);
	gtk_widget_show(hseparator);


	/* move buttons */
	button = dlist->up_button = gtk_button_new();
	arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2);
	gtk_widget_show(button);

	button = dlist->down_button = gtk_button_new();
	arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2);
	gtk_widget_show(button);


	/* Use list */
	vbox2 = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0);
	gtk_widget_show(vbox2);

	label = gtk_label_new(NULL);
	dlist->label2 = label;
	gtk_box_pack_start (GTK_BOX(vbox2), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	scrollwin2 = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin2),
					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin2),
					    GTK_SHADOW_IN);
	gtk_container_set_border_width(GTK_CONTAINER(scrollwin2), 5);
	gtk_box_pack_start(GTK_BOX(vbox2), scrollwin2, TRUE, TRUE, 0);
	gtk_widget_show(scrollwin2);

	clist = dlist->clist2 = kz_dlist_create_list_widget(dlist, TRUE);
	gtk_container_add(GTK_CONTAINER(scrollwin2), clist);
	gtk_widget_show(clist);

	gtk_widget_set_size_request(dlist->add_button,  20, 20);
	gtk_widget_set_size_request(dlist->del_button,  20, 20);
	gtk_widget_set_size_request(dlist->up_button,   20, 20);
	gtk_widget_set_size_request(dlist->down_button, 20, 20);

	g_signal_connect(dlist->add_button, "clicked",
			 G_CALLBACK(cb_add_button_pressed),
			 dlist);
	g_signal_connect(dlist->del_button, "clicked",
			 G_CALLBACK(cb_del_button_pressed),
			 dlist);
	g_signal_connect(dlist->up_button, "clicked",
			 G_CALLBACK(cb_up_button_pressed),
			 dlist);
	g_signal_connect(dlist->down_button, "clicked",
			 G_CALLBACK(cb_down_button_pressed),
			 dlist);

	kz_dlist_set_sensitive(dlist);
}


static void
dispose (GObject *object)
{
	KzDList *dlist = KZ_DLIST(object);

	g_list_foreach(dlist->available_list, (GFunc) g_free, NULL);
	g_list_free(dlist->available_list);
	dlist->available_list = NULL;

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

static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
	KzDList *dlist = KZ_DLIST(object);

	switch (prop_id)
	{
	case PROP_TITLE1:
		gtk_label_set_text(GTK_LABEL(dlist->label1),
				   g_value_get_string(value));
		break;
	case PROP_TITLE2:
		gtk_label_set_text(GTK_LABEL(dlist->label2),
				   g_value_get_string(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
	KzDList *dlist = KZ_DLIST(object);
	const gchar *text;

	switch (prop_id)
	{
	case PROP_TITLE1:
		text = gtk_label_get_text(GTK_LABEL(dlist->label1));
		g_value_set_string(value, text);
		break;
	case PROP_TITLE2:
		text = gtk_label_get_text(GTK_LABEL(dlist->label2));
		g_value_set_string(value, text);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


GtkWidget *
kz_dlist_new (const gchar *title1,
	      const gchar *title2)
{
	KzDList *dlist = g_object_new (KZ_TYPE_DLIST,
				       "title1", title1,
				       "title2", title2,
				       NULL);

	return GTK_WIDGET(dlist);
}


gint
kz_dlist_append_available_item (KzDList *dlist, const gchar *label, const gchar *id)
{
	GtkWidget *clist = dlist->clist1;
	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(clist));
	GtkListStore *store = GTK_LIST_STORE(model);
	GtkTreeIter iter;
	gchar *text;
	gint idx;

	g_return_val_if_fail(KZ_IS_DLIST(dlist), -1);
	g_return_val_if_fail(label, -1);

	if (id)
		text = g_strdup(id);
	else
		text = g_strdup(label);

	dlist->available_list = g_list_append(dlist->available_list, text);
	idx = g_list_index(dlist->available_list, text);

	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter,
			   COLUMN_LABEL, label,
			   COLUMN_ID,    id,
			   COLUMN_INDEX, idx,
			   COLUMN_TERMINATOR);

	kz_dlist_set_sensitive(dlist);

	return idx;
}


void
kz_dlist_column_add (KzDList *dlist, gint idx)
{
	GtkTreeView *treeview1 = GTK_TREE_VIEW(dlist->clist1);
	GtkTreeView *treeview2 = GTK_TREE_VIEW(dlist->clist2);
	GtkTreeModel *model1 = gtk_tree_view_get_model(treeview1);
	GtkTreeModel *model2 = gtk_tree_view_get_model(treeview2);
	GtkTreeIter iter1, iter2, next;
	gboolean go_next;
	gchar *label = NULL, *id = NULL;
	gchar *text;
	GList *list;

	list = g_list_nth(dlist->available_list, idx);
	g_return_if_fail(list);
	text = list->data;
	g_return_if_fail(text);

	/* find row from clist1 */
	go_next = gtk_tree_model_get_iter_first(model1, &iter1);
	for (; go_next; go_next = gtk_tree_model_iter_next(model1, &iter1))
	{
		gtk_tree_model_get(model1, &iter1,
				   COLUMN_LABEL, &label,
				   COLUMN_ID,    &id,
				   COLUMN_TERMINATOR);
		if (id && !strcmp(text, id)) break;
		g_free(label);
		g_free(id);
		label = NULL;
		id    = NULL;
	}
	if (!id)
	{
		g_free(label);
		return;
	}

	/* append to clist2 */
	gtk_list_store_append(GTK_LIST_STORE(model2), &iter2);
	gtk_list_store_set(GTK_LIST_STORE(model2), &iter2,
			   COLUMN_LABEL, label,
			   COLUMN_ID,    id,
			   COLUMN_INDEX, idx,
			   COLUMN_TERMINATOR);

	/* set cursor of clist1 */
	next = iter1;
	if (gtk_tree_model_iter_next(model1, &next))
	{
		GtkTreePath *path = gtk_tree_model_get_path(model1, &next);
		gtk_tree_view_set_cursor(treeview1, path, NULL, FALSE);
		gtk_tree_path_free(path);
	}
	else
	{
		GtkTreePath *path = gtk_tree_model_get_path(model1, &iter1);
		if (gtk_tree_path_prev(path))
			gtk_tree_view_set_cursor(treeview1, path, NULL, FALSE);
		gtk_tree_path_free(path);
	}

	/* remove from clist1 */
	gtk_list_store_remove(GTK_LIST_STORE(model1), &iter1);

	/* clean :-) */
	g_free (label);
	g_free (id);

	g_signal_emit_by_name(treeview1, "cursor-changed");

	kz_dlist_available_list_updated(dlist);
	kz_dlist_enabled_list_updated(dlist);
}


void
kz_dlist_column_del (KzDList *dlist, gint idx)
{
	GtkTreeView *treeview1 = GTK_TREE_VIEW(dlist->clist1);
	GtkTreeView *treeview2 = GTK_TREE_VIEW(dlist->clist2);
	GtkTreeModel *model1 = gtk_tree_view_get_model(treeview1);
	GtkTreeModel *model2 = gtk_tree_view_get_model(treeview2);
	GtkTreeIter iter, iter1, iter2, next;
	gboolean go_next;
	gchar *label = NULL, *id = NULL;
	gchar *text;
	GList *list;

	list = g_list_nth(dlist->available_list, idx);
	g_return_if_fail(list);
	text = list->data;
	g_return_if_fail(text);

	/* find row from clist2 */
	go_next = gtk_tree_model_get_iter_first(model2, &iter2);
	for (; go_next; go_next = gtk_tree_model_iter_next(model2, &iter2))
	{
		gtk_tree_model_get(model2, &iter2,
				   COLUMN_LABEL, &label,
				   COLUMN_ID,    &id,
				   COLUMN_TERMINATOR);
		if (id && !strcmp(text, id)) break;
		g_free(label);
		g_free(id);
		label = NULL;
		id    = NULL;
	}
	if (!id)
	{
		g_free(label);
		return;
	}

	/* append to clist1 */
	go_next = gtk_tree_model_get_iter_first(model1, &iter1);
	for (; go_next; go_next = gtk_tree_model_iter_next(model1, &iter1))
	{
		gint idx1;
		gtk_tree_model_get(model1, &iter1,
				   COLUMN_INDEX, &idx1,
				   COLUMN_TERMINATOR);
		if (idx < idx1) break;
	}

	if (go_next)
		gtk_list_store_insert_before(GTK_LIST_STORE(model1), &iter, &iter1);
	else
		gtk_list_store_append(GTK_LIST_STORE(model1), &iter);

	gtk_list_store_set(GTK_LIST_STORE(model1), &iter,
			   COLUMN_LABEL, label,
			   COLUMN_ID,    id,
			   COLUMN_INDEX, idx,
			   COLUMN_TERMINATOR);

	/* set cursor of clist2 */
	next = iter2;
	if (gtk_tree_model_iter_next(model2, &next))
	{
		GtkTreePath *path = gtk_tree_model_get_path(model2, &next);
		gtk_tree_view_set_cursor(treeview2, path, NULL, FALSE);
		gtk_tree_path_free(path);
	}
	else
	{
		GtkTreePath *path = gtk_tree_model_get_path(model2, &iter2);
		if (gtk_tree_path_prev(path))
			gtk_tree_view_set_cursor(treeview2, path, NULL, FALSE);
		gtk_tree_path_free(path);
	}

	/* remove from clist2 */
	gtk_list_store_remove(GTK_LIST_STORE(model2), &iter2);

	/* clean :-) */
	g_free(label);
	g_free(id);

	g_signal_emit_by_name(treeview2, "cursor-changed");

	kz_dlist_available_list_updated(dlist);
	kz_dlist_enabled_list_updated(dlist);
}


void
kz_dlist_column_add_by_id (KzDList *dlist, const gchar *label)
{
	GList *list;
	gint j, idx = -1;

	g_return_if_fail(KZ_IS_DLIST (dlist));
	g_return_if_fail(label && *label);

	list = dlist->available_list;
	for (j = 0; list; j++, list = g_list_next (list))
	{
		if (!strcmp (label, (gchar *)list->data))
		{
			idx = j;
			break;
		}
	}
	if (idx >= 0)
		kz_dlist_column_add(dlist, idx);

	kz_dlist_set_sensitive(dlist);
}


gint
kz_dlist_get_n_available_items (KzDList  *dlist)
{
	g_return_val_if_fail(KZ_IS_DLIST(dlist), 0);
	return GET_ROW_NUM(dlist->clist1);
}


gint
kz_dlist_get_n_enabled_items (KzDList  *dlist)
{
	g_return_val_if_fail(KZ_IS_DLIST(dlist), 0);
	return GET_ROW_NUM(dlist->clist2);
}


static gchar *
kz_dlist_get_row (KzDList *dlist, gint row,
		  gboolean is_available, gboolean is_label)
{
	GtkTreeView *treeview;
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean success;
	gchar *text;
	gint col = is_label ? COLUMN_LABEL : COLUMN_ID;

	g_return_val_if_fail(KZ_IS_DLIST(dlist), NULL);

	treeview = GTK_TREE_VIEW(is_available ? dlist->clist1 : dlist->clist2);
	model = gtk_tree_view_get_model(treeview);

	if (row < 0 && row >= GET_ROW_NUM(treeview)) return NULL;

	success = gtk_tree_model_iter_nth_child(model, &iter, NULL, row);
	if (!success) return NULL;

	gtk_tree_model_get(model, &iter,
			   col, &text,
			   COLUMN_TERMINATOR);

	return text;
}


gchar *
kz_dlist_get_available_id (KzDList *dlist, gint row)
{
	return kz_dlist_get_row(dlist, row, TRUE, FALSE);
}


gchar *
kz_dlist_get_available_label (KzDList *dlist, gint row)
{
	return kz_dlist_get_row(dlist, row, TRUE, TRUE);
}


gchar *
kz_dlist_get_enabled_id (KzDList *dlist, gint row)
{
	return kz_dlist_get_row(dlist, row, FALSE, FALSE);
}


gchar *
kz_dlist_get_enabled_label (KzDList *dlist, gint row)
{
	return kz_dlist_get_row(dlist, row, FALSE, TRUE);
}



/*******************************************************************************
 *
 *  Callback functions for child widget.
 *
 *******************************************************************************/
static void
cb_cursor_changed (GtkTreeView *treeview, gpointer data)
{
	KzDList *dlist = data;

	g_return_if_fail(treeview);
	g_return_if_fail(dlist);

	kz_dlist_set_sensitive(dlist);
}


static void
cb_row_changed (GtkTreeModel *model,
		GtkTreePath *treepath,
		GtkTreeIter *iter,
		gpointer data)
{
	KzDList *dlist = data;

	kz_dlist_enabled_list_updated(dlist);

	kz_dlist_set_sensitive(dlist);
}


static void
cb_row_deleted (GtkTreeModel *model,
		GtkTreePath *treepath,
		gpointer data)
{
	KzDList *dlist = data;

	kz_dlist_enabled_list_updated(dlist);
	kz_dlist_set_sensitive(dlist);
}


static void
cb_add_button_pressed (GtkButton *button, gpointer data)
{
	KzDList *dlist = data;
	GtkTreeView *treeview = GTK_TREE_VIEW(dlist->clist1);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	gboolean success;
	gint idx;

	selection = gtk_tree_view_get_selection(treeview);
	success = gtk_tree_selection_get_selected(selection, &model, &iter);
	if (!success) return;

	gtk_tree_model_get(model, &iter,
			   COLUMN_INDEX, &idx,
			   COLUMN_TERMINATOR);

	kz_dlist_column_add(dlist, idx);
}


static void
cb_del_button_pressed (GtkButton *button, gpointer data)
{
	KzDList *dlist = data;
	GtkTreeView *treeview = GTK_TREE_VIEW(dlist->clist2);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	gboolean success;
	gint idx;

	selection = gtk_tree_view_get_selection(treeview);
	success = gtk_tree_selection_get_selected(selection, &model, &iter);
	if (!success) return;

	gtk_tree_model_get(model, &iter,
			   COLUMN_INDEX, &idx,
			   COLUMN_TERMINATOR);

	kz_dlist_column_del(dlist, idx);
}


static void
cb_up_button_pressed (GtkButton *button, gpointer data)
{
	KzDList *dlist = data;
	GtkTreeView *treeview = GTK_TREE_VIEW(dlist->clist2);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
	GtkTreeIter iter, prev_iter;
	GtkTreePath *treepath;
	gboolean success;

	/* get src row */
	selection = gtk_tree_view_get_selection(treeview);
	success = gtk_tree_selection_get_selected(selection, &model, &iter);
	if (!success) return;
	treepath = gtk_tree_model_get_path(model, &iter);

	/* get prev row */
	success = gtk_tree_path_prev(treepath);
	if (!success)
	{
		gtk_tree_path_free(treepath);
		return;
	}
	gtk_tree_model_get_iter(model, &prev_iter, treepath);
	gtk_tree_path_free(treepath);

	gtk_list_store_swap(GTK_LIST_STORE(model), &iter, &prev_iter);

	kz_dlist_enabled_list_updated(dlist);
}


static void
cb_down_button_pressed (GtkButton *button, gpointer data)
{
	KzDList *dlist = data;
	GtkTreeView *treeview = GTK_TREE_VIEW(dlist->clist2);
	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
	GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
	GtkTreeIter iter, next_iter;
	gboolean success;

	/* get src row */
	selection = gtk_tree_view_get_selection(treeview);
	success = gtk_tree_selection_get_selected(selection, &model, &iter);
	if (!success) return;

	/* get next row */
	next_iter = iter;
	success = gtk_tree_model_iter_next(model, &next_iter);
	if (!success) return;

	gtk_list_store_swap(GTK_LIST_STORE(model), &iter, &next_iter);

	kz_dlist_enabled_list_updated(dlist);
}



/*******************************************************************************
 *
 *  Private functions.
 *
 *******************************************************************************/
static void
kz_dlist_available_list_updated (KzDList *dlist)
{
	g_return_if_fail(KZ_IS_DLIST(dlist));

	g_signal_emit(dlist,
		      kz_dlist_signals[AVAILABLE_LIST_UPDATED_SIGNAL], 0);

	kz_dlist_set_sensitive(dlist);
}


static void
kz_dlist_enabled_list_updated (KzDList *dlist)
{
	g_return_if_fail(KZ_IS_DLIST(dlist));

	g_signal_emit(dlist,
		      kz_dlist_signals[ENABLED_LIST_UPDATED_SIGNAL], 0);

	kz_dlist_set_sensitive(dlist);
}


static gint
get_selected_row (GtkTreeView *treeview)
{
	GtkTreeSelection *selection;
	GtkTreeIter iter;
	GtkTreeModel *model;
	gboolean success;

	selection = gtk_tree_view_get_selection(treeview);
	success = gtk_tree_selection_get_selected(selection, &model, &iter);
	if (success)
	{
		GtkTreePath *treepath = gtk_tree_model_get_path(model, &iter);
		gchar *path = gtk_tree_path_to_string(treepath);
		gint selected;

		selected = atoi(path);
		gtk_tree_path_free(treepath);
		g_free(path);

		return selected;
	}

	return -1;
}


static void
kz_dlist_set_sensitive (KzDList *dlist)
{
	gint selected1 = get_selected_row(GTK_TREE_VIEW(dlist->clist1));
	gint selected2 = get_selected_row(GTK_TREE_VIEW(dlist->clist2));

	/* add button */
	if (selected1 < 0 || selected1 >= GET_ROW_NUM(dlist->clist1))
		gtk_widget_set_sensitive(dlist->add_button, FALSE);
	else
		gtk_widget_set_sensitive(dlist->add_button, TRUE);

	/* delete button */
	if (selected2 < 0 || selected2 >= GET_ROW_NUM(dlist->clist2))
		gtk_widget_set_sensitive(dlist->del_button, FALSE);
	else
		gtk_widget_set_sensitive(dlist->del_button, TRUE);

	/* up button */
	if (selected2 > 0 && selected2 < GET_ROW_NUM(dlist->clist2))
		gtk_widget_set_sensitive(dlist->up_button, TRUE);
	else
		gtk_widget_set_sensitive(dlist->up_button, FALSE);

	/* down button */
	if (selected2 >= 0 && selected2 < GET_ROW_NUM(dlist->clist2) - 1)
		gtk_widget_set_sensitive(dlist->down_button, TRUE);
	else
		gtk_widget_set_sensitive(dlist->down_button, FALSE);
}


static GtkWidget *
kz_dlist_create_list_widget (KzDList *dlist, gboolean reorderble)
{
	GtkWidget *clist;

	GtkListStore *store;
	GtkTreeViewColumn *col;
	GtkCellRenderer *render;

	store = gtk_list_store_new(3,
				   G_TYPE_STRING,
				   G_TYPE_STRING,
				   G_TYPE_INT);
	clist = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	dlist->clist2 = clist;
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(clist), TRUE);
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(clist), FALSE);

	if (reorderble)
		gtk_tree_view_set_reorderable(GTK_TREE_VIEW(clist), TRUE);

	g_signal_connect(store, "row_changed",
			 G_CALLBACK(cb_row_changed),
			 dlist);
	g_signal_connect(store, "row_deleted",
			 G_CALLBACK(cb_row_deleted),
			 dlist);
	g_signal_connect(clist,"cursor_changed",
			 G_CALLBACK(cb_cursor_changed),
			 dlist);

	/* set column */
	col = gtk_tree_view_column_new();
	render = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(col, render, TRUE);
	gtk_tree_view_column_add_attribute(col, render, "text", 0);
	gtk_tree_view_append_column(GTK_TREE_VIEW(clist), col);

	g_object_unref(store);
	return clist;
}
