#include "e.h"

#define TEXT_NONE_ACTION_KEY    _("<None>")
#define TEXT_NO_PARAMS          _("<None>")
#define TEXT_NO_MODIFIER_HEADER _("Single key")

static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd,
                               E_Config_Dialog_Data *cfdata);
static int          _basic_apply_data(E_Config_Dialog *cfd,
                                      E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd,
                                          Evas *evas,
                                          E_Config_Dialog_Data *cfdata);

/********* private functions ***************/
static void         _fill_actions_list(E_Config_Dialog_Data *cfdata);

/**************** Updates ***********/
static int          _update_key_binding_list(E_Config_Dialog_Data *cfdata,
                                             E_Config_Binding_Key *bi);
static void         _update_action_list(E_Config_Dialog_Data *cfdata);
static void         _update_action_params(E_Config_Dialog_Data *cfdata);
static void         _update_buttons(E_Config_Dialog_Data *cfdata);

/**************** Callbacks *********/
static void         _binding_change_cb(void *data);
static void         _action_change_cb(void *data);
static void         _delete_all_key_binding_cb(void *data,
                                               void *data2);
static void         _delete_key_binding_cb(void *data,
                                           void *data2);
static void         _restore_key_binding_defaults_cb(void *data,
                                                     void *data2);
static void         _add_key_binding_cb(void *data,
                                        void *data2);
static void         _modify_key_binding_cb(void *data,
                                           void *data2);

/********* Helper *************************/
static char        *_key_binding_header_get(int modifiers);
static char        *_key_binding_text_get(E_Config_Binding_Key *bi);
static void         _auto_apply_changes(E_Config_Dialog_Data *cfdata);
static void         _find_key_binding_action(const char *action,
                                             const char *params,
                                             int *g,
                                             int *a,
                                             int *n);

/********* Sorting ************************/
static int       _key_binding_sort_cb(const void *d1,
                                      const void *d2);

/**************** grab window *******/
static void      _grab_wnd_show(E_Config_Dialog_Data *cfdata);
static Eina_Bool _grab_key_down_cb(void *data,
                                   int type,
                                   void *event);

struct _E_Config_Dialog_Data
{
   Evas *evas;
   struct
   {
      Eina_List *key;
   } binding;
   struct
   {
      const char    *binding, *action, *cur;
      char          *params;
      int            cur_act, add;
      Eina_Bool     changed E_BITFIELD;

      E_Grab_Dialog *eg;
   } locals;
   struct
   {
      Evas_Object *o_add, *o_mod, *o_del, *o_del_all;
      Evas_Object *o_binding_list, *o_action_list;
      Evas_Object *o_params;
   } gui;

   char            *params;
   E_Config_Dialog *cfd;
};

E_Config_Dialog *
e_int_config_keybindings(Evas_Object *parent EINA_UNUSED,
                         const char *params)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   if (e_config_dialog_find("E", "keyboard_and_mouse/key_bindings")) return NULL;
   v = E_NEW(E_Config_Dialog_View, 1);

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply_data;
   v->basic.create_widgets = _basic_create_widgets;
   v->override_auto_apply = 1;

   cfd = e_config_dialog_new(NULL, _("Key Bindings Settings"), "E",
                             "keyboard_and_mouse/key_bindings",
                             "preferences-desktop-keyboard-shortcuts", 0, v, NULL);
   if ((params) && (params[0]))
     {
        cfd->cfdata->params = strdup(params);
        _add_key_binding_cb(cfd->cfdata, NULL);
     }

   return cfd;
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   Eina_List *l = NULL;
   E_Config_Binding_Key *bi, *bi2;

   cfdata->locals.binding = eina_stringshare_add("");
   cfdata->locals.action = eina_stringshare_add("");
   cfdata->locals.params = strdup("");
   cfdata->locals.cur = NULL;
   cfdata->binding.key = NULL;
   cfdata->locals.eg = NULL;

   EINA_LIST_FOREACH(e_bindings->key_bindings, l, bi)
     {
        if (!bi) continue;

        bi2 = E_NEW(E_Config_Binding_Key, 1);
        bi2->context = bi->context;
        bi2->key = eina_stringshare_add(bi->key);
        bi2->modifiers = bi->modifiers;
        bi2->any_mod = bi->any_mod;
        bi2->action = eina_stringshare_ref(bi->action);
        bi2->params = eina_stringshare_ref(bi->params);

        cfdata->binding.key = eina_list_append(cfdata->binding.key, bi2);
     }
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->cfd = cfd;
   cfdata->locals.cur_act = -1;
   _fill_data(cfdata);

   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd  EINA_UNUSED,
           E_Config_Dialog_Data *cfdata)
{
   E_Config_Binding_Key *bi;

   EINA_LIST_FREE(cfdata->binding.key, bi)
     {
        eina_stringshare_del(bi->key);
        eina_stringshare_del(bi->action);
        eina_stringshare_del(bi->params);
        E_FREE(bi);
     }

   eina_stringshare_del(cfdata->locals.cur);
   eina_stringshare_del(cfdata->locals.binding);
   eina_stringshare_del(cfdata->locals.action);

   free(cfdata->locals.params);
   free(cfdata->params);
   E_FREE(cfdata);
}

static int
_basic_apply_data(E_Config_Dialog *cfd  EINA_UNUSED,
                  E_Config_Dialog_Data *cfdata)
{
   Eina_List *l = NULL;
   E_Config_Binding_Key *bi, *bi2;

   _auto_apply_changes(cfdata);

   e_comp_canvas_keys_ungrab();
   EINA_LIST_FREE(e_bindings->key_bindings, bi)
     {
        e_bindings_key_del(bi->context, bi->key, bi->modifiers, bi->any_mod,
                           bi->action, bi->params);
        if (bi->key) eina_stringshare_del(bi->key);
        if (bi->action) eina_stringshare_del(bi->action);
        if (bi->params) eina_stringshare_del(bi->params);
        E_FREE(bi);
     }

   EINA_LIST_FOREACH(cfdata->binding.key, l, bi2)
     {
        if (!bi2->key || !bi2->key[0]) continue;

        bi = E_NEW(E_Config_Binding_Key, 1);
        bi->context = bi2->context;
        bi->key = eina_stringshare_add(bi2->key);
        bi->modifiers = bi2->modifiers;
        bi->any_mod = bi2->any_mod;
        bi->action =
          ((!bi2->action) || (!bi2->action[0])) ? NULL : eina_stringshare_ref(bi2->action);
        bi->params =
          ((!bi2->params) || (!bi2->params[0])) ? NULL : eina_stringshare_ref(bi2->params);

        e_bindings->key_bindings = eina_list_append(e_bindings->key_bindings, bi);
        e_bindings_key_add(bi->context, bi->key, bi->modifiers, bi->any_mod,
                           bi->action, bi->params);
     }
   e_comp_canvas_keys_grab();
   e_config_save_queue();

   return 1;
}

static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *o, *ot, *of, *ob;

   cfdata->evas = evas;
   o = e_widget_list_add(evas, 0, 1);

   of = e_widget_frametable_add(evas, _("Key Bindings"), 0);
   ob = e_widget_ilist_add(evas, 32, 32, &(cfdata->locals.binding));
   cfdata->gui.o_binding_list = ob;
   e_widget_size_min_set(ob, 200, 200);
   e_widget_frametable_object_append(of, ob, 0, 0, 2, 1, 1, 1, 1, 1);
   ob = e_widget_button_add(evas, _("Add"), "list-add", _add_key_binding_cb, cfdata, NULL);
   cfdata->gui.o_add = ob;
   e_widget_frametable_object_append(of, ob, 0, 1, 1, 1, 1, 0, 1, 0);
   ob = e_widget_button_add(evas, _("Delete"), "list-remove", _delete_key_binding_cb, cfdata, NULL);
   cfdata->gui.o_del = ob;
   e_widget_disabled_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, 1, 1, 1, 1, 0, 1, 0);
   ob = e_widget_button_add(evas, _("Modify"), NULL, _modify_key_binding_cb, cfdata, NULL);
   cfdata->gui.o_mod = ob;
   e_widget_disabled_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 0, 2, 1, 1, 1, 0, 1, 0);
   ob = e_widget_button_add(evas, _("Delete All"), "edit-clear", _delete_all_key_binding_cb, cfdata, NULL);
   cfdata->gui.o_del_all = ob;
   e_widget_disabled_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, 2, 1, 1, 1, 0, 1, 0);
   ob = e_widget_button_add(evas, _("Restore Default Bindings"), "enlightenment", _restore_key_binding_defaults_cb, cfdata, NULL);
   e_widget_frametable_object_append(of, ob, 0, 3, 2, 1, 1, 0, 1, 0);
   e_widget_list_object_append(o, of, 1, 1, 0.5);

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);
   of = e_widget_framelist_add(evas, _("Action"), 0);
   ob = e_widget_ilist_add(evas, 24, 24, &(cfdata->locals.action));
   cfdata->gui.o_action_list = ob;
   e_widget_size_min_set(ob, 200, 280);
   e_widget_framelist_object_append(of, ob);
   e_widget_table_object_append(ot, of, 0, 0, 1, 1, 1, 1, 1, 1);

   of = e_widget_framelist_add(evas, _("Action Params"), 0);
   ob = e_widget_entry_add(cfd->dia->win, &(cfdata->locals.params), NULL, NULL, NULL);
   cfdata->gui.o_params = ob;
   e_widget_disabled_set(ob, 1);
   e_widget_framelist_object_append(of, ob);
   e_widget_table_object_append(ot, of, 0, 1, 1, 1, 1, 1, 1, 0);
   e_widget_list_object_append(o, ot, 1, 1, 0.5);

   _update_key_binding_list(cfdata, NULL);
   _fill_actions_list(cfdata);

   e_dialog_resizable_set(cfd->dia, 1);
   return o;
}

static void
_fill_actions_list(E_Config_Dialog_Data *cfdata)
{
   char buf[1024];
   Eina_List *l, *l2;
   E_Action_Group *actg;
   E_Action_Description *actd;
   int g, a;

   evas_event_freeze(evas_object_evas_get(cfdata->gui.o_action_list));
   edje_freeze();
   e_widget_ilist_freeze(cfdata->gui.o_action_list);

   e_widget_ilist_clear(cfdata->gui.o_action_list);
   for (l = e_action_groups_get(), g = 0; l; l = l->next, g++)
     {
        actg = l->data;

        if (!actg->acts) continue;

        e_widget_ilist_header_append(cfdata->gui.o_action_list, NULL, _(actg->act_grp));

        for (l2 = actg->acts, a = 0; l2; l2 = l2->next, a++)
          {
             actd = l2->data;

             snprintf(buf, sizeof(buf), "%d %d", g, a);
             e_widget_ilist_append(cfdata->gui.o_action_list, NULL, _(actd->act_name),
                                   _action_change_cb, cfdata, buf);
          }
     }
   e_widget_ilist_go(cfdata->gui.o_action_list);
   e_widget_ilist_thaw(cfdata->gui.o_action_list);
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(cfdata->gui.o_action_list));
}

/**************** Callbacks *********/

static void
_add_key_binding_cb(void *data,
                    void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;

   _auto_apply_changes(cfdata);

   cfdata->locals.add = 1;
   _grab_wnd_show(cfdata);
}

static void
_modify_key_binding_cb(void *data,
                       void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;

   _auto_apply_changes(cfdata);

   cfdata->locals.add = 0;
   _grab_wnd_show(cfdata);
}

static void
_binding_change_cb(void *data)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;

   if (cfdata->locals.changed) _auto_apply_changes(cfdata);
   eina_stringshare_del(cfdata->locals.cur);
   cfdata->locals.cur = NULL;
   cfdata->locals.cur_act = -1;

   cfdata->locals.changed = 0;
   if ((!cfdata->locals.binding) || (!cfdata->locals.binding[0])) return;

   cfdata->locals.cur = eina_stringshare_ref(cfdata->locals.binding);

   _update_buttons(cfdata);
   _update_action_list(cfdata);
}

static void
_action_change_cb(void *data)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   _update_action_params(cfdata);
   cfdata->locals.cur_act = e_widget_ilist_selected_get(cfdata->gui.o_action_list);
   cfdata->locals.changed = 1;
}

static void
_delete_all_key_binding_cb(void *data,
                           void *data2 EINA_UNUSED)
{
   E_Config_Binding_Key *bi;
   E_Config_Dialog_Data *cfdata;

   cfdata = data;

   /* FIXME: need confirmation dialog */
   EINA_LIST_FREE(cfdata->binding.key, bi)
     {
        eina_stringshare_del(bi->key);
        eina_stringshare_del(bi->action);
        eina_stringshare_del(bi->params);
        E_FREE(bi);
     }

   eina_stringshare_del(cfdata->locals.cur);
   cfdata->locals.cur = NULL;

   e_widget_ilist_clear(cfdata->gui.o_binding_list);
   e_widget_ilist_go(cfdata->gui.o_binding_list);
   e_widget_ilist_unselect(cfdata->gui.o_action_list);
   e_widget_entry_clear(cfdata->gui.o_params);
   e_widget_disabled_set(cfdata->gui.o_params, 1);

   _update_buttons(cfdata);
}

static void
_delete_key_binding_cb(void *data,
                       void *data2 EINA_UNUSED)
{
   Eina_List *l = NULL;
   const char *n;
   int sel;
   E_Config_Dialog_Data *cfdata;
   E_Config_Binding_Key *bi;

   cfdata = data;

   sel = e_widget_ilist_selected_get(cfdata->gui.o_binding_list);
   if (cfdata->locals.binding[0] == 'k')
     {
        n = cfdata->locals.binding;
        l = eina_list_nth_list(cfdata->binding.key, atoi(++n));

        /* FIXME: need confirmation dialog */
        if (l)
          {
             bi = eina_list_data_get(l);
             eina_stringshare_del(bi->key);
             eina_stringshare_del(bi->action);
             eina_stringshare_del(bi->params);
             E_FREE(bi);

             cfdata->binding.key = eina_list_remove_list(cfdata->binding.key, l);
          }
     }

   _update_key_binding_list(cfdata, NULL);

   if (sel >= e_widget_ilist_count(cfdata->gui.o_binding_list))
     sel = e_widget_ilist_count(cfdata->gui.o_binding_list) - 1;

   eina_stringshare_del(cfdata->locals.cur);
   cfdata->locals.cur = NULL;

   if (sel < 0)
     {
        e_widget_ilist_unselect(cfdata->gui.o_action_list);
        e_widget_entry_clear(cfdata->gui.o_params);
        e_widget_disabled_set(cfdata->gui.o_params, 1);
        _update_buttons(cfdata);
     }
   else
     {
        e_widget_ilist_selected_set(cfdata->gui.o_binding_list, sel);
        e_widget_ilist_nth_show(cfdata->gui.o_binding_list, sel, 0);
     }
}

static void
_restore_key_binding_defaults_cb(void *data,
                                 void *data2 EINA_UNUSED)
{
   E_Config_Bindings *ecb;
   Eina_Stringshare *prof;
   E_Config_Dialog_Data *cfdata = data;

   ecb = e_config_domain_system_load("e_bindings", e_config_descriptor_find("E_Config_Bindings"));
   if (!ecb)
     {
        const char *type;

        prof = eina_stringshare_ref(e_config_profile_get());
        switch (e_config->config_type)
          {
           case E_CONFIG_PROFILE_TYPE_DESKTOP:
             type = "standard";
             break;
           case E_CONFIG_PROFILE_TYPE_MOBILE:
             type = "mobile";
             break;
           //case E_CONFIG_PROFILE_TYPE_TABLET: FIXME - not used
           default:
             type = "default";
             break;
          }
        e_config_profile_set(type);
        ecb = e_config_domain_system_load("e_bindings", e_config_descriptor_find("E_Config_Bindings"));
        e_config_profile_set(prof);
        eina_stringshare_del(prof);
     }
   if (!ecb) return;
   E_FREE_LIST(cfdata->binding.key, e_config_binding_key_free);
   cfdata->binding.key = ecb->key_bindings, ecb->key_bindings = NULL;
   e_config_bindings_free(ecb);

   eina_stringshare_del(cfdata->locals.cur);
   cfdata->locals.cur = NULL;

   _update_key_binding_list(cfdata, NULL);
   _update_buttons(cfdata);

   e_widget_ilist_unselect(cfdata->gui.o_action_list);
   e_widget_entry_clear(cfdata->gui.o_params);
   e_widget_disabled_set(cfdata->gui.o_params, 1);
}

/**************** Updates ***********/
static void
_update_action_list(E_Config_Dialog_Data *cfdata)
{
   E_Config_Binding_Key *bi;
   int j = -1, i, n, cnt;
   const char *action, *params;

   if (!cfdata->locals.cur) return;

   if (cfdata->locals.cur[0] == 'k')
     {
        sscanf(cfdata->locals.cur, "k%d", &n);
        bi = eina_list_nth(cfdata->binding.key, n);
        if (!bi)
          {
             e_widget_ilist_unselect(cfdata->gui.o_action_list);
             e_widget_entry_clear(cfdata->gui.o_params);
             e_widget_disabled_set(cfdata->gui.o_params, 1);
             return;
          }
        action = bi->action;
        params = bi->params;
     }
   else
     return;

   _find_key_binding_action(action, params, NULL, NULL, &j);

   if (j >= 0)
     {
        cnt = e_widget_ilist_count(cfdata->gui.o_action_list);
        for (i = 0; i < cnt; i++)
          {
             if (i > j) break;
             if (e_widget_ilist_nth_is_header(cfdata->gui.o_action_list, i)) j++;
          }
     }

   if (j >= 0)
     {
        if (j == e_widget_ilist_selected_get(cfdata->gui.o_action_list))
          _update_action_params(cfdata);
        else
          e_widget_ilist_selected_set(cfdata->gui.o_action_list, j);
     }
   else
     {
        e_widget_ilist_unselect(cfdata->gui.o_action_list);
        eina_stringshare_del(cfdata->locals.action);
        cfdata->locals.action = eina_stringshare_add("");
        e_widget_entry_clear(cfdata->gui.o_params);
     }

   /*if (cfdata->locals.cur[0] == 'k')
      {
        sscanf(cfdata->locals.cur, "k%d", &n);
        bi = eina_list_nth(cfdata->binding.key, n);
        if (!bi)
          {
             e_widget_ilist_unselect(cfdata->gui.o_action_list);
             e_widget_entry_clear(cfdata->gui.o_params);
             e_widget_disabled_set(cfdata->gui.o_params, 1);
             return;
          }

        _find_key_binding_action(bi, NULL, NULL, &j);
        if (j >= 0)
          {
             for (i = 0; i < e_widget_ilist_count(cfdata->gui.o_action_list); i++)
               {
                  if (i > j) break;
                  if (e_widget_ilist_nth_is_header(cfdata->gui.o_action_list, i)) j++;
               }
          }

        if (j >= 0)
          {
             if (j == e_widget_ilist_selected_get(cfdata->gui.o_action_list))
               _update_action_params(cfdata);
             else
               e_widget_ilist_selected_set(cfdata->gui.o_action_list, j);
          }
        else
          {
             e_widget_ilist_unselect(cfdata->gui.o_action_list);
             free(cfdata->locals.action);
             cfdata->locals.action = strdup("");
             e_widget_entry_clear(cfdata->gui.o_params);
          }
      }*/
}

static void
_update_action_params(E_Config_Dialog_Data *cfdata)
{
   int g, a, b;
   E_Action_Group *actg;
   E_Action_Description *actd;
   E_Config_Binding_Key *bi;
   const char *action, *params;

#define KB_EXAMPLE_PARAMS                                           \
  if ((!actd->param_example) || (!actd->param_example[0]))          \
    e_widget_entry_text_set(cfdata->gui.o_params, TEXT_NO_PARAMS);  \
  else                                                              \
    e_widget_entry_text_set(cfdata->gui.o_params, actd->param_example)

   if ((!cfdata->locals.action) || (!cfdata->locals.action[0]))
     {
        e_widget_disabled_set(cfdata->gui.o_params, 1);
        e_widget_entry_clear(cfdata->gui.o_params);
        return;
     }
   sscanf(cfdata->locals.action, "%d %d", &g, &a);

   actg = eina_list_nth(e_action_groups_get(), g);
   if (!actg) return;
   actd = eina_list_nth(actg->acts, a);
   if (!actd) return;

   if (actd->act_params)
     {
        e_widget_disabled_set(cfdata->gui.o_params, 1);
        e_widget_entry_text_set(cfdata->gui.o_params, actd->act_params);
        return;
     }

   if ((!cfdata->locals.cur) || (!cfdata->locals.cur[0]))
     {
        e_widget_disabled_set(cfdata->gui.o_params, 1);
        KB_EXAMPLE_PARAMS;
        return;
     }

   if (!actd->editable)
     e_widget_disabled_set(cfdata->gui.o_params, 1);
   else
     e_widget_disabled_set(cfdata->gui.o_params, 0);

   if (cfdata->locals.cur[0] == 'k')
     {
        sscanf(cfdata->locals.cur, "k%d", &b);
        bi = eina_list_nth(cfdata->binding.key, b);
        if (!bi)
          {
             e_widget_disabled_set(cfdata->gui.o_params, 1);
             KB_EXAMPLE_PARAMS;
             return;
          }
        action = bi->action;
        params = bi->params;
     }
   else
     {
        e_widget_disabled_set(cfdata->gui.o_params, 1);
        KB_EXAMPLE_PARAMS;
        return;
     }

   if (action)
     {
        if (!e_util_strcmp(action, actd->act_cmd))
          {
             if ((cfdata->locals.cur_act >= 0) && (cfdata->locals.cur_act != e_widget_ilist_selected_get(cfdata->gui.o_action_list)))
               KB_EXAMPLE_PARAMS;
             else
               e_widget_entry_text_set(cfdata->gui.o_params, params);
          }
        else
          KB_EXAMPLE_PARAMS;
     }
   else
     KB_EXAMPLE_PARAMS;
}

static int
_update_key_binding_list(E_Config_Dialog_Data *cfdata,
                         E_Config_Binding_Key *bi_new)
{
   int i;
   char *b, b2[64];
   Eina_List *l;
   E_Config_Binding_Key *bi;
   int modifiers = -1;
   int bi_pos = 0;
   int ret = -1;

   evas_event_freeze(evas_object_evas_get(cfdata->gui.o_binding_list));
   edje_freeze();
   e_widget_ilist_freeze(cfdata->gui.o_binding_list);

   e_widget_ilist_clear(cfdata->gui.o_binding_list);
   e_widget_ilist_go(cfdata->gui.o_binding_list);

   if (cfdata->binding.key)
     {
        cfdata->binding.key = eina_list_sort(cfdata->binding.key,
                                             eina_list_count(cfdata->binding.key), _key_binding_sort_cb);
     }

   for (l = cfdata->binding.key, i = 0; l; l = l->next, i++)
     {
        bi = l->data;
        if (bi == bi_new) ret = bi_pos;
        if (ret < 0) bi_pos++;

        if (modifiers != (int)bi->modifiers)
          {
             modifiers = bi->modifiers;
             b = _key_binding_header_get(modifiers);
             if (b)
               {
                  if (ret < 0) bi_pos++;
                  e_widget_ilist_header_append(cfdata->gui.o_binding_list, NULL, b);
                  free(b);
               }
          }

        b = _key_binding_text_get(bi);
        if (!b) continue;

        snprintf(b2, sizeof(b2), "k%d", i);
        e_widget_ilist_append(cfdata->gui.o_binding_list, NULL, b,
                              _binding_change_cb, cfdata, b2);
        free(b);
     }
   e_widget_ilist_go(cfdata->gui.o_binding_list);

   e_widget_ilist_thaw(cfdata->gui.o_binding_list);
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(cfdata->gui.o_binding_list));

   if (eina_list_count(cfdata->binding.key))
     e_widget_disabled_set(cfdata->gui.o_del_all, 0);
   else
     e_widget_disabled_set(cfdata->gui.o_del_all, 1);

   return ret;
}

static void
_update_buttons(E_Config_Dialog_Data *cfdata)
{
   if (e_widget_ilist_count(cfdata->gui.o_binding_list))
     e_widget_disabled_set(cfdata->gui.o_del_all, 0);
   else
     e_widget_disabled_set(cfdata->gui.o_del_all, 1);

   if (!cfdata->locals.cur)
     {
        e_widget_disabled_set(cfdata->gui.o_mod, 1);
        e_widget_disabled_set(cfdata->gui.o_del, 1);
        return;
     }
   e_widget_disabled_set(cfdata->gui.o_mod, 0);
   e_widget_disabled_set(cfdata->gui.o_del, 0);
}

/*************** Sorting *****************************/
static int
_key_binding_sort_cb(const void *d1,
                     const void *d2)
{
   int i, j;
   const E_Config_Binding_Key *bi, *bi2;

   bi = d1;
   bi2 = d2;

   i = 0; j = 0;
   if (bi->modifiers & E_BINDING_MODIFIER_CTRL) i++;
   if (bi->modifiers & E_BINDING_MODIFIER_ALT) i++;
   if (bi->modifiers & E_BINDING_MODIFIER_SHIFT) i++;
   if (bi->modifiers & E_BINDING_MODIFIER_WIN) i++;

   if (bi2->modifiers & E_BINDING_MODIFIER_CTRL) j++;
   if (bi2->modifiers & E_BINDING_MODIFIER_ALT) j++;
   if (bi2->modifiers & E_BINDING_MODIFIER_SHIFT) j++;
   if (bi2->modifiers & E_BINDING_MODIFIER_WIN) j++;

   if (i < j) return -1;
   else if (i > j)
     return 1;

   if (bi->modifiers < bi2->modifiers) return -1;
   else if (bi->modifiers > bi2->modifiers)
     return 1;

   i = strlen(bi->key ? bi->key : "");
   j = strlen(bi2->key ? bi2->key : "");

   if (i < j) return -1;
   else if (i > j)
     return 1;

   i = e_util_strcmp(bi->key, bi2->key);
   if (i < 0) return -1;
   else if (i > 0)
     return 1;

   return 0;
}

/**************** grab window *******/

static void
_grab_wnd_hide(void *data)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = e_object_data_get(data);
   cfdata->locals.eg = NULL;
}

static void
_grab_wnd_show(E_Config_Dialog_Data *cfdata)
{
   if (cfdata->locals.eg) return;
   cfdata->locals.eg = e_grab_dialog_show(cfdata->cfd->dia->win, EINA_FALSE, _grab_key_down_cb, NULL, NULL, cfdata);
   e_object_data_set(E_OBJECT(cfdata->locals.eg), cfdata);
   e_object_del_attach_func_set(E_OBJECT(cfdata->locals.eg), _grab_wnd_hide);
}

static Eina_Bool
_grab_key_down_cb(void *data,
                  EINA_UNUSED int type,
                  void *event)
{
   E_Config_Dialog_Data *cfdata;
   Ecore_Event_Key *ev;

   ev = event;
   cfdata = data;

   if ((ev->keyname) && (ev->key) && (ev->compose))
     printf("'%s' '%s' '%s'\n", ev->key, ev->key, ev->compose);
   else if ((ev->keyname) && (ev->key))
     printf("'%s' '%s'\n", ev->keyname, ev->key);
   else
     printf("unknown key!!!!\n");
   if (!e_util_strcmp(ev->key, "Control_L") || !e_util_strcmp(ev->key, "Control_R") ||
       !e_util_strcmp(ev->key, "Shift_L") || !e_util_strcmp(ev->key, "Shift_R") ||
       !e_util_strcmp(ev->key, "Alt_L") || !e_util_strcmp(ev->key, "Alt_R") ||
       !e_util_strcmp(ev->key, "Super_L") || !e_util_strcmp(ev->key, "Super_R"))
     {
        /* Do nothing */
     }
   else
     {
        E_Config_Binding_Key *bi = NULL;
        const Eina_List *l = NULL;
        unsigned int mod = E_BINDING_MODIFIER_NONE;
        unsigned int n, found = 0;
        const char *key = ev->key;

        if (!e_bindings_key_allowed(ev->key))
          return ECORE_CALLBACK_PASS_ON;

        if (ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT)
          mod |= E_BINDING_MODIFIER_SHIFT;
        if (ev->modifiers & ECORE_EVENT_MODIFIER_CTRL)
          mod |= E_BINDING_MODIFIER_CTRL;
        if (ev->modifiers & ECORE_EVENT_MODIFIER_ALT)
          mod |= E_BINDING_MODIFIER_ALT;
        if (ev->modifiers & ECORE_EVENT_MODIFIER_WIN)
          mod |= E_BINDING_MODIFIER_WIN;

        /* swap for un-shifted key; binding '(' or ')' is impossible */
        if ((ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT) &&
            (eina_streq(ev->key, "parenleft") || eina_streq(ev->key, "parenright")))
          ev->key = ev->keyname;
        if (cfdata->locals.add)
          found = !!e_util_binding_match(cfdata->binding.key, ev, &n, NULL);
        else if (cfdata->locals.cur && cfdata->locals.cur[0])
          {
             sscanf(cfdata->locals.cur, "k%d", &n);
             bi = eina_list_nth(cfdata->binding.key, n);
             found = !!e_util_binding_match(cfdata->binding.key, ev, &n, bi);
          }

        if (!found)
          {
             if (cfdata->locals.add)
               {
                  bi = E_NEW(E_Config_Binding_Key, 1);

                  bi->context = E_BINDING_CONTEXT_ANY;
                  bi->modifiers = mod;
                  bi->key = eina_stringshare_add(ev->key);
                  bi->action = NULL;
                  bi->params = NULL;
                  bi->any_mod = 0;

                  cfdata->binding.key = eina_list_append(cfdata->binding.key, bi);

                  n = _update_key_binding_list(cfdata, bi);

                  e_widget_ilist_selected_set(cfdata->gui.o_binding_list, n);
                  e_widget_ilist_nth_show(cfdata->gui.o_binding_list, n, 0);
                  e_widget_ilist_unselect(cfdata->gui.o_action_list);
                  eina_stringshare_replace(&cfdata->locals.action, "");
                  if ((cfdata->params) && (cfdata->params[0]))
                    {
                       int j, g = -1;
                       _find_key_binding_action("exec", NULL, &g, NULL, &j);
                       if (j >= 0)
                         {
                            e_widget_ilist_unselect(cfdata->gui.o_action_list);
                            e_widget_ilist_selected_set(cfdata->gui.o_action_list, (j + g + 1));
                            e_widget_entry_clear(cfdata->gui.o_params);
                            e_widget_entry_text_set(cfdata->gui.o_params, cfdata->params);
                         }
                    }
                  else
                    {
                       e_widget_entry_clear(cfdata->gui.o_params);
                       e_widget_disabled_set(cfdata->gui.o_params, 1);
                    }
               }
             else if (cfdata->locals.cur && cfdata->locals.cur[0])
               {
                  char *label;
                  E_Ilist_Item *it;
                  unsigned int i = 0;

                  sscanf(cfdata->locals.cur, "k%d", &n);
                  bi = eina_list_nth(cfdata->binding.key, n);

                  bi->modifiers = mod;
                  if (bi->key) eina_stringshare_del(bi->key);
                  bi->key = eina_stringshare_add(ev->key);
                  printf("blub\n");

                  label = _key_binding_text_get(bi);

                  EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->gui.o_binding_list), l, it)
                    {
                       if (it->header) n++;
                       if (i++ >= n) break;
                    }

                  e_widget_ilist_nth_label_set(cfdata->gui.o_binding_list, n, label);
                  free(label);
               }
          }
        else
          {
             unsigned int i = 0;
             E_Ilist_Item *it;
#if 0
             /* this advice is rather irritating as one sees that the
                key is bound to an action. if you want to set a
                keybinding you dont care about whether there is
                sth else set to it. */
             int g, a, j;
             const char *label = NULL;
             E_Action_Group *actg = NULL;
             E_Action_Description *actd = NULL;

             if (cfdata->locals.add)
               _find_key_binding_action(bi->action, bi->params, &g, &a, &j);
             else
               _find_key_binding_action(bi2->action, bi2->params, &g, &a, &j);

             actg = eina_list_nth(e_action_groups_get(), g);
             if (actg) actd = eina_list_nth(actg->acts, a);

             if (actd) label = _(actd->act_name);

             e_util_dialog_show(_("Binding Key Error"),
                                _("The binding key combination that you chose"
                                  " is already used by <ps/>"
                                  "<hilight>%s</hilight> action.<ps/>"
                                  "Please choose another binding key combination."),
                                label ? label : _("Unknown"));
#endif
             EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->gui.o_binding_list), l, it)
               {
                  if (it->header) n++;
                  if (i++ >= n) break;
               }

             e_widget_ilist_nth_show(cfdata->gui.o_binding_list, n, 1);
             e_widget_ilist_selected_set(cfdata->gui.o_binding_list, n);
          }
        e_object_del(E_OBJECT(cfdata->locals.eg));
        ev->key = key;
     }
   return ECORE_CALLBACK_PASS_ON;
}


/********** Helper *********************************/
static void
_auto_apply_changes(E_Config_Dialog_Data *cfdata)
{
   int n, g, a, ok = 0;
   E_Config_Binding_Key *bi;
   E_Action_Group *actg;
   E_Action_Description *actd;

   if ((!cfdata->locals.cur) || (!cfdata->locals.cur[0]) ||
       (!cfdata->locals.action) || (!cfdata->locals.action[0])) return;

   sscanf(cfdata->locals.cur, "k%d", &n);
   sscanf(cfdata->locals.action, "%d %d", &g, &a);

   bi = eina_list_nth(cfdata->binding.key, n);
   if (!bi) return;

   actg = eina_list_nth(e_action_groups_get(), g);
   if (!actg) return;
   actd = eina_list_nth(actg->acts, a);
   if (!actd) return;

   eina_stringshare_replace(&bi->action, actd->act_cmd);
   eina_stringshare_replace(&bi->params, actd->act_params);
   if (bi->params) return;
   if (cfdata->locals.params)
     {
        ok = 1;
        if (!e_util_strcmp(cfdata->locals.params, TEXT_NO_PARAMS))
          ok = 0;

        else if ((actd->param_example) && (!e_util_strcmp(cfdata->locals.params, actd->param_example)))
          ok = 0;
     }

   if (ok)
     bi->params = eina_stringshare_add(cfdata->locals.params);
}

static void
_find_key_binding_action(const char *action,
                         const char *params,
                         int *g,
                         int *a,
                         int *n)
{
   Eina_List *l, *l2;
   int gg = -1, aa = -1, nn = -1, found;
   E_Action_Group *actg;
   E_Action_Description *actd;

   if (g) *g = -1;
   if (a) *a = -1;
   if (n) *n = -1;

   found = 0;
   for (l = e_action_groups_get(), gg = 0, nn = 0; l; l = l->next, gg++)
     {
        actg = l->data;

        for (l2 = actg->acts, aa = 0; l2; l2 = l2->next, aa++)
          {
             actd = l2->data;
             if (!e_util_strcmp((!action ? "" : action), (!actd->act_cmd ? "" : actd->act_cmd)))
               {
                  if (!params || !params[0])
                    {
                       if ((!actd->act_params) || (!actd->act_params[0]))
                         {
                            if (g) *g = gg;
                            if (a) *a = aa;
                            if (n) *n = nn;
                            return;
                         }
                       else
                         continue;
                    }
                  else
                    {
                       if ((!actd->act_params) || (!actd->act_params[0]))
                         {
                            if (g) *g = gg;
                            if (a) *a = aa;
                            if (n) *n = nn;
                            found = 1;
                         }
                       else
                         {
                            if (!e_util_strcmp(params, actd->act_params))
                              {
                                 if (g) *g = gg;
                                 if (a) *a = aa;
                                 if (n) *n = nn;
                                 return;
                              }
                         }
                    }
               }
             nn++;
          }
        if (found) break;
     }

   if (!found)
     {
        if (g) *g = -1;
        if (a) *a = -1;
        if (n) *n = -1;
     }
}

static void
_modifiers_add(Eina_Strbuf *b, int modifiers)
{
   if (modifiers & E_BINDING_MODIFIER_CTRL)
     {
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " + ");
        eina_strbuf_append(b, _("CTRL"));
     }
   if (modifiers & E_BINDING_MODIFIER_ALT)
     {
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " + ");
        eina_strbuf_append(b, _("ALT"));
     }

   if (modifiers & E_BINDING_MODIFIER_SHIFT)
     {
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " + ");
        eina_strbuf_append(b, _("SHIFT"));
     }

   if (modifiers & E_BINDING_MODIFIER_WIN)
     {
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " + ");
        eina_strbuf_append(b, _("WIN"));
     }
}

static char *
_key_binding_header_get(int modifiers)
{
   Eina_Strbuf *b;
   char *ret;

   b = eina_strbuf_new();
   _modifiers_add(b, modifiers);

   ret = eina_strbuf_string_steal(b);
   eina_strbuf_free(b);
   if (ret[0]) return ret;
   free(ret);
   return strdup(TEXT_NO_MODIFIER_HEADER);
}

static char *
_key_binding_text_get(E_Config_Binding_Key *bi)
{
   Eina_Strbuf *b;
   char *ret;

   if (!bi) return NULL;

   b = eina_strbuf_new();
   _modifiers_add(b, bi->modifiers);

   if (bi->key && bi->key[0])
     {
        char *l;
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " + ");

        l = strdup(bi->key);
        l[0] = (char)toupper(bi->key[0]);
        eina_strbuf_append(b, l);
        free(l);
     }

   /* see comment in e_bindings on numlock
      if (bi->modifiers & ECORE_X_LOCK_NUM)
      {
        if (eina_strbuf_length_get(b)) eina_strbuf_append(b, " ");
        eina_strbuf_append(b, _("OFF"));
      }
    */

   ret = eina_strbuf_string_steal(b);
   eina_strbuf_free(b);
   if (ret[0]) return ret;
   free(ret);
   return strdup(TEXT_NONE_ACTION_KEY);
}

