/* -*- mode: c; style: linux -*- */

/* accessibility-keyboard.c
 * Copyright (C) 2002 Ximian, Inc.
 *
 * Written by: Jody Goldberg <jody@gnome.org>
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "accessibility-keyboard.h"
#include <gnome.h>
#include <gconf/gconf-client.h>
#include <glade/glade.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <math.h>

#include "capplet-util.h"
#include "capplet-stock-icons.h"
#include "gconf-property-editor.h"
#include "activate-settings-daemon.h"

#define CONFIG_ROOT "/desktop/gnome/accessibility/keyboard"

static struct {
	char const * const checkbox;
	char const * const image;
	char const * const image_file;
	char const * const gconf_key;
	char const * const content [3];
	gboolean always_enabled;
} const features [] = {
	{ "repeatkeys_enable", "repeatkeys_image", KEYBOARD_REPEAT,
		"/desktop/gnome/peripherals/keyboard/repeat",
		{ "repeatkeys_table", NULL, NULL }, TRUE },
	{ "bouncekeys_enable", "bouncekeys_image", ACCESSX_KEYBOARD_BOUNCE,
		CONFIG_ROOT "/bouncekeys_enable",
		{ "bouncekey_table", NULL, NULL }, FALSE },
	{ "slowkeys_enable", "slowkeys_image", ACCESSX_KEYBOARD_SLOW,
		CONFIG_ROOT "/slowkeys_enable",
		{ "slowkeys_table", NULL, NULL }, FALSE },
	{ "mousekeys_enable", "mousekeys_image", ACCESSX_KEYBOARD_MOUSE,
		CONFIG_ROOT "/mousekeys_enable",
		{ "mousekeys_table", NULL, NULL }, FALSE },
	{ "stickykeys_enable", "stickykeys_image", ACCESSX_KEYBOARD_STICK,
		CONFIG_ROOT "/stickykeys_enable",
		{ "stickeykeys_table", NULL, NULL }, FALSE },
	{ "togglekeys_enable", "togglekeys_image", ACCESSX_KEYBOARD_TOGGLE,
		CONFIG_ROOT "/togglekeys_enable",
		{ NULL, NULL, NULL }, FALSE },
	{ "timeout_enable", NULL, NULL,
		CONFIG_ROOT "/timeout_enable",
		{ "timeout_slide", "timeout_spin", "timeout_label" }, FALSE },
	{ "feature_state_change_beep", NULL, NULL,
		CONFIG_ROOT "/feature_state_change_beep",
		{ NULL, NULL, NULL }, FALSE }
};

static struct {
	char const * const slide;
	char const * const spin;
	int default_val;
	int min_val;
	int max_val;
	int step_size;
	char const * const gconf_key;
} const ranges [] = {
	{ "repeatkeys_delay_slide",	"repeatkeys_delay_spin",     500, 100, 1500,   10,
	  "/desktop/gnome/peripherals/keyboard/delay" },
	{ "repeatkeys_rate_slide",	"repeatkeys_rate_spin",      90,   10,  110,   10,
	  "/desktop/gnome/peripherals/keyboard/rate" },
	{ "bouncekeys_delay_slide",	"bouncekeys_delay_spin",       0,   0,  900,   10,
	  CONFIG_ROOT "/bouncekeys_delay" },
	{ "slowkeys_delay_slide",	"slowkeys_delay_spin",	       0,   0,  500,   10,
	  CONFIG_ROOT "/slowkeys_delay" },
	  /* WARNING anything larger than approx 512 seems to loose all keyboard input */
	{ "mousekeys_max_speed_slide",	"mousekeys_max_speed_spin",  300,  10, 500,   10,
	  CONFIG_ROOT "/mousekeys_max_speed" },
	{ "mousekeys_accel_time_slide",	"mousekeys_accel_time_spin", 300,  10, 3000,  10,
	  CONFIG_ROOT "/mousekeys_accel_time" },
	{ "mousekeys_init_delay_slide",	"mousekeys_init_delay_spin", 300,  10, 5000,  10,
	  CONFIG_ROOT "/mousekeys_init_delay" },
	{ "timeout_slide",	"timeout_spin",			     200,  10,  500,   10,
	  CONFIG_ROOT "/timeout" },
};

static void
set_sensitive (GladeXML *dialog, char const *name, gboolean state)
{
	if (name != NULL)
		gtk_widget_set_sensitive (WID (name), state);
}

/**
 * cb_feature_toggled :
 *
 * NOTE : for this to work the toggle MUST be initialized to active in the
 * glade file.  That way if the gconf setting is FALSE the toggle will fire.
 */
static void
cb_feature_toggled (GtkToggleButton *btn, gpointer feature_index)
{
	gboolean const state =
		(GTK_WIDGET_STATE (btn) != GTK_STATE_INSENSITIVE) &&
		gtk_toggle_button_get_active (btn);
	GladeXML *dialog = g_object_get_data (G_OBJECT (btn), "dialog");
	int feature, i;

	g_return_if_fail (dialog != NULL);

	feature = GPOINTER_TO_INT (feature_index);

	if (features [feature].image != NULL)
		set_sensitive (dialog, features [feature].image, state);
	for (i = G_N_ELEMENTS (features [feature].content) ; i-- > 0 ; )
		set_sensitive (dialog, features [feature].content [i], state);
}

static void
setup_toggles (GladeXML *dialog, GConfChangeSet *changeset)
{
	GObject *peditor;
	GtkWidget *checkbox;
	GtkWidget *checkbox_label;
	int i = G_N_ELEMENTS (features);

	while (i-- > 0) {
		checkbox = WID (features [i].checkbox);

		g_return_if_fail (checkbox != NULL);

		/* you can't do this from glade */
		checkbox_label = gtk_bin_get_child (GTK_BIN (checkbox));
		g_object_set (G_OBJECT (checkbox_label), "use_markup", TRUE, NULL);

		g_object_set_data (G_OBJECT (checkbox), "dialog", dialog);
		g_signal_connect (G_OBJECT (checkbox),
			"toggled",
			G_CALLBACK (cb_feature_toggled), GINT_TO_POINTER (i));
		peditor = gconf_peditor_new_boolean (changeset,
			(gchar *)features [i].gconf_key, checkbox, NULL);
	}
}

static void
setup_simple_toggles (GladeXML *dialog, GConfChangeSet *changeset)
{
	static struct {
		char const *gconf_key;
		char const *checkbox;
	} const simple_toggles [] = {
		{ CONFIG_ROOT "/bouncekeys_beep_reject",  "bouncekeys_beep_reject" },

		{ CONFIG_ROOT "/slowkeys_beep_press",      "slowkeys_beep_press" },
		{ CONFIG_ROOT "/slowkeys_beep_accept",     "slowkeys_beep_accept" },
		{ CONFIG_ROOT "/slowkeys_beep_reject",     "slowkeys_beep_reject" },

		{ CONFIG_ROOT "/stickykeys_two_key_off",   "stickykeys_two_key_off" },
		{ CONFIG_ROOT "/stickykeys_modifier_beep", "stickykeys_modifier_beep" },
	};
	int i = G_N_ELEMENTS (simple_toggles);
	while (i-- > 0) {
		GtkWidget *w = WID (simple_toggles [i].checkbox);

		g_return_if_fail (w != NULL);

		gconf_peditor_new_boolean (changeset,
			(gchar *) simple_toggles [i].gconf_key,
			w, NULL);
	}
}

static void
setup_ranges (GladeXML *dialog, GConfChangeSet *changeset)
{
	GObject *peditor;
	GtkWidget *slide, *spin;
	GtkAdjustment  *adjustment;
	int i = G_N_ELEMENTS (ranges);

	while (i-- > 0) {
		slide = WID (ranges [i].slide);
		spin  = WID (ranges [i].spin);
		g_return_if_fail (slide != NULL);
		g_return_if_fail (spin != NULL);

		adjustment = gtk_range_get_adjustment (GTK_RANGE (slide));

		g_return_if_fail (adjustment != NULL);

		adjustment->value = ranges [i].default_val;
		adjustment->lower = ranges [i].min_val;
		adjustment->upper = ranges [i].max_val;
		adjustment->step_increment = ranges [i].step_size;
		adjustment->page_increment = ranges [i].step_size;
		adjustment->page_size = 0;

		gtk_adjustment_changed (adjustment);
		gtk_spin_button_configure (GTK_SPIN_BUTTON (spin), adjustment,
			ranges [i].step_size, 0);
		peditor = gconf_peditor_new_numeric_range (changeset,
			(gchar *)ranges [i].gconf_key, slide,
			 NULL);
	}
}

static void
setup_images (GladeXML *dialog)
{
	GtkWidget *image;
	int i = G_N_ELEMENTS (features);

	while (i-- > 0)
		if (features [i].image != NULL)
			gtk_image_set_from_stock (GTK_IMAGE (WID (features [i].image)),
						  features [i].image_file,
						  keyboard_capplet_icon_get_size ());

	image = gtk_image_new_from_stock (GTK_STOCK_REVERT_TO_SAVED, GTK_ICON_SIZE_BUTTON);
	gtk_button_set_image (GTK_BUTTON (WID ("load_CDE_file")), image);

	image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON);
	gtk_button_set_image (GTK_BUTTON (WID ("launch_mouse_capplet")), image);
}

static void
cb_launch_mouse_capplet (GtkButton *button, GtkWidget *dialog)
{
	GError *err = NULL;
	if (!g_spawn_command_line_async ("gnome-mouse-properties", &err))
		capplet_error_dialog (GTK_WINDOW (gtk_widget_get_toplevel (dialog)),
			_("There was an error launching the mouse preferences dialog: %s"),
			err);
}

static void
cb_master_enable_toggle (GtkToggleButton *btn, GladeXML *dialog)
{
	int i = G_N_ELEMENTS (features);
	gboolean flag = gtk_toggle_button_get_active (btn);
	GtkWidget *w;

	while (i-- > 0) {
		if (!features [i].always_enabled) {
			w = WID (features [i].checkbox);
			gtk_widget_set_sensitive (w, flag);
			cb_feature_toggled (GTK_TOGGLE_BUTTON (w), GINT_TO_POINTER (i));
		}
	}
}

static void
setup_dialog (GladeXML *dialog, GConfChangeSet *changeset)
{
	GtkWidget *master_enable = WID ("master_enable");

	capplet_init_stock_icons ();
	setup_images (dialog);
	setup_ranges (dialog, changeset);
	setup_toggles (dialog, changeset);
	setup_simple_toggles (dialog, changeset);

	g_signal_connect (master_enable,
		"toggled",
		G_CALLBACK (cb_master_enable_toggle), dialog);
	gconf_peditor_new_boolean (changeset,
		CONFIG_ROOT "/enable",
		GTK_WIDGET (master_enable), NULL);
}

/*******************************************************************************/

static gboolean
xrm_get_bool (GConfClient *client, XrmDatabase *db, char const *gconf_key,
	      char const *res_str, char const *class_str)
{
	XrmValue  resourceValue;
	char	 *res;

	if (!XrmGetResource (*db, res_str, class_str, &res, &resourceValue))
		return FALSE;
	gconf_client_set_bool (client, gconf_key,
		!g_ascii_strcasecmp (resourceValue.addr, "True"), NULL);

	return TRUE;
}

static gboolean
xrm_get_int (GConfClient *client, XrmDatabase *db, char const *gconf_key,
	     char const *res_str, char const *class_str, float scale)
{
	XrmValue	resourceValue;
	char		*res;
	int		value, log_scale;
	char		resource [256];

	snprintf (resource, sizeof (resource), "%s.value", res_str);
	if (!XrmGetResource (*db, resource, class_str, &res, &resourceValue))
		return FALSE;
	value = atoi (resourceValue.addr);

	snprintf (resource, sizeof (resource), "%s.decimalPoints", res_str);
	if (!XrmGetResource (*db, resource, class_str, &res, &resourceValue))
		return FALSE;
	log_scale = atoi (resourceValue.addr);

	while (log_scale-- > 0)
		scale /= 10.;

	gconf_client_set_int (client, gconf_key, value, NULL);

	return TRUE;
}

/* This loads the current users XKB settings from their file */
static gboolean
load_CDE_file (GtkFileChooser *fchooser)
{
	char *file = gtk_file_chooser_get_filename (fchooser);
	GConfClient *client;
	XrmDatabase  db;
	gboolean found = FALSE;

	if (!(db = XrmGetFileDatabase (file))) {
		GtkWidget *warn = gtk_message_dialog_new (
			gtk_window_get_transient_for (GTK_WINDOW (fchooser)),
			GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
			_("Unable to import AccessX settings from file '%s'"),
			file);
		g_signal_connect (warn,
			"response",
			G_CALLBACK (gtk_widget_destroy), NULL);
		gtk_widget_show (warn);
		g_free (file);
		return FALSE;
	}

	client = gconf_client_get_default ();
	gconf_client_set_bool (client, CONFIG_ROOT "/enable", TRUE, NULL);

	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/feature_state_change_beep",
		"*SoundOnOffToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/timeout_enable",
		"*TimeOutToggle.set",		"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/stickykeys_enable",
		"*StickyKeysToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/mousekeys_enable",
		"*MouseKeysToggle.set",		"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/togglekeys_enable",
		"*ToggleKeysToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/slowkeys_enable",
		"*SlowKeysToggle.set",		"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/bouncekeys_enable",
		"*BounceKeysToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/stickykeys_modifier_beep",
		"*StickyModSoundToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/stickykeys_two_key_off",
		"*StickyTwoKeysToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/slowkeys_beep_press",
		"*SlowKeysOnPressToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/slowkeys_beep_accept",
		"*SlowKeysOnAcceptToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/timeout",
		"*TimeOutScale",		"AccessX*XmScale", 60);
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/mousekeys_max_speed",
		"*MouseMaxSpeedScale",		"AccessX*XmScale", 1);
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/mousekeys_accel_time",
		"*MouseAccelScale",		"AccessX*XmScale", 1);
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/mousekeys_init_delay",
		"*MouseDelayScale",		"AccessX*XmScale", 1);
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/slowkeys_delay",
		"*KRGSlowKeysDelayScale",	"AccessX*XmScale", 1000);
	found |= xrm_get_int  (client, &db, CONFIG_ROOT "/bouncekeys_delay",
		"*KRGDebounceScale",		"AccessX*XmScale", 1000);

	/* Set the master enable flag last */
	found |= xrm_get_bool (client, &db, CONFIG_ROOT "/enable",
		"*EnableAccessXToggle.set",	"AccessX*ToggleButtonGadget.XmCSet");

	g_object_unref (client);

	if (!found) {
		/* it would be nice to have a better message bu that would
		 * break string freeze
		 */
		GtkWidget *warn = gtk_message_dialog_new (
			gtk_window_get_transient_for (GTK_WINDOW (fchooser)),
			GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
			_("Unable to import AccessX settings from file '%s'"),
			file);
		g_signal_connect (warn,
			"response",
			G_CALLBACK (gtk_widget_destroy), NULL);
		gtk_widget_show (warn);
		g_free (file);
		return FALSE;
	}
	g_free(file);
	return TRUE;
}

static void
fchooser_handle_response (GtkFileChooser *fchooser, gint response, gpointer data)
{
	char *file_name;

	if (response == GTK_RESPONSE_OK) {
		file_name = gtk_file_chooser_get_filename (fchooser);

		/* Change into directory if that's what user selected */
		if (g_file_test (file_name, G_FILE_TEST_IS_DIR))
			gtk_file_chooser_set_current_folder (fchooser, file_name);
		else if (load_CDE_file (fchooser))
			gtk_widget_destroy (GTK_WIDGET (fchooser));

		g_free (file_name);
	} else {
		gtk_widget_destroy (GTK_WIDGET (fchooser));
	}
}

static void
cb_load_CDE_file (GtkButton *button, GtkWidget *dialog)
{
	GtkFileChooser *fchooser = GTK_FILE_CHOOSER (
		gtk_file_chooser_dialog_new (_("Import Feature Settings File"),
					     GTK_WINDOW (gtk_widget_get_toplevel (dialog)),
					     GTK_FILE_CHOOSER_ACTION_OPEN,
					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
					     _("_Import"), GTK_RESPONSE_OK,
					     NULL));

	gtk_window_set_position (GTK_WINDOW (fchooser), GTK_WIN_POS_MOUSE);
	gtk_window_set_modal (GTK_WINDOW (fchooser), TRUE);
	g_signal_connect (G_OBJECT (fchooser),
		"response",
		G_CALLBACK (fchooser_handle_response), NULL);

	gtk_widget_show (GTK_WIDGET (fchooser));
}

/*******************************************************************************/

GtkWidget *
setup_accessX_dialog (GConfChangeSet *changeset)
{
	GConfClient *client;
	char const  *toplevel_name = "accessX_dialog";
	GladeXML    *dialog = glade_xml_new (GNOMECC_GLADE_DIR "/gnome-accessibility-keyboard-properties.glade",
		toplevel_name, NULL);
	GtkWidget *toplevel = WID (toplevel_name);

	client = gconf_client_get_default ();
	gconf_client_add_dir (client, CONFIG_ROOT, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
	g_object_unref (client);

	setup_dialog (dialog, changeset);

	g_signal_connect (G_OBJECT (WID ("load_CDE_file")),
		"clicked",
		G_CALLBACK (cb_load_CDE_file), toplevel);
	g_signal_connect (G_OBJECT (WID ("launch_mouse_capplet")),
		"clicked",
		G_CALLBACK (cb_launch_mouse_capplet), toplevel);

	return toplevel;
}
