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

/*
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "prefs_gesture.h"

#include <ctype.h>
#include <glib/gi18n.h>
#include "kazehakase.h"
#include "kz-window.h"
#include "kz-gesture.h"

#define DATA_KEY "KzPrefsGesture::info"

enum
{
	TERMINATOR = -1,
	COLUMN_ACTION,
	COLUMN_GESTURE,
	N_COLUMNS
};


typedef struct _KzPrefsGesture
{
	GtkWidget    *main_vbox;
	GtkWidget    *use_gesture_check;

	GtkWidget    *treeview;
	GtkTreeStore *treestore;

	GtkWidget    *edit_area;
	GtkWidget    *up, *down, *left, *right, *bs;
	GtkWidget    *entry;

	KzGesture    *gesture;
} KzPrefsGesture;


static GtkWidget *prefs_gesture_create   (void);
static void       prefs_gesture_response (GtkWidget *widget,
					  gint       response);

static KzPrefsWinPageEntry prefs_entry =
{
	/* .path          = */ N_("/Gesture"),
	/* .priority_hint = */ 0,
	/* .ui_level      = */ ~KZ_UI_LEVEL_BEGINNER,
	/* .create        = */ prefs_gesture_create,
	/* .response      = */ prefs_gesture_response,
};


static void
prefs_gesture_destroy (gpointer data)
{
	KzPrefsGesture *self = data;
	
	if (self->treestore)
	{
		g_object_unref(self->treestore);
		self->treestore = NULL;
	}

	g_free(self);
}


static gboolean
valid_char (gint c)
{
	gint valid[] = {
		'U', 'D', 'L', 'R',
	};
	gint src;
	guint i;

	src = toupper(c);

	for (i = 0; i < G_N_ELEMENTS(valid); i++)
	{
		if (valid[i] == toupper(c))
			return TRUE;
	}

	return FALSE;
}


static gint
get_last_char (KzPrefsGesture *self)
{
	const gchar *text = gtk_entry_get_text(GTK_ENTRY(self->entry));
	gsize len;
	gint i;

	g_return_val_if_fail(text, 0);

	len = strlen(text);
	if (len <= 0) return 0;

	/* FIXME!: validate the character? */
	for (i = len - 1; len > 0 && i >= 0; i--)
		if (!isspace(text[i])) return text[i];

	return 0;
}


static void
reduce_motion_str (gchar *str)
{
	gint i, len;

	g_return_if_fail(str);

	len = strlen(str);

	for (i = 0; i < len; i++)
	{
		if (!valid_char(str[i]))
		{
			memmove(str + i, str + i + 1, (len - i + 1));
			len--;
			i--;
		}
	}
}


static void
append_motion (KzPrefsGesture *self, gint motion)
{
	const gchar *text = gtk_entry_get_text(GTK_ENTRY(self->entry));
	gchar *newstr;
	gsize size, len;
	gint c = 0, i;

	g_return_if_fail(text);

	len = strlen(text);
	size = strlen(text) + 2;

	for (i = len - 1; len > 0 && i >= 0; i--)
	{
		if (!isspace(text[i]))
		{
			c = text[i];
			break;
		}
	}

	if (g_ascii_toupper(c) == motion)
		return;

	newstr = g_alloca(size);
	g_return_if_fail(newstr);

	memcpy(newstr, text, size - 2);
	newstr[size - 2] = motion;
	newstr[size - 1] = '\0';

	gtk_entry_set_text(GTK_ENTRY(self->entry), newstr);
}


static void
remove_last_motion (KzPrefsGesture *self)
{
	const gchar *text = gtk_entry_get_text(GTK_ENTRY(self->entry));
	gchar *newstr;
	gsize size, len;
	gint i;

	g_return_if_fail(text);

	len = strlen(text);
	for (i = len - 1; len > 0 && i >= 0; i--)
		if (!isspace(text[i])) break;

	if (i > 0)
		size = i + 1;
	else
		size = 1;

	newstr = g_alloca(size);
	g_return_if_fail(newstr);

	memcpy(newstr, text, size - 1);
	newstr[size - 1] = '\0';

	gtk_entry_set_text(GTK_ENTRY(self->entry), newstr);
}


static void
reset_gesture_items (KzPrefsGesture *self)
{
	GtkWidget *top;
	KzWindow *kz;
	GList *list, *node;
	GtkActionGroup *actions;

	g_return_if_fail(self);

	top = gtk_widget_get_toplevel(self->main_vbox);
	if (!GTK_IS_WINDOW(top)) return;

	kz = KZ_WINDOW(gtk_window_get_transient_for(GTK_WINDOW(top)));
	if (!KZ_IS_WINDOW(kz)) return;

	/* clear */
	gtk_tree_store_clear(self->treestore);

	/* set new list */
	actions = kz->actions;
	list = gtk_action_group_list_actions(actions);

	for (node = list; node; node = g_list_next(node))
	{
		GtkAction *action = node->data;
		const gchar *name = gtk_action_get_name(action);
		gchar *gesture;
		GtkTreeIter iter;

		gesture = KZ_CONF_GET_STR("Gesture", name);
		if (gesture)
			reduce_motion_str(gesture);

		gtk_tree_store_append(self->treestore, &iter, NULL);
		gtk_tree_store_set(self->treestore, &iter,
				   COLUMN_ACTION,  name,
				   COLUMN_GESTURE, gesture,
				   TERMINATOR);

		g_free(gesture);
	}

	g_list_free(list);
}


static void
cb_realize (GtkWidget *widget, KzPrefsGesture *self)
{
	reset_gesture_items(self);
	g_signal_handlers_disconnect_by_func(widget,
					     G_CALLBACK(cb_realize), self);
}


static void
cb_selection_changed (GtkTreeSelection *selection, KzPrefsGesture *self)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gchar *gesture = NULL;

	g_return_if_fail(self);

	if (!gtk_tree_selection_get_selected(selection, &model, &iter))
	{
		gtk_widget_set_sensitive(self->edit_area, FALSE);
		gtk_entry_set_text(GTK_ENTRY(self->entry), "");
		return;
	}

	gtk_widget_set_sensitive(self->edit_area, TRUE);

	gtk_tree_model_get(model, &iter,
			   COLUMN_GESTURE, &gesture,
			   TERMINATOR);
	if (!gesture)
		gesture = g_strdup("");

	gtk_entry_set_text(GTK_ENTRY(self->entry), gesture);

	g_free(gesture);
}


static void
set_sensitive_edit_buttons (KzPrefsGesture *self)
{
	const gchar *text = gtk_entry_get_text(GTK_ENTRY(self->entry));
	gsize len;
	gint c = 0;

	g_return_if_fail(text);

	gtk_widget_set_sensitive(self->up,    TRUE);
	gtk_widget_set_sensitive(self->down,  TRUE);
	gtk_widget_set_sensitive(self->left,  TRUE);
	gtk_widget_set_sensitive(self->right, TRUE);
	gtk_widget_set_sensitive(self->bs,    TRUE);

	len = strlen(text);
	if (len <= 0)
	{
		gtk_widget_set_sensitive(self->bs, FALSE);
		return;
	}

	c = get_last_char(self);

	switch (c)
	{
	case 'U':
		gtk_widget_set_sensitive(self->up, FALSE);
		break;
	case 'D':
		gtk_widget_set_sensitive(self->down, FALSE);
		break;
	case 'L':
		gtk_widget_set_sensitive(self->left, FALSE);
		break;
	case 'R':
		gtk_widget_set_sensitive(self->right, FALSE);
		break;
	default:
		break;
	}
}


static void
cb_up_clicked (GtkButton *button, KzPrefsGesture *self)
{
	append_motion(self, 'U');
}


static void
cb_down_clicked (GtkButton *button, KzPrefsGesture *self)
{
	append_motion(self, 'D');
}


static void
cb_left_clicked (GtkButton *button, KzPrefsGesture *self)
{
	append_motion(self, 'L');
}


static void
cb_right_clicked (GtkButton *button, KzPrefsGesture *self)
{
	append_motion(self, 'R');
}


static void
cb_bs_clicked (GtkButton *button, KzPrefsGesture *self)
{
	remove_last_motion(self);
}


static gboolean
cb_motion_notify (GtkWidget *widget,
		  GdkEventMotion *event,
		  KzPrefsGesture *self)
{
	gint x, y;

	g_return_val_if_fail(self, FALSE);

	gtk_widget_get_pointer (GTK_WIDGET(widget), &x, &y);

	if (kz_gesture_is_started(self->gesture))
	{
		kz_gesture_update_position(self->gesture, x, y);
	}

	return FALSE;
}


static gboolean
cb_button_release (GtkWidget *widget,
		   GdkEventButton *event,
		   KzPrefsGesture *self)
{
        if (gdk_pointer_is_grabbed ())
        {
                gdk_pointer_ungrab (gtk_get_current_event_time ());
        }
	g_signal_handlers_disconnect_by_func(widget,
					     G_CALLBACK(cb_button_release),
					     self);
	g_signal_handlers_disconnect_by_func(widget,
					     G_CALLBACK(cb_motion_notify),
					     self);

	g_object_unref(self->gesture);
	self->gesture = NULL;

	return FALSE;
}


static void
cb_gesture_stack_motion (KzGesture *gesture, KzGestureMotion motion,
			 KzPrefsGesture *self)
{
	gchar buf[256];

	kz_gesture_create_gesture_string(gesture, buf, G_N_ELEMENTS(buf));

	gtk_entry_set_text(GTK_ENTRY(self->entry), buf);
}


static gboolean
cb_button_press (GtkButton *button, GdkEventButton *event,
		 KzPrefsGesture *self)
{
	if (event->button == 3)
	{
		static GdkCursor *cursor;
		GtkWidget *top = gtk_widget_get_toplevel(GTK_WIDGET(button));
		gint x, y;

		gtk_widget_get_pointer (GTK_WIDGET(top), &x, &y);

		self->gesture = kz_gesture_new();
		g_signal_connect(self->gesture, "stack_motion",
				 G_CALLBACK(cb_gesture_stack_motion), self);
		kz_gesture_start(self->gesture, 0, x, y);

		if (!cursor) cursor = gdk_cursor_new (GDK_HAND1);
		gdk_pointer_grab (top->window,
				  FALSE,
				  GDK_POINTER_MOTION_MASK |
				  GDK_BUTTON_RELEASE_MASK |
				  GDK_BUTTON_PRESS_MASK,
				  NULL, cursor, gtk_get_current_event_time ());
		g_signal_connect(top, "button-release-event",
				 G_CALLBACK(cb_button_release), self);
		g_signal_connect(top, "motion-notify-event",
				 G_CALLBACK(cb_motion_notify), self);

		return TRUE;
	}
	return FALSE;
}


static void
cb_insert_text (GtkEditable *editable,
		const gchar *text,
		gint length,
		gint *position,
		KzPrefsGesture *self)
{
	int i, j = 0, last = 0;
	gchar *result = g_alloca(sizeof(gchar) * length + 1);

	result[0] = '\0';

	last = get_last_char(self);

	for (i = 0; i < length; i++)
	{
		gint c = toupper(text[i]);

		if (valid_char(c) && (!last || last != c))
		{
			result[j++] = c;
			last = c;
		}
	}

	result[j] = '\0';
	length = j;

	if (length > 0)
	{
		g_signal_handlers_block_by_func(editable, 
						G_CALLBACK(cb_insert_text),
						self);
		gtk_editable_insert_text(editable, result, length, position);
		g_signal_handlers_unblock_by_func(editable, 
						  G_CALLBACK(cb_insert_text),
						  self);
	}

	g_signal_stop_emission_by_name (editable, "insert_text"); 
}


static void
cb_entry_changed (GtkEntry *entry, KzPrefsGesture *self)
{
	g_return_if_fail(self);

	set_sensitive_edit_buttons(self);
}


static void
cb_use_gesture_toggled(GtkToggleButton *button, KzPrefsGesture *self)
{
	gtk_widget_set_sensitive(self->treeview,
				 gtk_toggle_button_get_active(button));
	gtk_widget_set_sensitive(self->edit_area,
				 gtk_toggle_button_get_active(button));
}

typedef struct _CheckDupl
{
	KzPrefsGesture *self;
	const gchar *action;
	const gchar *gesture;
	gboolean valid;
} CheckDupl;


static void
cb_response (GtkDialog *dialog, gint response, CheckDupl *dupl)
{
	if (response == GTK_RESPONSE_NO)
		dupl->valid = FALSE;
}


static gboolean
check_dupl_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
		 gpointer data)
{
	CheckDupl *dupl = data;

	gchar *action = NULL, *gesture = NULL;

	gtk_tree_model_get(model, iter,
			   COLUMN_ACTION,  &action,
			   COLUMN_GESTURE, &gesture,
			   TERMINATOR);

	if (action && dupl->action && strcmp(action, dupl->action) &&
	    gesture && *gesture && dupl->gesture && !strcmp(gesture, dupl->gesture))
	{
		GtkWidget *top = gtk_widget_get_toplevel(dupl->self->main_vbox);
		GtkWidget *dialog;

		dialog = gtk_message_dialog_new
				(GTK_WINDOW(top),
				 GTK_DIALOG_DESTROY_WITH_PARENT,
				 GTK_MESSAGE_QUESTION,
				 GTK_BUTTONS_YES_NO,
				 _("Specified gesture is duplicated with \"%s\" action. "
				   "Replace?"), action);
		g_signal_connect(dialog, "response",
				 G_CALLBACK(cb_response), dupl);
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);

		if (dupl->valid)
			gtk_tree_store_set(GTK_TREE_STORE(model), iter,
					   COLUMN_GESTURE, "",
					   TERMINATOR);
		else
			return TRUE;
	}

	g_free(gesture);
	g_free(action);

	return FALSE;
}


static void
cb_apply_button_clicked (GtkButton *button, KzPrefsGesture *self)
{
	const gchar *str = gtk_entry_get_text(GTK_ENTRY(self->entry));
	const gchar *action;
	gchar *old_gesture = NULL, *new_gesture = g_strdup(str);
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	CheckDupl dupl;

	g_return_if_fail(self);

	reduce_motion_str(new_gesture);

	selection= gtk_tree_view_get_selection(GTK_TREE_VIEW(self->treeview));
	if (!gtk_tree_selection_get_selected(selection, &model, &iter)) return;

	gtk_tree_model_get(model, &iter,
			   COLUMN_ACTION,  &action,
			   COLUMN_GESTURE, &old_gesture,
			   TERMINATOR);

	if (old_gesture)
	{
		reduce_motion_str(old_gesture);
		if (!strcmp(old_gesture, new_gesture))
			goto ERROR;
	}

	dupl.self    = self;
	dupl.action  = action;
	dupl.gesture = new_gesture;
	dupl.valid   = TRUE;

	gtk_tree_model_foreach(model, check_dupl_func, &dupl);

	if (dupl.valid)
		gtk_tree_store_set(self->treestore, &iter,
				   COLUMN_GESTURE, new_gesture,
				   TERMINATOR);

ERROR:
	g_free(old_gesture);
	g_free(new_gesture);
}


static void
cb_clear_button_clicked (GtkButton *button, KzPrefsGesture *self)
{
	g_return_if_fail(self);

	gtk_entry_set_text(GTK_ENTRY(self->entry), "");
}


static GtkWidget *
prefs_gesture_create (void)
{
	KzPrefsGesture *self = g_new0(KzPrefsGesture, 1);
	GtkWidget *main_vbox, *swin;
	GtkWidget *label, *check, *frame, *frame_vbox;
	GtkTreeModel *model;
	GtkTreeView *treeview;
	GtkTreeSortable *sortable;
	GtkTreeViewColumn *col;
	GtkCellRenderer *renderer;
	GtkWidget *table, *hbox, *vbox, *button, *arrow, *entry/*, *ebox */;
	gboolean use_gesture = TRUE;

	self->gesture = NULL;


	main_vbox = gtk_vbox_new(FALSE, 0);
	self->main_vbox = main_vbox;
	g_object_set_data_full(G_OBJECT(main_vbox), DATA_KEY,
			       self, (GDestroyNotify) prefs_gesture_destroy);
	g_signal_connect(main_vbox, "realize",
			 G_CALLBACK(cb_realize), self);

	label = kz_prefs_ui_utils_create_title(_("Gesture"));
	gtk_box_pack_start(GTK_BOX(main_vbox), label,
			   FALSE, FALSE, 0);
	gtk_widget_show(label);

	KZ_CONF_GET("Gesture", "use_gesture", use_gesture, BOOL);
	self->use_gesture_check = check =
		gtk_check_button_new_with_label(_("Use gestures"));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check),
				     TRUE);
	g_signal_connect(check, "toggled",
			 G_CALLBACK(cb_use_gesture_toggled), self);
	frame = gtk_frame_new("");
	gtk_frame_set_label_widget(GTK_FRAME(frame), check);
	gtk_frame_set_label_align(GTK_FRAME(frame), 0.03, 0.5);
	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
	gtk_box_pack_start(GTK_BOX(main_vbox), frame, TRUE, TRUE, 2);
	gtk_widget_show(frame);
	gtk_widget_show(check);

	frame_vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(frame_vbox), 4);
	gtk_container_add(GTK_CONTAINER(frame), frame_vbox);
	gtk_widget_show(frame_vbox);

	/*
	 *  Tree view
	 */
	self->treestore = gtk_tree_store_new(3,
					     G_TYPE_STRING,
					     G_TYPE_STRING,
					     G_TYPE_STRING);
	model = GTK_TREE_MODEL(self->treestore);
	sortable = GTK_TREE_SORTABLE(self->treestore);
	gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING);
	reset_gesture_items (self);


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

	self->treeview = gtk_tree_view_new_with_model(model);
	treeview = GTK_TREE_VIEW(self->treeview);
	gtk_tree_view_set_rules_hint(treeview, TRUE);
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(treeview),
				    GTK_SELECTION_BROWSE);
	g_signal_connect(gtk_tree_view_get_selection(treeview), "changed",
			 G_CALLBACK(cb_selection_changed), self);

	renderer = gtk_cell_renderer_text_new();
	g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
	gtk_tree_view_insert_column_with_attributes(treeview, -1,
						    _("Action"), renderer,
						    "text", COLUMN_ACTION,
						    NULL);
	col = gtk_tree_view_get_column(treeview, 0);
	gtk_tree_view_column_set_sort_column_id(col, 0);
	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width (col, 200);
	gtk_tree_view_column_set_resizable(col, TRUE);

	renderer = gtk_cell_renderer_text_new();
	g_object_set (G_OBJECT (renderer), "xalign", 0.0, NULL);
	gtk_tree_view_insert_column_with_attributes(treeview, -1,
						    _("Gesture"), renderer,
						    "text", COLUMN_GESTURE,
						    NULL);
	col = gtk_tree_view_get_column(treeview, 1);
	gtk_tree_view_column_set_sort_column_id(col, 1);
	gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_resizable(col, TRUE);

	gtk_container_add(GTK_CONTAINER(swin), self->treeview);
	gtk_widget_show(self->treeview);


	/*
	 *  Edit area
	 */
	hbox = gtk_hbox_new(FALSE, 0);
	self->edit_area = hbox;
	gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
	gtk_box_pack_start(GTK_BOX(frame_vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

#if 0
	ebox = kz_input_event_box_new();
	gtk_box_pack_start(GTK_BOX(hbox), ebox, FALSE, FALSE, 2);
	g_signal_connect(ebox, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(ebox);
#endif

	table = gtk_table_new(3, 3, TRUE);
	gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 2);
	/*gtk_container_add(GTK_CONTAINER(ebox), table);*/
	gtk_widget_show(table);

	/* up button */
	button = gtk_button_new();
	self->up = button;
	arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_table_attach(GTK_TABLE(table), button,
			 1, 2, 0, 1,
			 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
			 2, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_up_clicked), self);
	g_signal_connect(button, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(button);

	/* left button */
	button = gtk_button_new();
	self->left = button;
	arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_table_attach(GTK_TABLE(table), button,
			 0, 1, 1, 2,
			 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
			 2, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_left_clicked), self);
	g_signal_connect(button, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(button);

	/* back space button */
	button = gtk_button_new();
	self->bs = button;
	label = gtk_label_new_with_mnemonic("_BS");
	gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
	gtk_container_add(GTK_CONTAINER(button), label);
	gtk_widget_show(label);
	gtk_table_attach(GTK_TABLE(table), button,
			 1, 2, 1, 2,
			 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
			 2, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_bs_clicked), self);
	g_signal_connect(button, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(button);

	/* right button */
	button = gtk_button_new();
	self->right = button;
	arrow = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_table_attach(GTK_TABLE(table), button,
			 2, 3, 1, 2,
			 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
			 2, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_right_clicked), self);
	g_signal_connect(button, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(button);

	/* down button */
	button = gtk_button_new();
	self->down = button;
	arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
	gtk_container_add(GTK_CONTAINER(button), arrow);
	gtk_widget_show(arrow);
	gtk_table_attach(GTK_TABLE(table), button,
			 1, 2, 2, 3,
			 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
			 2, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_down_clicked), self);
	g_signal_connect(button, "button-press-event",
			 G_CALLBACK(cb_button_press), self);
	gtk_widget_show(button);

	/* right side of edit area */
	vbox = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 2);
	gtk_widget_show(vbox);

	/* description */
	label = gtk_label_new(_("Press right mouse button on edit button "
				"to start to caputure gesture sequence."));
	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
	gtk_box_pack_start(GTK_BOX(vbox), label,
			   FALSE, FALSE, 5);
	gtk_widget_show(label);

	/* button box */
	hbox = gtk_hbutton_box_new();
	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);
	gtk_widget_show(hbox);

	button = gtk_button_new_from_stock(GTK_STOCK_APPLY);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_apply_button_clicked), self);
	gtk_widget_show(button);

	button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 2);
	g_signal_connect(button, "clicked",
			 G_CALLBACK(cb_clear_button_clicked), self);
	gtk_widget_show(button);

	/* entry */
	entry = gtk_entry_new();
	self->entry = entry;
	gtk_box_pack_end(GTK_BOX(vbox), entry, FALSE, FALSE, 2);
	g_signal_connect(entry, "insert-text",
			 G_CALLBACK(cb_insert_text), self);
	g_signal_connect(entry, "changed",
			 G_CALLBACK(cb_entry_changed), self);
	gtk_widget_show(entry);

	gtk_widget_set_sensitive(self->edit_area, FALSE);

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check),
				     use_gesture);
	return main_vbox;
}


static gboolean
apply_config_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
		   gpointer data)
{
	gchar *action = NULL, *gesture = NULL, *old_gesture;

	gtk_tree_model_get(model, iter,
			   COLUMN_ACTION,  &action,
			   COLUMN_GESTURE, &gesture,
			   TERMINATOR);

	old_gesture = KZ_CONF_GET_STR("Gesture", action);

	/* didn't change gestures */
	if ((!old_gesture || !*old_gesture) && (!gesture || !*gesture))
		goto ERROR;
	if (old_gesture && *old_gesture && !strcmp(gesture, old_gesture))
		goto ERROR;

	/* apply */
	if (!gesture)
		gesture = g_strdup("");
	KZ_CONF_SET_STR("Gesture", action, gesture);

ERROR:
	g_free(action);
	g_free(gesture);
	g_free(old_gesture);

	return FALSE;
}


static void
prefs_gesture_response (GtkWidget *widget, gint response)
{
	gboolean use_gesture = TRUE;
	KzPrefsGesture *self = g_object_get_data(G_OBJECT(widget), DATA_KEY);

	g_return_if_fail(self);

	switch (response) {
	case GTK_RESPONSE_ACCEPT:
	case GTK_RESPONSE_APPLY:
		gtk_tree_model_foreach(GTK_TREE_MODEL(self->treestore),
				       apply_config_func, self);

		use_gesture = gtk_toggle_button_get_active
				(GTK_TOGGLE_BUTTON(self->use_gesture_check));
		KZ_CONF_SET("Gesture", "use_gesture", use_gesture, BOOL);
		
		break;
	case GTK_RESPONSE_REJECT:
		break;
	case KZ_RESPONSE_UI_LEVEL_MEDIUM:
		break;
	case KZ_RESPONSE_UI_LEVEL_EXPERT:
		break;
	case KZ_RESPONSE_UI_LEVEL_CUSTOM:
		break;
	default:
		break;
	}
}


KzPrefsWinPageEntry *
prefs_gesture_get_entry (gint idx)
{
	if (idx == 0)
		return &prefs_entry;
	else
		return NULL;
}
