/*
 * Copyright (C) 2000-2022 the xine project
 *
 * This file is part of xine, a unix video player.
 *
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * xine 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>

#include "common.h"
#include "kbindings.h"
#include "videowin.h"
#include "actions.h"
#include "event.h"
#include "errors.h"
#include "xine-toolkit/backend.h"
#include "xine-toolkit/label.h"
#include "xine-toolkit/labelbutton.h"
#include "xine-toolkit/button.h"
#include "xine-toolkit/browser.h"
#include "kbindings_common.h"

#undef TRACE_KBINDINGS

#define _DYN_STRING_STEP 4096

typedef struct {
  char *buf;
  uint32_t used, have;
} _dyn_string_t;

static void _dyn_string_init (_dyn_string_t *ds) {
  ds->buf = malloc (_DYN_STRING_STEP);
  if (ds->buf) {
    ds->buf[0] = 0;
    ds->have = _DYN_STRING_STEP;
  } else {
    ds->have = 0;
  }
  ds->used = 0;
}

static void _dyn_string_clear (_dyn_string_t *ds) {
  if (ds->buf)
    ds->buf[0] = 0;
  ds->used = 0;
}

static void _dyn_string_deinit (_dyn_string_t *ds) {
  SAFE_FREE (ds->buf);
  ds->used = 0;
  ds->have = 0;
}

static char *_dyn_string_get (_dyn_string_t *ds) {
  if (ds->used) {
    ds->used += 1;
    ds->buf[ds->used] = 0;
  }
  return (char *)(uintptr_t)ds->used;
}
  
static void _dyn_string_append (_dyn_string_t *ds, const char *s) {
  size_t ls = strlen (s), ln = ds->used + ls;
  if (ln + 2 > ds->have) {
    char *ns;
    ln += 2 + _DYN_STRING_STEP - 1;
    ln &= ~(_DYN_STRING_STEP - 1);
    ns = realloc (ds->buf, ln);
    if (!ns)
      return;
    ds->buf = ns;
    ds->have = ln;
  }
  memcpy (ds->buf + ds->used, s, ls + 1);
  ds->used += ls;
}

static void _dyn_string_fix (_dyn_string_t *ds, char **s, uint32_t n) {
  while (n) {
    *s = ds->buf + (uintptr_t)*s;
    s++;
    n--;
  }
}

typedef enum {
  _W_browser = 0,
  /* keep order */
  _W_edit,
  _W_alias,
  _W_delete,
  _W_grab,
  /* /keep order */
  _W_save,
  _W_reset,
  _W_done,
  _W_comment,
  _W_key,
  /* keep order */
  _W_ctrl,
  _W_meta,
  _W_mod3,
  _W_mod4,
  _W_mod5,
  /* /keep order */
  _W_LAST
} _W_t;

struct xui_keyedit_s {
  gGui_t               *gui;

  kbinding_t           *kbt;

  xitk_window_t        *xwin;
  xitk_widget_list_t   *widget_list;
  int                   visible;

  int                   nsel;
  const kbinding_entry_t *ksel;

  xitk_widget_t        *w[_W_LAST];

  int                   num_entries;
  char                **entries;
  char                **shortcuts;
  _dyn_string_t         ebuf;

  int                   grabbing;
  enum {
    KBEDIT_NOOP = 0,
    KBEDIT_ALIASING,
    KBEDIT_EDITING
  }                     action_wanted;

  xitk_register_key_t   kreg;

  struct {
    xitk_register_key_t key;
    kbinding_entry_t    entry;
    xitk_window_t      *xwin;
  }                     kbr;
};

#define WINDOW_WIDTH        576
#define WINDOW_HEIGHT       456
#define MAX_DISP_ENTRIES    12

/*
  Remap file entries syntax are:
  ...
  WindowReduce {
      key = less
      modifier = none
  }

  Alias {
      entry = WindowReduce
      key = w
      modifier = control
  }
  ...
  WindowReduce action key is <less>, with no modifier usage.
  There is an alias: C-w (control w> keystroke will have same
                     action than WindowReduce.

  modifier entry is optional.
  modifier key can be multiple:
 ...
  WindowReduce {
      key = less
      modifier = control, meta
  }
  ...
  Here, WindowReduce action keystroke will be CM-<

  Key modifier name are:
    none, control, ctrl, meta, alt, mod3, mod4, mod5.

  shift,lock and numlock modifier are voluntary not handled.

*/


static void _kbindings_display_kbindings_to_stream (gGui_t *gui, kbinding_t *kbt, FILE *stream, kbedit_display_mode_t mode) {
  int i, num_entries = _kbindings_get_num_entries (kbt);

  if(kbt == NULL) {
    gui_msg (gui, XUI_MSG_ERROR, _("OOCH: key binding table is NULL.\n"));
    return;
  }

  switch(mode) {
  case KBT_DISPLAY_MODE_LIRC:
    fprintf(stream, "##\n# xine key bindings.\n"
		    "# Automatically generated by %s version %s.\n##\n\n", PACKAGE, VERSION);

    for (i = 0; i < num_entries; i++) {
      char buf[2048], *p = buf, *e = buf + sizeof (buf) - 2 - 7 - 16 - 16 - 15 - 12 - 10 - 7;
      const kbinding_entry_t *entry = _kbindings_get_entry (kbt, i);
      if (!entry->is_alias) {
        memcpy (p, "# ", 2); p += 2; e += 2;
        p += strlcpy (p, entry->comment, e - p);
        if (p > e)
          p = e;
        memcpy (p, "\nbegin\n"
                   "\tremote = xxxxx\n"
                   "\tbutton = xxxxx\n"
                   "\tprog   = xine\n"
                   "\trepeat = 0\n"
                   "\tconfig = ", 7 + 16 + 16 + 15 + 12 + 10);
        p += 7 + 16 + 16 + 15 + 12 + 10;
        e += 7 + 16 + 16 + 15 + 12 + 10;
        p += strlcpy (p, entry->action, e - p);
        if (p > e)
          p = e;
        memcpy (p, "\nend\n\n", 7); p += 7;
        fputs (buf, stream);
      }
    }
    fprintf(stream, "##\n# End of xine key bindings.\n##\n");
    break;

  case KBT_DISPLAY_MODE_MAN:
    /* NOTE: this replaces kbinding_man, which would need to link in most stuff now anyway. */

    for (i = 0; i < num_entries; i++) {
      char buf[2048], *p = buf, *e = buf + sizeof (buf) - 5 - 2 * 9 - 3 * 10 - 3 - 2 - 7 - 2 - 5 - 1;
      const kbinding_entry_t *entry = _kbindings_get_entry (kbt, i);

      if (entry->is_alias || !strcasecmp (entry->key, "VOID"))
        continue;

      memcpy (p, ".IP \"", 5); p += 5; e += 5;
      if (entry->modifier != KEYMOD_NOMOD) {
        if (entry->modifier & KEYMOD_CONTROL) {
          memcpy (p, "\\fBC\\fP\\-", 9); p += 9; e += 9;
        }
        if (entry->modifier & KEYMOD_META) {
          memcpy (p, "\\fBM\\fP\\-", 9); p += 9; e += 9;
        }
        if (entry->modifier & KEYMOD_MOD3) {
          memcpy (p, "\\fBM3\\fP\\-", 10); p += 10; e += 10;
        }
        if (entry->modifier & KEYMOD_MOD4) {
          memcpy (p, "\\fBM4\\fP\\-", 10); p += 10; e += 10;
        }
        if (entry->modifier & KEYMOD_MOD5) {
          memcpy (p, "\\fBM5\\fP\\-", 10); p += 10; e += 10;
        }
      }
      memcpy (p, "\\fB", 3); p += 3; e += 3;
      if (entry->key[0] && entry->key[1]) {
        memcpy (p, "\\<", 2); p += 2; e += 2;
      }
      if (!strncmp (entry->key, "KP_", 3)) {
        memcpy (p, "Keypad ", 7); p += 7; e += 7;
        p += strlcpy (p, entry->key + 3, e - p);
      } else {
        p += strlcpy (p, entry->key, e - p);
      }
      if (p > e)
        p = e;
      if (entry->key[0] && entry->key[1]) {
        memcpy (p, "\\>", 2); p += 2; e += 2;
      }
      memcpy (p, "\\fP\"\n", 5); p += 5; e += 5;
      p += strlcpy (p, entry->comment, e - p);
      if (p > e)
        p = e;
      *p++ = '\n';
      *p = 0;
      fputs (buf, stream);
    }
    break;

  default:
    fprintf(stream, "##\n# xine key bindings.\n"
		    "# Automatically generated by %s version %s.\n##\n\n", PACKAGE, VERSION);

    for (i = 0; i < num_entries; i++) {
      char buf[2048], *p = buf, *e = buf + sizeof (buf) - 2 - 18 - 1 - 7 - 13 - 9 - 4 * 6 - 5;
      const kbinding_entry_t *entry = _kbindings_get_entry (kbt, i);

      memcpy (p, "# ", 2); p += 2; e += 2;
      p += strlcpy (p, entry->comment, e - p);
      if (p > e)
        p = e;
      if (entry->is_alias) {
        memcpy (p, "\nAlias {\n\tentry = ", 18); p += 18; e += 18;
        p += strlcpy (p, entry->action, e - p);
        if (p > e)
          p = e;
        *p++ = '\n'; e += 1;
      } else {
        *p++ = '\n';
        p += strlcpy (p, entry->action, e - p);
        memcpy (p, " {\n", 3); p += 3; e += 3;
      }
      memcpy (p, "\tkey = ", 7); p += 7; e += 7;
      p += strlcpy (p, entry->key, e - p);
      if (p > e)
        p = e;
      memcpy (p, "\n\tmodifier = ", 13); p += 13;
      if (entry->modifier == KEYMOD_NOMOD) {
        memcpy (p, "none, ", 6); p += 6;
      } else {
        if (entry->modifier & KEYMOD_CONTROL) {
          memcpy (p, "control, ", 9); p += 9;
        }
        if (entry->modifier & KEYMOD_META) {
          memcpy (p, "meta, ", 6); p += 6;
        }
        if (entry->modifier & KEYMOD_MOD3) {
          memcpy (p, "mod3, ", 6); p += 6;
        }
        if (entry->modifier & KEYMOD_MOD4) {
          memcpy (p, "mod4, ", 6); p += 6;
        }
        if (entry->modifier & KEYMOD_MOD5) {
          memcpy (p, "mod5, ", 6); p += 6;
        }
      }
      p -= 2;
      memcpy (p, "\n}\n\n", 5); p += 5;
      fputs(buf, stream);
    }
    fprintf(stream, "##\n# End of xine key bindings.\n##\n");
    break;
  }

}

/*
 * Convert a modifier to key binding modifier style.
 */
static int kbindings_convert_modifier (int mod) {
  int modifier = KEYMOD_NOMOD;
  if (mod & MODIFIER_CTRL)
    modifier |= KEYMOD_CONTROL;
  if (mod & MODIFIER_META)
    modifier |= KEYMOD_META;
  if (mod & MODIFIER_MOD3)
    modifier |= KEYMOD_MOD3;
  if (mod & MODIFIER_MOD4)
    modifier |= KEYMOD_MOD4;
  if (mod & MODIFIER_MOD5)
    modifier |= KEYMOD_MOD5;
  return modifier;
}

/*
 * Save current key binding table to keymap file.
 */
void kbindings_save_kbinding (gGui_t *gui, kbinding_t *kbt, const char *filename) {
  FILE   *f;

  if (!kbt || !filename)
    return;
  f = fopen (filename, "w+");
  if (!f)
    return;
  _kbindings_display_kbindings_to_stream (gui, kbt, f, KBT_DISPLAY_MODE_DEFAULT);
  fclose (f);
}

/*
 * Free key binding table kbt, then set it to default table.
 */
void kbindings_reset_kbinding(kbinding_t *kbt) {

  ABORT_IF_NULL(kbt);

  kbindings_reset (kbt, -1);
}

/*
 * This could be used to create a default key binding file
 * with 'xine --keymap > $HOME/.xine_keymap'
 */
void kbindings_display_bindings (gGui_t *gui, kbedit_display_mode_t mode) {
  kbinding_t *kbt;
  int f = 1;

  switch (mode) {
    case KBT_DISPLAY_MODE_DEFAULT:
    case KBT_DISPLAY_MODE_LIRC:
    default:
      kbt = kbindings_init_kbinding (gui, NULL);
      break;
    case KBT_DISPLAY_MODE_CURRENT:
    case KBT_DISPLAY_MODE_MAN:
      f = 0;
      kbt = gui->kbindings;
      if (!kbt) {
        kbt = kbindings_init_kbinding (gui, gui->keymap_file);
        f = 1;
      }
      break;
  }
  _kbindings_display_kbindings_to_stream (gui, kbt, stdout, mode);
  if (f)
    kbindings_free_kbinding (&kbt);
}

/*
 * Return action id from key binding kbt entry.
 */
action_id_t kbindings_get_action_id (const kbinding_entry_t *kbt) {

  if(kbt == NULL)
    return ACTID_NOKEY;

  return kbt->action_id;
}

static int _kbindings_key_is_void (const kbinding_entry_t *kbe) {
  const union {
    char z[4];
    uint32_t v;
  } _void = {{'v', 'o', 'i', 'd'}};
  uint32_t start;

  memcpy (&start, kbe->key, 4);
  return (start | 0x20202020) == _void.v;
}

static size_t _kbindings_get_shortcut_from_kbe (const kbinding_entry_t *kbe, char *shortcut, size_t shortcut_size, int style) {
  char *q = shortcut, *e = q + shortcut_size;

  do {
    if (!kbe)
      break;
    if (_kbindings_key_is_void (kbe))
      break;
    if (q + 1 >= e)
      break;
    *q++ = '[';
    if (style == 0) {
      if (kbe->modifier & KEYMOD_CONTROL) {
        q += strlcpy (q, _("Ctrl"), e - q);
        if (q + 1 >= e)
          break;
        *q++ = '+';
      }
      if (kbe->modifier & KEYMOD_META) {
        q += strlcpy (q, _("Alt"), e - q);
        if (q + 1 >= e)
          break;
        *q++ = '+';
      }
      if (kbe->modifier & KEYMOD_MOD3) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M3+", 4);
        q += 3;
      }
      if (kbe->modifier & KEYMOD_MOD4) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M4+", 4);
        q += 3;
      }
      if (kbe->modifier & KEYMOD_MOD5) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M5+", 4);
        q += 3;
      }
    } else {
      if (kbe->modifier & KEYMOD_CONTROL) {
        if (q + 2 >= e)
          break;
        memcpy (q, "C-", 3);
        q += 2;
      }
      if (kbe->modifier & KEYMOD_META) {
        if (q + 2 >= e)
          break;
        memcpy (q, "M-", 3);
        q += 2;
      }
      if (kbe->modifier & KEYMOD_MOD3) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M3-", 4);
        q += 3;
      }
      if (kbe->modifier & KEYMOD_MOD4) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M4-", 4);
        q += 3;
      }
      if (kbe->modifier & KEYMOD_MOD5) {
        if (q + 3 >= e)
          break;
        memcpy (q, "M5-", 4);
        q += 3;
      }
    }
    q += strlcpy (q, kbe->key, e - q);
    if (q + 2 >= e)
      break;
    *q++ = ']';
  } while (0);
  if (q >= e)
    q = e - 1;
  *q = 0;
  return q - shortcut;
}

size_t kbindings_get_shortcut (kbinding_t *kbt, const char *action, char *buf, size_t buf_size, int style) {
  const kbinding_entry_t  *k;

  if (!kbt || !action || !buf || !buf_size)
    return 0;
  if (!(k = kbindings_lookup_action (kbt, action)))
    return 0;
  return _kbindings_get_shortcut_from_kbe (k, buf, buf_size, style);
}

/*
 * Try to find and entry in key binding table matching with key and modifier value.
 */
const kbinding_entry_t *kbindings_lookup_binding (kbinding_t *kbt, const char *key, int modifier) {
  kbinding_entry_t *kret = NULL, *k;
  int               i, num_entries;

  if((key == NULL) || (kbt == NULL))
    return NULL;

#ifdef TRACE_KBINDINGS
  printf("Looking for: '%s' [", key);
  if(modifier == KEYMOD_NOMOD)
    printf("none, ");
  if(modifier & KEYMOD_CONTROL)
    printf("control, ");
  if(modifier & KEYMOD_META)
    printf("meta, ");
  if(modifier & KEYMOD_MOD3)
    printf("mod3, ");
  if(modifier & KEYMOD_MOD4)
    printf("mod4, ");
  if(modifier & KEYMOD_MOD5)
    printf("mod5, ");
  printf("\b\b]\n");
#endif

  /* Be case sensitive */
  k = kbindings_find_key (kbt, key, modifier);
  if (k)
    return k;

  /* Not case sensitive */
  /*
  for (i = 0, k = kbt[0]; i < kbt->num_entries; i++, k = kbt[i]) {
    if((!(strcasecmp(k->key, key))) && (modifier == k->modifier))
      return k;
  }
  */
  /* Last chance */
  num_entries = _kbindings_get_num_entries (kbt);
  for (i = 0; i < num_entries; i++) {
    const kbinding_entry_t *entry = _kbindings_get_entry (kbt, i);
    if (entry && entry->key && ((!(strcmp (entry->key, key))) && (entry->modifier == KEYMOD_NOMOD))) {
      kret = k;
      break;
    }
  }

  /* Keybinding unknown */
  return kret;
}

/*
 * Handle key event from an XEvent.
 */
action_id_t kbinding_aid_from_be_event (kbinding_t *kbt, const xitk_be_event_t *event, int no_gui) {
  char buf[256];
  const kbinding_entry_t *entry;
  int qual;

  if (!kbt || !event)
    return 0;

  if (xitk_be_event_name (event, buf, sizeof (buf)) < 1)
    return 0;
  /* printf ("_kbinding_entry_from_be_event (%s, 0x%x).\n", s, (unsigned int)event->qual); */

  qual = kbindings_convert_modifier (event->qual);
  entry = kbindings_lookup_binding (kbt, buf, qual);
  if (entry && (!entry->is_gui || !no_gui))
    return entry->action_id;
  return 0;
}

/*
 * ***** Key Binding Editor ******
 */
/*
 * Return 1 if setup panel is visible
 */
int kbedit_is_visible (xui_keyedit_t *kbedit) {
  if (kbedit) {
    if (kbedit->gui->use_root_window)
      return (xitk_window_flags (kbedit->xwin, 0, 0) & XITK_WINF_VISIBLE);
    else
      return kbedit->visible && (xitk_window_flags (kbedit->xwin, 0, 0) & XITK_WINF_VISIBLE);
  }
  return 0;
}

/*
 * Raise kbedit->xwin
 */
void kbedit_raise_window (xui_keyedit_t *kbedit) {
  if(kbedit != NULL)
    raise_window (kbedit->gui, kbedit->xwin, kbedit->visible, 1);
}

/*
 * Hide/show the kbedit panel
 */
void kbedit_toggle_visibility (xitk_widget_t *w, void *data) {
  xui_keyedit_t *kbedit = data;
  (void)w;
  if (kbedit)
    toggle_window (kbedit->gui, kbedit->xwin, kbedit->widget_list, &kbedit->visible, 1);
}

static void kbedit_create_browser_entries (xui_keyedit_t *kbedit) {
  int i;

  if (kbedit->num_entries) {
    SAFE_FREE (kbedit->entries);
    kbedit->shortcuts = NULL;
  }
  _dyn_string_clear (&kbedit->ebuf);

  kbedit->num_entries = _kbindings_get_num_entries (kbedit->kbt);
  kbedit->entries = (char **)malloc (2 * kbedit->num_entries * sizeof (kbedit->entries[0]));
  if (!kbedit->entries)
    return;
  kbedit->shortcuts = kbedit->entries + kbedit->num_entries;

  for (i = 0; i < kbedit->num_entries; i++) {
    /* beware of "Ctrl+Alt+Mod3+Mod4+Mod5+XF86AudioLowerVolume" ;-) */
    char  shortcut[256];
    const kbinding_entry_t *entry = _kbindings_get_entry (kbedit->kbt, i);

    _kbindings_get_shortcut_from_kbe (entry, shortcut, sizeof (shortcut), kbedit->gui->shortcut_style);

    kbedit->entries[i] = _dyn_string_get (&kbedit->ebuf);
    if (entry->is_alias) {
      _dyn_string_append (&kbedit->ebuf, "@{");
      _dyn_string_append (&kbedit->ebuf, entry->comment);
      _dyn_string_append (&kbedit->ebuf, "}");
    } else {
      _dyn_string_append (&kbedit->ebuf, entry->comment);
    }
    kbedit->shortcuts[i] = _dyn_string_get (&kbedit->ebuf);
    _dyn_string_append (&kbedit->ebuf, shortcut);
  }
  _dyn_string_fix (&kbedit->ebuf, kbedit->entries, 2 * kbedit->num_entries);
}

static void kbedit_display_kbinding (xui_keyedit_t *kbedit, const char *action, const kbinding_entry_t *kbe) {

  if(action && kbe) {

    xitk_label_change_label (kbedit->w[_W_comment], action);
    xitk_label_change_label (kbedit->w[_W_key], _kbindings_key_is_void (kbe) ? _("None") : kbe->key);

    xitk_button_set_state (kbedit->w[_W_ctrl], (kbe->modifier & KEYMOD_CONTROL) ? 1 : 0);
    xitk_button_set_state (kbedit->w[_W_meta], (kbe->modifier & KEYMOD_META) ? 1 : 0);
    xitk_button_set_state (kbedit->w[_W_mod3], (kbe->modifier & KEYMOD_MOD3) ? 1 : 0);
    xitk_button_set_state (kbedit->w[_W_mod4], (kbe->modifier & KEYMOD_MOD4) ? 1 : 0);
    xitk_button_set_state (kbedit->w[_W_mod5], (kbe->modifier & KEYMOD_MOD5) ? 1 : 0);
  }
}

/*
 *
 */
static void kbedit_unset (xui_keyedit_t *kbedit) {

  if (xitk_labelbutton_get_state (kbedit->w[_W_alias]))
    xitk_labelbutton_set_state (kbedit->w[_W_alias], 0);

  if (xitk_labelbutton_get_state (kbedit->w[_W_edit]))
    xitk_labelbutton_set_state (kbedit->w[_W_edit], 0);

  xitk_widgets_state (kbedit->w + _W_reset, 1, XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE, ~0u);
  /* edit, alias, delete, grab */
  xitk_widgets_state (kbedit->w + _W_edit, 4, XITK_WIDGET_STATE_ENABLE, 0);
  kbedit->ksel = NULL;
  kbedit->nsel = -1;

  xitk_label_change_label (kbedit->w[_W_comment], _("Nothing selected"));
  xitk_label_change_label (kbedit->w[_W_key], _("None"));

  xitk_button_set_state (kbedit->w[_W_ctrl], 0);
  xitk_button_set_state (kbedit->w[_W_meta], 0);
  xitk_button_set_state (kbedit->w[_W_mod3], 0);
  xitk_button_set_state (kbedit->w[_W_mod4], 0);
  xitk_button_set_state (kbedit->w[_W_mod5], 0);
}

static void _kbedit_set (xui_keyedit_t *kbedit) {
  xitk_widgets_state (kbedit->w + _W_edit, 1, XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE, ~0u);
  /* alias, delete */
  xitk_widgets_state (&kbedit->w[_W_alias], 2, XITK_WIDGET_STATE_ENABLE,
    strcasecmp (kbedit->ksel->key, "VOID") ? XITK_WIDGET_STATE_ENABLE : 0);
  xitk_widgets_state (&kbedit->w[_W_reset], 1, XITK_WIDGET_STATE_ENABLE,
    kbedit->ksel->is_default ? 0 : XITK_WIDGET_STATE_ENABLE);
  kbedit_display_kbinding (kbedit, kbedit->entries[kbedit->nsel], kbedit->ksel);
}

/*
 *
 */
static void kbedit_select (xui_keyedit_t *kbedit, int s) {
  if (kbedit->action_wanted != KBEDIT_NOOP) {
    kbedit_unset (kbedit);
    kbedit->action_wanted = KBEDIT_NOOP;
  }

  kbedit->nsel = s;
  kbedit->ksel = _kbindings_get_entry (kbedit->kbt, s);
  _kbedit_set (kbedit);
}

static void _kbr_close (xui_keyedit_t *kbe) {
  if (kbe->kbr.key)
    xitk_unregister_event_handler (kbe->gui->xitk, &kbe->kbr.key);
  xitk_labelbutton_change_label (kbe->w[_W_grab], _("Grab"));
  xitk_labelbutton_set_state (kbe->w[_W_grab], 0);
  SAFE_FREE (kbe->kbr.entry.key);
}

/*
 *
 */
static void kbedit_exit (xitk_widget_t *w, void *data, int state) {
  xui_keyedit_t *kbedit = data;

  (void)w;
  (void)state;
  if (kbedit) {
    _kbr_close (kbedit);
    kbedit->visible = 0;

    gui_save_window_pos (kbedit->gui, "kbedit", kbedit->kreg);
    xitk_unregister_event_handler (kbedit->gui->xitk, &kbedit->kreg);
    xitk_window_destroy_window (kbedit->xwin);
    kbedit->xwin = NULL;

    SAFE_FREE (kbedit->entries);
    kbedit->shortcuts = NULL;
    _dyn_string_deinit (&kbedit->ebuf);

    kbindings_free_kbinding(&kbedit->kbt);

    kbedit->gui->ssaver_enabled = 1;

    video_window_set_input_focus (kbedit->gui->vwin);

    kbedit->gui->keyedit = NULL;
    free (kbedit);
  }
}

/*
 *
 */
static void kbedit_sel(xitk_widget_t *w, void *data, int s, int modifier) {
  xui_keyedit_t *kbedit = data;

  (void)w;
  (void)modifier;
  _kbr_close (kbedit);
  if(s >= 0)
    kbedit_select (kbedit, s);
  else
    kbedit_unset (kbedit);
}

/*
 * Create an alias from the selected entry.
 */
static void kbedit_alias(xitk_widget_t *w, void *data, int state, int modifier) {
  xui_keyedit_t *kbedit = data;

  (void)w;
  (void)modifier;
  _kbr_close (kbedit);
  xitk_labelbutton_set_state(kbedit->w[_W_edit], 0);

  xitk_widgets_state (kbedit->w + _W_grab, 1, XITK_WIDGET_STATE_ENABLE, state ? ~0u : 0);
  kbedit->action_wanted = state ? KBEDIT_ALIASING : KBEDIT_NOOP;
}

/*
 * Change shortcut, should take care about reduncancy.
 */
static void kbedit_edit(xitk_widget_t *w, void *data, int state, int modifier) {
  xui_keyedit_t *kbedit = data;

  (void)w;
  (void)modifier;
  _kbr_close (kbedit);
  xitk_labelbutton_set_state(kbedit->w[_W_alias], 0);

  xitk_widgets_state (kbedit->w + _W_grab, 1, XITK_WIDGET_STATE_ENABLE, state ? ~0u : 0);
  kbedit->action_wanted = state ? KBEDIT_EDITING : KBEDIT_NOOP;
}

/*
 * Remove selected entry, but alias ones only.
 */
static void kbedit_delete (xitk_widget_t *w, void *data, int state) {
  xui_keyedit_t *kbedit = data;
  int s = xitk_browser_get_current_selected(kbedit->w[_W_browser]);

  (void)w;
  (void)state;
  _kbr_close (kbedit);
  xitk_labelbutton_set_state(kbedit->w[_W_alias], 0);
  xitk_labelbutton_set_state(kbedit->w[_W_edit], 0);
  xitk_widgets_state (kbedit->w + _W_grab, 1, XITK_WIDGET_STATE_ENABLE, 0);

  if (s >= 0) {
    int r, is_alias;

    kbedit->nsel = s;
    kbedit->ksel = _kbindings_get_entry (kbedit->kbt, s);
    is_alias = kbedit->ksel->is_alias;
    r = kbindings_entry_set (kbedit->kbt, s, 0, "VOID");

    if (r == -1) {
      kbedit_create_browser_entries (kbedit);
      xitk_browser_update_list (kbedit->w[_W_browser],
        (const char* const*) kbedit->entries,
        (const char* const*) kbedit->shortcuts, kbedit->num_entries, xitk_browser_get_current_start(kbedit->w[_W_browser]));
      if (is_alias) {
        xitk_browser_set_select (kbedit->w[_W_browser], -1);
        kbedit_unset (kbedit);
      } else {
        _kbedit_set (kbedit);
      }
    }
    /* gone ;-)
    gui_msg (kbedit->gui, XUI_MSG_ERROR, _("You can only delete alias entries."));
    */
  }
}

/*
 * Reset to xine's default table.
 */
static void kbedit_reset (xitk_widget_t *w, void *data, int state) {
  xui_keyedit_t *kbedit = data;
  int n1, n2, r;

  (void)w;
  (void)state;
  _kbr_close (kbedit);

  n1 = _kbindings_get_num_entries (kbedit->kbt);
  r = kbindings_reset (kbedit->kbt, kbedit->nsel);
  n2 = _kbindings_get_num_entries (kbedit->kbt);

  kbedit_create_browser_entries (kbedit);
  xitk_browser_update_list (kbedit->w[_W_browser], (const char * const *) kbedit->entries,
    (const char * const *)kbedit->shortcuts, kbedit->num_entries, xitk_browser_get_current_start (kbedit->w[_W_browser]));
  xitk_labelbutton_set_state (kbedit->w[_W_alias], 0);
  xitk_labelbutton_set_state (kbedit->w[_W_edit], 0);
  xitk_widgets_state (kbedit->w + _W_grab, 1, XITK_WIDGET_STATE_ENABLE, 0);

  if (kbedit->nsel < 0) {
    /* full list reset */
    ;
  } else if (n2 < n1) {
    /* alias deleted */
    xitk_browser_set_select (kbedit->w[_W_browser], -1);
    kbedit_unset (kbedit);
  } else if (r == -1) {
    /* base entry changed */
    kbedit->ksel = _kbindings_get_entry (kbedit->kbt, kbedit->nsel);
    _kbedit_set (kbedit);
  }
}

/*
 * Save keymap file, then set global table to hacked one
 */
static void kbedit_save (xitk_widget_t *w, void *data, int state) {
  xui_keyedit_t *kbedit = data;

  (void)w;
  (void)state;
  _kbr_close (kbedit);
  xitk_labelbutton_set_state(kbedit->w[_W_alias], 0);
  xitk_labelbutton_set_state(kbedit->w[_W_edit], 0);
  xitk_widgets_state (kbedit->w + _W_grab, 1, XITK_WIDGET_STATE_ENABLE, 0);

  kbindings_free_kbinding (&kbedit->gui->kbindings);
  kbedit->gui->kbindings = _kbindings_duplicate_kbindings (kbedit->kbt);
  kbindings_save_kbinding (kbedit->gui, kbedit->gui->kbindings, kbedit->gui->keymap_file);
}

/*
 * Forget and dismiss kbeditor
 */
void kbedit_end (xui_keyedit_t *kbedit) {
  kbedit_exit (NULL, kbedit, 0);
}

static void _kbedit_store_1 (xui_keyedit_t *kbe) {
  int r;
  switch (kbe->action_wanted) {
    case KBEDIT_ALIASING:
      r = _kbindings_get_num_entries (kbe->kbt);
      if (r >= MAX_ENTRIES) {
        gui_msg (kbe->gui, XUI_MSG_ERROR, _("No more space for additional entries!"));
        return;
      }
      kbe->kbr.entry.is_alias = 1;
      r = kbindings_alias_add (kbe->kbt, kbe->nsel, kbe->kbr.entry.modifier, kbe->kbr.entry.key);
      SAFE_FREE (kbe->kbr.entry.key);
      if (r == -1) {
        kbedit_create_browser_entries (kbe);
        xitk_browser_update_list (kbe->w[_W_browser], (const char * const *) kbe->entries,
          (const char * const *) kbe->shortcuts, kbe->num_entries, xitk_browser_get_current_start (kbe->w[_W_browser]));
      }
      break;
    case KBEDIT_EDITING:
      kbe->kbr.entry.is_alias = kbe->ksel->is_alias;
      r = kbindings_entry_set (kbe->kbt, kbe->nsel, kbe->kbr.entry.modifier, kbe->kbr.entry.key);
      SAFE_FREE (kbe->kbr.entry.key);
      kbe->ksel = _kbindings_get_entry (kbe->kbt, kbe->nsel);
      if (r == -1) {
        kbedit_create_browser_entries (kbe);
        xitk_browser_update_list (kbe->w[_W_browser], (const char * const *) kbe->entries,
          (const char * const *) kbe->shortcuts, kbe->num_entries, xitk_browser_get_current_start (kbe->w[_W_browser]));
        _kbedit_set (kbe);
      }
      break;
    default: ;
  }
  if (xitk_browser_get_current_selected (kbe->w[_W_browser]) < 0) {
    kbedit_unset (kbe);
    kbe->action_wanted = KBEDIT_NOOP;
  }
}

/*
 * Grab key binding.
 */

static int kbr_event (void *data, const xitk_be_event_t *e) {
  xui_keyedit_t *kbe = data;

  if (e->type == XITK_EV_DEL_WIN) {
    _kbr_close (kbe);
    kbe->grabbing = 0;
    return 1;
  }

  if (e->type == XITK_EV_KEY_DOWN) {
    kbe->grabbing = 2;
    return 1;
  }

  if (e->type == XITK_EV_KEY_UP) {
    const kbinding_entry_t *redundant;
    char name[64];

    /* 1. user hits grab button with ENTER/SPACE,
     * 2. grab window opens,
     * 3. we get the ENTER/SPACE key up here, and
     * 4. ignore it :-)) */
    if (kbe->grabbing < 2)
      return 0;
    if (xitk_be_event_name (e, name, sizeof(name)) < 1)
      return 0;

    _kbr_close (kbe);
    xitk_labelbutton_set_state (kbe->w[_W_grab], 0);
    kbe->grabbing = 0;

    kbe->kbr.entry.comment   = kbe->ksel->comment;
    kbe->kbr.entry.action    = kbe->ksel->action;
    kbe->kbr.entry.action_id = kbe->ksel->action_id;
    kbe->kbr.entry.is_alias  = kbe->ksel->is_alias;
    kbe->kbr.entry.is_gui    = kbe->ksel->is_gui;
    kbe->kbr.entry.key       = strdup (name);
    kbe->kbr.entry.modifier  = kbindings_convert_modifier (e->qual);

    redundant = kbindings_find_key (kbe->kbt, kbe->kbr.entry.key, kbe->kbr.entry.modifier);
    if (redundant) {
      /* error, redundant */
      gui_msg (kbe->gui, XUI_MSG_ERROR, _("This key binding is redundant with action:\n\"%s\".\n"),
        redundant->comment);
      SAFE_FREE (kbe->kbr.entry.key);
      return 1;
    }

    kbedit_display_kbinding (kbe, xitk_label_get_label (kbe->w[_W_comment]), &kbe->kbr.entry);
    xitk_labelbutton_change_label (kbe->w[_W_grab], _("Store new key binding"));
    return 1;
  }

  return 0;
}

static void kbr_delete (void *data) {
  xui_keyedit_t *kbe = data;

  xitk_window_destroy_window (kbe->kbr.xwin);
  kbe->kbr.xwin = NULL;
}

static void kbedit_grab (xitk_widget_t *w, void *data, int state, int qual) {
  xui_keyedit_t *kbe = data;

  (void)w;
  (void)qual;
  if (!kbe)
    return;

  if (kbe->kbr.key) {
    if (!state)
      _kbr_close (kbe);
    return;
  }

  if (kbe->kbr.entry.key) {
    if (state) {
      _kbedit_store_1 (kbe);
      _kbr_close (kbe);
    }
    return;
  }

  if (!state)
    return;

  kbe->grabbing = 1;
  /* xitk_labelbutton_change_label (kbe->w[_W_grab], _("Press Keyboard Keys...")); */
  {
    xitk_rect_t wr = {0, 0, 0, 0};
    xitk_window_get_window_position (kbe->xwin, &wr);
    kbe->kbr.xwin = xitk_window_create_dialog_window (kbe->gui->xitk,
      _("Press keyboard keys to bind..."), wr.x, wr.y + wr.height, wr.width, 50);
  }
  set_window_states_start (kbe->gui, kbe->kbr.xwin);
  xitk_window_flags (kbe->kbr.xwin, XITK_WINF_VISIBLE | XITK_WINF_ICONIFIED, XITK_WINF_VISIBLE);
  xitk_window_raise_window (kbe->kbr.xwin);
  xitk_window_set_input_focus (kbe->kbr.xwin);
  kbe->kbr.key = xitk_be_register_event_handler ("keybinding recorder", kbe->kbr.xwin, kbr_event, kbe, kbr_delete, kbe);
}

/*
 *
 */

static int kbedit_event (void *data, const xitk_be_event_t *e) {
  xui_keyedit_t *kbedit = data;

  if (((e->type == XITK_EV_KEY_DOWN) && (e->utf8[0] == XITK_CTRL_KEY_PREFIX) && (e->utf8[1] == XITK_KEY_ESCAPE))
    || (e->type == XITK_EV_DEL_WIN)) {
    kbedit_exit (NULL, kbedit, 0);
    return 1;
  }
  return 0;
}

/* needed to turn button into checkbox */
static void kbedit_dummy (xitk_widget_t *w, void *data, int state) {
  (void)w;
  (void)data;
  (void)state;
}

/*
 *
 */
void kbedit_window (gGui_t *gui) {
  int                        x, y, y1;
  xitk_image_t              *bg;
  xitk_labelbutton_widget_t  lb;
  xitk_label_widget_t        l;
  xitk_browser_widget_t      br;
  int                        fontheight;
  xitk_font_t               *fs;
  xui_keyedit_t             *kbedit;

  if (!gui)
    return;
  if (gui->keyedit)
    return;

  x = y = 80;
  gui_load_window_pos (gui, "kbedit", &x, &y);

  kbedit = (xui_keyedit_t *)calloc (1, sizeof (*kbedit));
  if (!kbedit)
    return;
  kbedit->gui = gui;
  kbedit->gui->keyedit = kbedit;

  _dyn_string_init (&kbedit->ebuf);

  kbedit->kbt           = _kbindings_duplicate_kbindings(gui->kbindings);
  kbedit->action_wanted = KBEDIT_NOOP;
  kbedit->xwin          = xitk_window_create_dialog_window(gui->xitk,
							   _("Key Binding Editor"),
							   x, y, WINDOW_WIDTH, WINDOW_HEIGHT);
  set_window_states_start(gui, kbedit->xwin);

  kbedit->widget_list = xitk_window_widget_list(kbedit->xwin);

  bg = xitk_window_get_background_image (kbedit->xwin);

#define _XBRD 15 /* window border */
#define _XGAP 10
#define _FRAMEWIDTH 2
#define _CSIZE 10 /* checkbox */
#define _XMODTEXT 40
#define _XMODALL (2 * _FRAMEWIDTH + 5 * (_CSIZE + _XGAP + _XMODTEXT) + 6 * _XGAP)
#define _XBTN ((WINDOW_WIDTH - 2 * _XBRD - 5 * _XGAP / 2) / 6)

  y = 34;
  xitk_image_draw_rectangular_box (bg, _XBRD, y, WINDOW_WIDTH - 2 * _XBRD, MAX_DISP_ENTRIES * 20 + 10, DRAW_INNER);

  y += MAX_DISP_ENTRIES * 20 + 10 + 30;
  y1 = y; /* remember for later */
  xitk_image_draw_outter_frame (bg, _("Binding Action"), hboldfontname, _XBRD, y, WINDOW_WIDTH - 2 * _XBRD, 45);

  y += 45 + 3;
  xitk_image_draw_outter_frame (bg, _("Key"), hboldfontname, _XBRD, y, WINDOW_WIDTH - 2 * _XBRD - _XGAP / 2 - _XMODALL, 45);
  xitk_image_draw_outter_frame (bg, _("Modifiers"), hboldfontname, WINDOW_WIDTH - _XBRD - _XMODALL, y, _XMODALL, 45);

  xitk_window_set_background_image (kbedit->xwin, bg);

  kbedit_create_browser_entries (kbedit);

  y = 34;

  br.nw.wl                = kbedit->widget_list;
  br.nw.skin_element_name = NULL;
  br.nw.userdata          = kbedit;

  br.arrow_up.skin_element_name    = NULL;
  br.slider.skin_element_name      = NULL;
  br.arrow_dn.skin_element_name    = NULL;
  br.browser.skin_element_name     = NULL;
  br.browser.max_displayed_entries = MAX_DISP_ENTRIES;
  br.browser.num_entries           = kbedit->num_entries;
  br.browser.entries               = (const char* const*)kbedit->entries;
  br.callback                      = kbedit_sel;
  br.dbl_click_callback            = NULL;
  kbedit->w[_W_browser] = xitk_noskin_browser_create (&br,
    _XBRD + 5, y + 5, WINDOW_WIDTH - 2 * (_XBRD + 5), 20, -16, br_fontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_browser], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  xitk_browser_set_alignment (kbedit->w[_W_browser], ALIGN_LEFT);
  xitk_browser_update_list (kbedit->w[_W_browser],
    (const char* const*) kbedit->entries,
    (const char* const*) kbedit->shortcuts, kbedit->num_entries, xitk_browser_get_current_start (kbedit->w[_W_browser]));

  y = y1 - 30 + 4;

  lb.nw.wl                = kbedit->widget_list;
  lb.nw.skin_element_name = NULL;
  lb.nw.userdata          = kbedit;
  lb.align = ALIGN_CENTER;

  x = (WINDOW_WIDTH - 5 * _XGAP / 2 - 6 * _XBTN) / 2;

  lb.button_type    = RADIO_BUTTON;
  lb.label          = _("Alias");
  lb.callback       = NULL;
  lb.state_callback = kbedit_alias;
  kbedit->w[_W_alias] = xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_alias], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  x += _XBTN + _XGAP / 2;
  lb.button_type    = RADIO_BUTTON;
  lb.label          = _("Edit");
  lb.callback       = NULL;
  lb.state_callback = kbedit_edit;
  kbedit->w[_W_edit] = xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_edit], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  x += _XBTN + _XGAP / 2;
  lb.button_type    = CLICK_BUTTON;
  lb.label          = _("Delete");
  lb.callback       = kbedit_delete;
  lb.state_callback = NULL;
  kbedit->w[_W_delete] = xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_delete], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  x += _XBTN + _XGAP / 2;
  lb.button_type    = CLICK_BUTTON;
  lb.label          = _("Save");
  lb.callback       = kbedit_save;
  lb.state_callback = NULL;
  kbedit->w[_W_save] = xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_save], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  x += _XBTN + _XGAP / 2;
  lb.button_type    = CLICK_BUTTON;
  lb.label          = _("Reset");
  lb.callback       = kbedit_reset;
  lb.state_callback = NULL;
  kbedit->w[_W_reset] =  xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_reset], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  x += _XBTN + _XGAP / 2;
  lb.button_type    = CLICK_BUTTON;
  lb.label          = _("Done");
  lb.callback       = kbedit_exit;
  lb.state_callback = NULL;
  kbedit->w[_W_done] = xitk_noskin_labelbutton_create (&lb, x, y, _XBTN, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_done], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  fs = xitk_font_load_font(gui->xitk, hboldfontname);
  fontheight = xitk_font_get_string_height(fs, " ");
  xitk_font_unload_font(fs);

  y = y1 + (45 / 2);                /* Checkbox                     */
  y1 = y - ((fontheight - 10) / 2); /* Text, v-centered to ckeckbox */

  l.nw.wl                = kbedit->widget_list;
  l.nw.skin_element_name = NULL;
  l.nw.userdata          = kbedit;
  l.callback = NULL;

  l.label = "Binding Action";
  kbedit->w[_W_comment] = xitk_noskin_label_create (&l, _XBRD + _FRAMEWIDTH + _XGAP, y1,
    WINDOW_WIDTH - 2 * (_XBRD + _FRAMEWIDTH + _XGAP), fontheight, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_comment], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  y += 45 + 3;
  y1 += 45 + 3;

  l.label = "THE Key";
  kbedit->w[_W_key] = xitk_noskin_label_create (&l, _XBRD + _FRAMEWIDTH + _XGAP, y1,
    WINDOW_WIDTH - 2 * (_XBRD + _FRAMEWIDTH + _XGAP) - _XMODALL - _XGAP / 2, fontheight, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_key], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  {
    static const char s[5][8] = { N_("ctrl"), N_("meta"), N_("mod3"), N_("mod4"), N_("mod5") };
    xitk_button_widget_t b;
    uint32_t i;

    b.nw.wl                = kbedit->widget_list;
    b.nw.skin_element_name = "XITK_NOSKIN_CHECK";
    b.nw.userdata          = kbedit;
    b.callback       = NULL;
    b.state_callback = kbedit_dummy;

    x = WINDOW_WIDTH - _XBRD - _XMODALL + _FRAMEWIDTH + _XGAP;
    for (i = 0; i < sizeof (s) / sizeof (s[0]); i++) {
      xitk_widget_t *w;

      kbedit->w[_W_ctrl + i] = w = xitk_noskin_button_create (&b, x, y, _CSIZE, _CSIZE);
      xitk_add_widget (kbedit->widget_list, w, XITK_WIDGET_STATE_VISIBLE);
      x += _CSIZE + _XGAP;
      l.label = gettext (s[i]);
      w =  xitk_noskin_label_create (&l, x, y1, _XMODTEXT, fontheight, hboldfontname);
      xitk_add_widget (kbedit->widget_list, w, XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);
      x += _XMODTEXT + _XGAP;
    }
  }

  y = WINDOW_HEIGHT - (23 + 15);

  lb.button_type    = RADIO_BUTTON;
  lb.label          = _("Grab");
  lb.callback       = NULL;
  lb.state_callback = kbedit_grab;
  kbedit->w[_W_grab] = xitk_noskin_labelbutton_create (&lb, _XBRD, y, WINDOW_WIDTH - 2 * _XBRD, 23,
    XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_NORM, XITK_NOSKIN_TEXT_INV, hboldfontname);
  xitk_add_widget (kbedit->widget_list, kbedit->w[_W_grab], XITK_WIDGET_STATE_ENABLE | XITK_WIDGET_STATE_VISIBLE);

  kbedit_unset (kbedit);

  kbedit->kreg = xitk_be_register_event_handler ("kbedit", kbedit->xwin, kbedit_event, kbedit, NULL, NULL);

  kbedit->kbr.key = 0;
  kbedit->kbr.entry.key = NULL;
  kbedit->kbr.xwin = NULL;

  kbedit->nsel = -1;
  kbedit->ksel = NULL;

  gui->ssaver_enabled = 0;
  kbedit->visible      = 1;
  kbedit_raise_window (kbedit);

  xitk_window_set_input_focus (kbedit->xwin);
}
