/* This program was written with lots of love under the GPL by Jonathan
 * Blandford <jrb@gnome.org>
 */

#include <config.h>

#include <string.h>
#include <gtk/gtk.h>
#include <gconf/gconf-client.h>
#include <gdk/gdkx.h>
#include <glade/glade.h>
#include <X11/Xatom.h>

#include "gnome-theme-info.h"
#include "wm-common.h"
#include "capplet-util.h"
#include "eggcellrendererkeys.h"
#include "activate-settings-daemon.h"

#define LABEL_DATA "gnome-keybinding-properties-label"
#define MAX_ELEMENTS_BEFORE_SCROLLING 10

typedef enum {
  ALWAYS_VISIBLE,
  N_WORKSPACES_GT
} KeyListEntryVisibility;

typedef struct
{
  const char *name;
  KeyListEntryVisibility visibility;
  gint data;
} KeyListEntry;

static const KeyListEntry desktop_key_list[] =
{
  { "/apps/gnome_settings_daemon/keybindings/help", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/power", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/sleep", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/screensaver", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/home", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/search", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/email", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/www", ALWAYS_VISIBLE, 0 },
  { "/apps/metacity/global_keybindings/panel_run_dialog", ALWAYS_VISIBLE, 0 },
  { "/apps/metacity/global_keybindings/panel_main_menu", ALWAYS_VISIBLE, 0 },
  { "/apps/metacity/global_keybindings/run_command_screenshot", ALWAYS_VISIBLE, 0 },
  { "/apps/metacity/global_keybindings/run_command_window_screenshot", ALWAYS_VISIBLE, 0 },
  { "/apps/metacity/global_keybindings/run_command_terminal", ALWAYS_VISIBLE, 0 },
  { NULL }
};
static const KeyListEntry sounds_key_list[] =
{
  { "/apps/gnome_settings_daemon/keybindings/volume_mute", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/volume_down", ALWAYS_VISIBLE, 0 },
  { "/apps/gnome_settings_daemon/keybindings/volume_up", ALWAYS_VISIBLE, 0 },
  /* Other ones that need keysyms bindings */
  { "/apps/gnome_settings_daemon/keybindings/play", ALWAYS_VISIBLE, 0 },        
  { "/apps/gnome_settings_daemon/keybindings/pause", ALWAYS_VISIBLE, 0 },       
  { "/apps/gnome_settings_daemon/keybindings/stop", ALWAYS_VISIBLE, 0 },        
  { "/apps/gnome_settings_daemon/keybindings/previous", ALWAYS_VISIBLE, 0 },    
  { "/apps/gnome_settings_daemon/keybindings/next", ALWAYS_VISIBLE, 0 },        
  { "/apps/gnome_settings_daemon/keybindings/eject", ALWAYS_VISIBLE, 0 },
  { NULL }
};

static const KeyListEntry metacity_key_list[] =
{
  { "/apps/metacity/window_keybindings/activate_window_menu",      ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/toggle_fullscreen",         ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/toggle_maximized",          ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/maximize",                  ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/unmaximize",                ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/toggle_shaded",             ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/close",                     ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/minimize",                  ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/begin_move",                ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/begin_resize",              ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/toggle_on_all_workspaces",  N_WORKSPACES_GT, 1 },
  /* These next three are out of order compared to the schemas file. */
  { "/apps/metacity/window_keybindings/raise_or_lower",            ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/raise",                     ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/lower",                     ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/maximize_vertically",       ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/maximize_horizontally",     ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/window_keybindings/move_to_workspace_1",       N_WORKSPACES_GT, 1 },
  { "/apps/metacity/window_keybindings/move_to_workspace_2",       N_WORKSPACES_GT, 1 },
  { "/apps/metacity/window_keybindings/move_to_workspace_3",       N_WORKSPACES_GT, 2 },
  { "/apps/metacity/window_keybindings/move_to_workspace_4",       N_WORKSPACES_GT, 3 },
  { "/apps/metacity/window_keybindings/move_to_workspace_5",       N_WORKSPACES_GT, 4 },
  { "/apps/metacity/window_keybindings/move_to_workspace_6",       N_WORKSPACES_GT, 5 },
  { "/apps/metacity/window_keybindings/move_to_workspace_7",       N_WORKSPACES_GT, 6 },
  { "/apps/metacity/window_keybindings/move_to_workspace_8",       N_WORKSPACES_GT, 7 },
  { "/apps/metacity/window_keybindings/move_to_workspace_9",       N_WORKSPACES_GT, 8 },
  { "/apps/metacity/window_keybindings/move_to_workspace_10",      N_WORKSPACES_GT, 9 },
  { "/apps/metacity/window_keybindings/move_to_workspace_11",      N_WORKSPACES_GT, 10 },
  { "/apps/metacity/window_keybindings/move_to_workspace_12",      N_WORKSPACES_GT, 11 },
  { "/apps/metacity/window_keybindings/move_to_workspace_left",    N_WORKSPACES_GT, 1 },
  { "/apps/metacity/window_keybindings/move_to_workspace_right",   N_WORKSPACES_GT, 1 },
  { "/apps/metacity/window_keybindings/move_to_workspace_up",      N_WORKSPACES_GT, 1 },
  { "/apps/metacity/window_keybindings/move_to_workspace_down",    N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_windows",            ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/global_keybindings/switch_panels",             ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/global_keybindings/cycle_windows",             ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/global_keybindings/cycle_panels",              ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/global_keybindings/show_desktop",              ALWAYS_VISIBLE,  0 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_1",     N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_2",     N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_3",     N_WORKSPACES_GT, 2 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_4",     N_WORKSPACES_GT, 3 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_5",     N_WORKSPACES_GT, 4 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_6",     N_WORKSPACES_GT, 5 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_7",     N_WORKSPACES_GT, 6 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_8",     N_WORKSPACES_GT, 7 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_9",     N_WORKSPACES_GT, 8 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_10",    N_WORKSPACES_GT, 9 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_11",    N_WORKSPACES_GT, 10 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_12",    N_WORKSPACES_GT, 11 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_left",  N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_right", N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_up",    N_WORKSPACES_GT, 1 },
  { "/apps/metacity/global_keybindings/switch_to_workspace_down",  N_WORKSPACES_GT, 1 },
  { NULL }
};

enum
{
  DESCRIPTION_COLUMN,
  KEYENTRY_COLUMN,
  N_COLUMNS
};

typedef struct
{
  char *gconf_key;
  guint keyval;
  guint keycode;
  EggVirtualModifierType mask;
  gboolean editable;
  GtkTreeModel *model;
  guint gconf_cnxn;
  char *description;
} KeyEntry;

static void  reload_key_entries (gpointer                wm_name,
                                 GladeXML               *dialog);
static char* binding_name       (guint                   keyval,
				 guint			 keycode,
                                 EggVirtualModifierType  mask,
                                 gboolean                translate);

static GtkTreeModel*
get_real_model (GtkTreeView *tree_view)
{
  GtkTreeModel *model;
  GtkTreeModel *submodel;
  
  model = gtk_tree_view_get_model (tree_view);

  if (model)
    submodel = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
  else
    submodel = NULL;
  
  return submodel;
}

static GladeXML *
create_dialog (void)
{
  GladeXML *dialog;

  dialog = glade_xml_new (GNOMECC_DATA_DIR "/interfaces/gnome-keybinding-properties.glade", "gnome-keybinding-dialog", NULL);

  return dialog;
}

static char*
binding_name (guint                   keyval,
	      guint		      keycode,
              EggVirtualModifierType  mask,
              gboolean                translate)
{
  if (keyval != 0 || keycode != 0)
    return egg_virtual_accelerator_name (keyval, keycode, mask);
  else
    return translate ? g_strdup (_("Disabled")) : g_strdup ("disabled");
}

static gboolean
binding_from_string (const char             *str,
                     guint                  *accelerator_key,
		     guint		    *keycode,
                     EggVirtualModifierType *accelerator_mods)
{
  g_return_val_if_fail (accelerator_key != NULL, FALSE);
  
  if (str == NULL || (str && strcmp (str, "disabled") == 0))
    {
      *accelerator_key = 0;
      *keycode = 0;
      *accelerator_mods = 0;
      return TRUE;
    }

  egg_accelerator_parse_virtual (str, accelerator_key, keycode, accelerator_mods);
  
  if (*accelerator_key == 0)
    return FALSE;
  else
    return TRUE;
}

static void
accel_set_func (GtkTreeViewColumn *tree_column,
                GtkCellRenderer   *cell,
                GtkTreeModel      *model,
                GtkTreeIter       *iter,
                gpointer           data)
{
  KeyEntry *key_entry;
  
  gtk_tree_model_get (model, iter,
                      KEYENTRY_COLUMN, &key_entry,
                      -1);

  if (key_entry == NULL)
    g_object_set (G_OBJECT (cell),
		  "visible", FALSE,
		  NULL);
  else if (! key_entry->editable)
    g_object_set (G_OBJECT (cell),
		  "visible", TRUE,
		  "editable", FALSE,
		  "accel_key", key_entry->keyval,
		  "accel_mask", key_entry->mask,
		  "keycode", key_entry->keycode,
		  "style", PANGO_STYLE_ITALIC,
		  NULL);
  else
    g_object_set (G_OBJECT (cell),
		  "visible", TRUE,
		  "editable", TRUE,
		  "accel_key", key_entry->keyval,
		  "accel_mask", key_entry->mask,
		  "keycode", key_entry->keycode,
		  "style", PANGO_STYLE_NORMAL,
		  NULL);
}

static gboolean
keybinding_key_changed_foreach (GtkTreeModel *model,
				GtkTreePath  *path,
				GtkTreeIter  *iter,
				gpointer      user_data)
{
  KeyEntry *key_entry;
  KeyEntry *tmp_key_entry;

  key_entry = (KeyEntry *)user_data;
  gtk_tree_model_get (key_entry->model, iter,
		      KEYENTRY_COLUMN, &tmp_key_entry,
		      -1);

  if (key_entry == tmp_key_entry)
    {
      gtk_tree_model_row_changed (key_entry->model, path, iter);
      return TRUE;
    }
  return FALSE;
}

static void
keybinding_key_changed (GConfClient *client,
			guint        cnxn_id,
			GConfEntry  *entry,
			gpointer     user_data)
{
  KeyEntry *key_entry;
  const gchar *key_value;

  key_entry = (KeyEntry *)user_data;
  key_value = gconf_value_get_string (entry->value);

  binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask);
  key_entry->editable = gconf_entry_get_is_writable (entry);

  /* update the model */
  gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry);
}

static int
keyentry_sort_func (GtkTreeModel *model,
                    GtkTreeIter  *a,
                    GtkTreeIter  *b,
                    gpointer      user_data)
{
  KeyEntry *key_entry_a;
  KeyEntry *key_entry_b;
  char *name_a;
  char *name_b;
  int retval;

  /* mmmmm, super-slow. */

  key_entry_a = NULL;
  gtk_tree_model_get (model, a,
                      KEYENTRY_COLUMN, &key_entry_a,
                      -1);  

  key_entry_b = NULL;
  gtk_tree_model_get (model, b,
                      KEYENTRY_COLUMN, &key_entry_b,
                      -1);

  
  if (key_entry_a != NULL)
    name_a = binding_name (key_entry_a->keyval,
		    	   key_entry_a->keycode,
                           key_entry_a->mask,
                           TRUE);
  else
    name_a = NULL;

  if (key_entry_b != NULL)
    name_b = binding_name (key_entry_b->keyval,
		    	   key_entry_b->keycode,
                           key_entry_b->mask,
                           TRUE);
  else
    name_b = NULL;

  if (name_a && name_b)
    retval = g_utf8_collate (name_a, name_b);
  else if (name_a)
    retval = 1;
  else if (name_b)
    retval = -1;
  else
    retval = 0;

  g_free (name_a);
  g_free (name_b);

  return retval;
}

static void
clear_old_model (GladeXML  *dialog,
		 GtkWidget *tree_view)
{
  GtkTreeModel *model;
  GtkTreeModel *sort_model;
  GtkTreeIter iter;
  KeyEntry *key_entry;
  gboolean valid;
  GConfClient *client;

  client = gconf_client_get_default ();
  model = get_real_model (GTK_TREE_VIEW (tree_view));

  if (model != NULL)
    {
      g_object_ref (model);

      for (valid = gtk_tree_model_get_iter_first (model, &iter);
	   valid;
	   valid = gtk_tree_model_iter_next (model, &iter))
	{
	  gtk_tree_model_get (model, &iter,
			      KEYENTRY_COLUMN, &key_entry,
			      -1);
	  if (key_entry != NULL)
	    {
	      gconf_client_notify_remove (client, key_entry->gconf_cnxn);
	      g_free (key_entry->gconf_key);
              g_free (key_entry->description);
	      g_free (key_entry);
            }
	}
      g_object_unref (model);
    }

  g_object_unref (client);

  model = (GtkTreeModel *) gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
  
  sort_model = gtk_tree_model_sort_new_with_model (model);

  /* N_COLUMNS is just a place to stick the extra sort function */
  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model),
                                   N_COLUMNS,
                                   keyentry_sort_func,
                                   NULL, NULL);
  
  gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), sort_model);

  g_object_unref (G_OBJECT (model));
  g_object_unref (G_OBJECT (sort_model));
  
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (WID ("actions_swindow")),
				  GTK_POLICY_NEVER, GTK_POLICY_NEVER);
  gtk_widget_set_usize (WID ("actions_swindow"), -1, -1);
}

static gboolean
count_rows_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
  gint *rows = data;

  (*rows)++;

  return FALSE;
}

static gboolean
should_show_key (const KeyListEntry *entry)
{
  gint workspaces;
  GConfClient *client;
 
  switch (entry->visibility) {
  case ALWAYS_VISIBLE:
    return TRUE;
  case N_WORKSPACES_GT:
    client = gconf_client_get_default();
    workspaces = gconf_client_get_int (client,
				       "/apps/metacity/general/num_workspaces", NULL);
    g_object_unref (client);

    if (workspaces > entry->data)
      return TRUE;
    else
      return FALSE;
  }

  return FALSE;
}

static void
append_keys_to_tree (GladeXML           *dialog,
		     const gchar        *title,
		     const KeyListEntry *keys_list)
{
  GConfClient *client;
  GtkTreeIter parent_iter;
  GtkTreeModel *model;
  gint i, j;

  client = gconf_client_get_default ();
  model = get_real_model (GTK_TREE_VIEW (WID ("shortcut_treeview")));

  i = 0;
  gtk_tree_model_foreach (model, count_rows_foreach, &i);

  gtk_tree_store_append (GTK_TREE_STORE (model), &parent_iter, NULL);
  gtk_tree_store_set (GTK_TREE_STORE (model), &parent_iter,
  		      DESCRIPTION_COLUMN, title,
  		      -1);

  for (j = 0; keys_list[j].name != NULL; j++)
    {
      GConfEntry *entry;
      GConfSchema *schema = NULL;
      KeyEntry *key_entry;
      GError *error = NULL;
      GtkTreeIter iter;
      const gchar *key_string;
      gchar *key_value;

      if (!should_show_key (&keys_list[j]))
	continue;
      
      key_string = keys_list[j].name;

      entry = gconf_client_get_entry (client,
                                      key_string,
				      NULL,
				      TRUE,
				      &error);
      if (error || entry == NULL)
	{
	  /* We don't actually want to popup a dialog - just skip this one */
	  if (error)
	    g_error_free (error);
	  continue;
	}

      if (gconf_entry_get_schema_name (entry))
	schema = gconf_client_get_schema (client, gconf_entry_get_schema_name (entry), &error);
      
      if (error || schema == NULL)
	{
	  /* We don't actually want to popup a dialog - just skip this one */
	  if (error)
	    g_error_free (error);
	  continue;
	}

      key_value = gconf_client_get_string (client, key_string, &error);

      key_entry = g_new0 (KeyEntry, 1);
      key_entry->gconf_key = g_strdup (key_string);
      key_entry->editable = gconf_entry_get_is_writable (entry);
      key_entry->model = model;
      gconf_client_add_dir (client, key_string, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
      key_entry->gconf_cnxn = gconf_client_notify_add (client,
						       key_string,
						       (GConfClientNotifyFunc) &keybinding_key_changed,
						       key_entry, NULL, NULL);
      binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask);
      g_free (key_value);
      key_entry->description = g_strdup (gconf_schema_get_short_desc (schema));

      if (i == MAX_ELEMENTS_BEFORE_SCROLLING)
	{
	  GtkRequisition rectangle;
	  gtk_widget_ensure_style (WID ("shortcut_treeview"));
	  gtk_widget_size_request (WID ("shortcut_treeview"), &rectangle);
	  gtk_widget_set_size_request (WID ("shortcut_treeview"), -1, rectangle.height);
	  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (WID ("actions_swindow")),
					  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	}
      i++;
      gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter);
      if (gconf_schema_get_short_desc (schema))
	gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
			    DESCRIPTION_COLUMN,
                            key_entry->description,
			    KEYENTRY_COLUMN, key_entry,
			    -1);
      else
	gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
			    DESCRIPTION_COLUMN, _("<Unknown Action>"),
			    KEYENTRY_COLUMN, key_entry,
			    -1);
      gtk_tree_view_expand_all (GTK_TREE_VIEW (WID ("shortcut_treeview")));
      gconf_entry_free (entry);
      gconf_schema_free (schema);
    }

  g_object_unref (client);

  if (i == 0)
      gtk_widget_hide (WID ("shortcuts_vbox"));
  else
      gtk_widget_show (WID ("shortcuts_vbox"));
}

static void
reload_key_entries (gpointer wm_name, GladeXML *dialog)
{
  clear_old_model (dialog, WID ("shortcut_treeview"));
  
  append_keys_to_tree (dialog, _("Desktop"), desktop_key_list);
  append_keys_to_tree (dialog, _("Sound"), sounds_key_list);
  
  if (strcmp((char *) wm_name, WM_COMMON_METACITY) == 0)
    {
      append_keys_to_tree (dialog, _("Window Management"), metacity_key_list);
    }
}

static void
key_entry_controlling_key_changed (GConfClient *client,
				   guint        cnxn_id,
				   GConfEntry  *entry,
				   gpointer     user_data)
{
  reload_key_entries (wm_common_get_current_window_manager(), user_data);
}

static gboolean
cb_check_for_uniqueness (GtkTreeModel *model,
			 GtkTreePath  *path,
			 GtkTreeIter  *iter,
			 KeyEntry *new_key)
{
  KeyEntry *element;

  gtk_tree_model_get (new_key->model, iter,
		      KEYENTRY_COLUMN, &element,
		      -1);

  /* no conflict for : blanks, different modifiers, or ourselves */
  if (element == NULL || new_key->mask != element->mask ||
      !strcmp (new_key->gconf_key, element->gconf_key))
    return FALSE;

  if (new_key->keyval != 0) {
      if (new_key->keyval != element->keyval)
	  return FALSE;
  } else if (element->keyval != 0 || new_key->keycode != element->keycode)
    return FALSE;

  new_key->editable = FALSE;
  new_key->gconf_key = element->gconf_key;
  new_key->description = element->description;
  return TRUE;
}

static void
accel_edited_callback (GtkCellRendererText   *cell,
                       const char            *path_string,
                       guint                  keyval,
                       EggVirtualModifierType mask,
		       guint		      keycode,
                       gpointer               data)
{
  GConfClient *client;
  GtkTreeView *view = (GtkTreeView *)data;
  GtkTreeModel *model;
  GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
  GtkTreeIter iter;
  KeyEntry *key_entry, tmp_key;
  GError *err = NULL;
  char *str;

  model = gtk_tree_view_get_model (view);
  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_path_free (path);
  gtk_tree_model_get (model, &iter,
		      KEYENTRY_COLUMN, &key_entry,
		      -1);

  /* sanity check */
  if (key_entry == NULL)
    return;

  model = get_real_model (view);
  tmp_key.model  = model;
  tmp_key.keyval = keyval;
  tmp_key.keycode = keycode;
  tmp_key.mask   = mask;
  tmp_key.gconf_key = key_entry->gconf_key;
  tmp_key.description = NULL;
  tmp_key.editable = TRUE; /* kludge to stuff in a return flag */

  if (keyval != 0 || keycode != 0) /* any number of keys can be disabled */
    gtk_tree_model_foreach (model,
      (GtkTreeModelForeachFunc) cb_check_for_uniqueness,
      &tmp_key);

  /* Check for unmodified keys */
  if (tmp_key.mask == 0 && tmp_key.keycode != 0)
    {
      if ((tmp_key.keyval >= GDK_a && tmp_key.keyval <= GDK_z)
	   || (tmp_key.keyval >= GDK_A && tmp_key.keyval <= GDK_Z)
	   || (tmp_key.keyval >= GDK_kana_fullstop && tmp_key.keyval <= GDK_semivoicedsound)
	   || (tmp_key.keyval >= GDK_Arabic_comma && tmp_key.keyval <= GDK_Arabic_sukun)
	   || (tmp_key.keyval >= GDK_Serbian_dje && tmp_key.keyval <= GDK_Cyrillic_HARDSIGN)
	   || (tmp_key.keyval >= GDK_Greek_ALPHAaccent && tmp_key.keyval <= GDK_Greek_omega)
	   || (tmp_key.keyval >= GDK_hebrew_doublelowline && tmp_key.keyval <= GDK_hebrew_taf)
	   || (tmp_key.keyval >= GDK_Thai_kokai && tmp_key.keyval <= GDK_Thai_lekkao)
	   || (tmp_key.keyval >= GDK_Hangul && tmp_key.keyval <= GDK_Hangul_Special)
	   || (tmp_key.keyval >= GDK_Hangul_Kiyeog && tmp_key.keyval <= GDK_Hangul_J_YeorinHieuh)
	   || tmp_key.keyval == GDK_Mode_switch) {
        GtkWidget *dialog;
	char *name;

	name = egg_virtual_accelerator_name (keyval, keycode, mask);

	dialog =
	  gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
			  	  GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
				  GTK_MESSAGE_WARNING,
				  GTK_BUTTONS_CANCEL,
				  _("The shortcut \"%s\" cannot be used because it will become unusable to type using this key.\n"
				  "Please try with a key such as Control, Alt or Shift at the same time.\n"),
				  name);

	g_free (name);
	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);

	/* set it back to its previous value. */
	egg_cell_renderer_keys_set_accelerator
	  (EGG_CELL_RENDERER_KEYS (cell),
	   key_entry->keyval, key_entry->keycode, key_entry->mask);
	return;
      }
    }

  /* flag to see if the new accelerator was in use by something */
  if (!tmp_key.editable)
    {
      GtkWidget *dialog;
      char *name;

      name = egg_virtual_accelerator_name (keyval, keycode, mask);
      
      dialog =
        gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
                                GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
                                GTK_MESSAGE_WARNING,
                                GTK_BUTTONS_CANCEL,
                                _("The shortcut \"%s\" is already used for:\n \"%s\"\n"),
                                name,
                                tmp_key.description ?
                                tmp_key.description : tmp_key.gconf_key);
      g_free (name);
      gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);

      /* set it back to its previous value. */
      egg_cell_renderer_keys_set_accelerator (EGG_CELL_RENDERER_KEYS (cell),
					      key_entry->keyval, key_entry->keycode, key_entry->mask);
      return;
    }

  str = binding_name (keyval, keycode, mask, FALSE);

  client = gconf_client_get_default();
  gconf_client_set_string (client,
                           key_entry->gconf_key,
                           str,
                           &err);
  g_free (str);
  g_object_unref (G_OBJECT (client));

  if (err != NULL)
    {
      GtkWidget *dialog;

      dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
				       GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
				       GTK_MESSAGE_WARNING,
				       GTK_BUTTONS_OK,
				       _("Error setting new accelerator in configuration database: %s\n"),
				       err->message);
      gtk_dialog_run (GTK_DIALOG (dialog));

      gtk_widget_destroy (dialog);
      g_error_free (err);
      key_entry->editable = FALSE;
    }
}

static void
accel_cleared_callback (GtkCellRendererText *cell,
			const char          *path_string,
			gpointer             data)
{
  GConfClient *client;
  GtkTreeView *view = (GtkTreeView *) data;
  GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
  KeyEntry *key_entry;
  GtkTreeIter iter;
  GError *err = NULL;
  GtkTreeModel *model;

  model = gtk_tree_view_get_model (view);
  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_path_free (path);
  gtk_tree_model_get (model, &iter,
		      KEYENTRY_COLUMN, &key_entry,
		      -1);

  /* sanity check */
  if (key_entry == NULL)
    return;

  /* Unset the key */
  client = gconf_client_get_default();
  gconf_client_set_string (client,
			   key_entry->gconf_key,
			   "disabled",
			   &err);
  g_object_unref (G_OBJECT (client));

  if (err != NULL)
    {
      GtkWidget *dialog;

      dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
				       GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
				       GTK_MESSAGE_WARNING,
				       GTK_BUTTONS_OK,
				       _("Error unsetting accelerator in configuration database: %s\n"),
				       err->message);
      gtk_dialog_run (GTK_DIALOG (dialog));

      gtk_widget_destroy (dialog);
      g_error_free (err);
      key_entry->editable = FALSE;
    }
}


typedef struct
{
  GtkTreeView *tree_view;
  GtkTreePath *path;
} IdleData;

static gboolean
real_start_editing_cb (IdleData *idle_data)
{
  gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view));
  gtk_tree_view_set_cursor (idle_data->tree_view,
			    idle_data->path,
			    gtk_tree_view_get_column (idle_data->tree_view, 1),
			    TRUE);

  gtk_tree_path_free (idle_data->path);
  g_free (idle_data);
  return FALSE;
}

static gboolean
start_editing_kb_cb (GtkTreeView *treeview, 
			  GtkTreePath *path, 
			  GtkTreeViewColumn *column,
			  gpointer user_data)
{
  gtk_widget_grab_focus (GTK_WIDGET (treeview));
  gtk_tree_view_set_cursor (treeview,
			    path,
			    gtk_tree_view_get_column (treeview, 1),
			    TRUE);

  return FALSE;
}

static gboolean
start_editing_cb (GtkTreeView    *tree_view,
		  GdkEventButton *event,
		  GladeXML       *dialog)
{
  GtkTreePath *path;

  if (event->window != gtk_tree_view_get_bin_window (tree_view))
    return FALSE;

  if (gtk_tree_view_get_path_at_pos (tree_view,
				     (gint) event->x,
				     (gint) event->y,
				     &path, NULL,
				     NULL, NULL))
    {
      IdleData *idle_data;

      if (gtk_tree_path_get_depth (path) == 1)
	{
	  gtk_tree_path_free (path);
	  return FALSE;
	}

      idle_data = g_new (IdleData, 1);
      idle_data->tree_view = tree_view;
      idle_data->path = path;
      g_signal_stop_emission_by_name (G_OBJECT (tree_view), "button_press_event");
      g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data);
    }
  return TRUE;
}

static void
cb_dialog_response (GtkWidget *widget, gint response_id, gpointer data)
{
	if (response_id == GTK_RESPONSE_HELP)
		capplet_help (GTK_WINDOW (widget),
			      "user-guide.xml",
			      "goscustdesk-39");
	else
		gtk_main_quit ();
}

static void
setup_dialog (GladeXML *dialog)
{
  GConfClient *client;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkWidget *widget;

  client = gconf_client_get_default ();

  g_signal_connect (GTK_TREE_VIEW (WID ("shortcut_treeview")),
		    "button_press_event",
		    G_CALLBACK (start_editing_cb), dialog),
	g_signal_connect (GTK_TREE_VIEW (WID ("shortcut_treeview")),
	      "row-activated",
		    G_CALLBACK (start_editing_kb_cb), dialog),
		    
  column = gtk_tree_view_column_new_with_attributes (_("Action"),
						     gtk_cell_renderer_text_new (),
						     "text", DESCRIPTION_COLUMN,
						     NULL);
  gtk_tree_view_column_set_resizable (column, FALSE);

  gtk_tree_view_append_column (GTK_TREE_VIEW (WID ("shortcut_treeview")), column);
  gtk_tree_view_column_set_sort_column_id (column, DESCRIPTION_COLUMN);  
  
  renderer = (GtkCellRenderer *) g_object_new (EGG_TYPE_CELL_RENDERER_KEYS,
					       "editable", TRUE,
					       "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X,
					       NULL);

  g_signal_connect (G_OBJECT (renderer),
		    "accel_edited",
                    G_CALLBACK (accel_edited_callback),
                    WID ("shortcut_treeview"));

  g_signal_connect (G_OBJECT (renderer),
		    "accel_cleared",
                    G_CALLBACK (accel_cleared_callback),
                    WID ("shortcut_treeview"));

  column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL);
  gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL);
  gtk_tree_view_column_set_resizable (column, FALSE);

  gtk_tree_view_append_column (GTK_TREE_VIEW (WID ("shortcut_treeview")), column);
  /* N_COLUMNS is just a place to stick the extra sort function */
  gtk_tree_view_column_set_sort_column_id (column, N_COLUMNS); 
  
  gconf_client_add_dir (client, "/apps/gnome_keybinding_properties", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
  gconf_client_add_dir (client, "/apps/metacity/general", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
  gconf_client_notify_add (client,
			   "/apps/metacity/general/num_workspaces",
			   (GConfClientNotifyFunc) &key_entry_controlling_key_changed,
			   dialog, NULL, NULL);
  g_object_unref (client);

  /* set up the dialog */
  reload_key_entries (wm_common_get_current_window_manager(), dialog);

  widget = WID ("gnome-keybinding-dialog");
  capplet_set_icon (widget, "gnome-settings-keybindings");
  gtk_widget_show (widget);

  g_signal_connect (G_OBJECT (widget), "response", G_CALLBACK(cb_dialog_response), NULL);
  g_signal_connect (G_OBJECT (widget), "close", gtk_main_quit, NULL);
}

int
main (int argc, char *argv[])
{
  GladeXML *dialog;

  gtk_init (&argc, &argv);

  bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  gnome_program_init ("gnome-keybinding-properties", VERSION, LIBGNOMEUI_MODULE, argc, argv,
		      GNOME_PARAM_APP_DATADIR, GNOMECC_DATA_DIR,
		      NULL);

  gnome_theme_init (NULL);

  activate_settings_daemon ();

  dialog = create_dialog ();
  wm_common_register_window_manager_change ((GFunc)(reload_key_entries), dialog);
  setup_dialog (dialog);
  
  gtk_main ();

  return 0;
}
