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

/*
 *  $Date: 2000/01/09 17:31:29 $
 *  $Author: hafner $
 *  $Revision: 1.26 $
 *  $State: Exp $
 */

#include "config.h"

#if HAVE_STRING_H
#	include <string.h>
#else /* not HAVE_STRING_H */
#	include <strings.h>
#endif /* not HAVE_STRING_H */

#include <ctype.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/keysym.h>
#include "proplist_t.h"

#include "keys.h"
#include "misc.h"
#include "icons.h"
#include "error.h"

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

				global variables
  
*******************************************************************************/

extern GtkWidget  *main_window;		/* from window.c */
extern proplist_t windowmaker;		/* from window.c */

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

				local variables
  
*******************************************************************************/

static GList *keylist = NULL;

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

				prototypes
  
*******************************************************************************/

static GList *
keysym_list (void);
static int
compare (const void *string1, const void *string2);
static void
set_key (GtkWidget *widget, gpointer ptr);
static gint
grab_key (GtkWidget *widget, GdkEventKey *event, gpointer ptr);
static gint
leave_focus (GtkWidget *widget, GdkEventAny *event, gpointer ptr);
static void
toggle_pressed (GtkWidget *widget, gpointer ptr);
static bool_t
modifier_available (const char *modifier, const char **modifier_list);

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

				public code
  
*******************************************************************************/

GtkWidget *
key_dialog (const char *keytext, GtkTooltips *tooltips, const char *infotext,
	    void (*update) (const key_widget_t *data), void *data)
/*
 *  Generate shortcurt widget.
 *
 *  Side effects: user data of widget object is set to key_widget_t struct.
 */
{
   GtkWidget	*box;
   GtkWidget	*lbox;
   key_widget_t	*kw = Calloc (1, sizeof (key_widget_t));
   const char   **modlist = modifier_list ();
   unsigned     n;
   unsigned     nmod;

   for (nmod = n = 0; modlist [n]; n++)
      if (strncaseeq (modlist [n], "mod", 3))
	 nmod++;
   
   if (!keylist)
      keylist = keysym_list ();

   kw->data   = data;
   kw->update = update;

   box  = gtk_hbox_new (FALSE, 0);
   lbox = gtk_vbox_new (FALSE, 0);
   gtk_box_pack_start (GTK_BOX (box), lbox, TRUE, TRUE, 0);
      
   /*
    *  Check buttons for control, shift and mod1-5
    */
   {
      GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
      GtkWidget *button;
	    
      gtk_box_pack_start (GTK_BOX (lbox), hbox, TRUE, TRUE, 0);

      if (nmod <= 1)
      {
	 kw->control = kw->lcontrol = button
		     = gtk_check_button_new_with_label (_("Control"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
	 if (infotext)
	    gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	 
	 kw->shift = kw->lshift = button
		   = gtk_check_button_new_with_label (_("Shift"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
	 if (infotext)
	    gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	 
	 for (n = 0; n < 5; n++)
	 {
	    char *modname = g_strdup_printf ("Mod%d", n + 1);
	    
	    if (modifier_available (modname, modlist))
	    {
	       kw->mod [n] = kw->lmod [n] = button
			   = gtk_check_button_new_with_label (modname);
	       gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
	       if (infotext)
		  gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	    }
	    else
	       kw->mod [n] = NULL;
	    Free (modname);
	 }
      }
      else
      {
	 GtkWidget *vbox;
	 GtkWidget *gbox;

	 vbox = gtk_vbox_new (FALSE, 0);
	 gbox = gtk_hbox_new (TRUE, 0);
	 kw->lcontrol = gtk_label_new (_("Control"));
	 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 5);
	 gtk_box_pack_start (GTK_BOX (vbox), kw->lcontrol, TRUE, TRUE, 0);
	 kw->control = button = gtk_check_button_new ();
	 gtk_box_pack_start (GTK_BOX (gbox), button, FALSE, FALSE, 0);
	 gtk_box_pack_start (GTK_BOX (vbox), gbox, TRUE, TRUE, 0);
	 if (infotext)
	    gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	 
	 gbox = gtk_hbox_new (TRUE, 0);
	 vbox = gtk_vbox_new (FALSE, 0);
	 kw->lshift = gtk_label_new (_("Shift"));
	 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 5);
	 gtk_box_pack_start (GTK_BOX (vbox), kw->lshift, TRUE, TRUE, 0);
	 kw->shift = button = gtk_check_button_new ();
	 gtk_box_pack_start (GTK_BOX (gbox), button, FALSE, FALSE, 0);
	 gtk_box_pack_start (GTK_BOX (vbox), gbox, TRUE, TRUE, 0);
	 if (infotext)
	    gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	 
	 for (n = 0; n < 5; n++)
	 {
	    char *modname = g_strdup_printf ("Mod%d", n + 1);
	    
	    if (modifier_available (modname, modlist))
	    {
	       if (n == 0 && nmod == 5)
	       {
		  hbox = gtk_hbox_new (FALSE, 0);
		  gtk_box_pack_start (GTK_BOX (lbox), hbox, TRUE, TRUE, 0);
	       }
	       
	       kw->lmod [n] = gtk_label_new (modname);
	       gbox = gtk_hbox_new (TRUE, 0);
	       vbox = gtk_vbox_new (FALSE, 0);
	       gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 5);
	       gtk_box_pack_start (GTK_BOX (vbox), kw->lmod [n], TRUE, TRUE, 0);
	       kw->mod [n] = button = gtk_check_button_new ();
	       gtk_box_pack_start (GTK_BOX (gbox), button, FALSE, FALSE, 0);
	       gtk_box_pack_start (GTK_BOX (vbox), gbox, TRUE, TRUE, 0);
	       if (infotext)
		  gtk_tooltips_set_tip (tooltips, button, infotext, NULL);
	    }
	    else
	       kw->mod [n] = NULL;
	    Free (modname);
	 }
      }
   }
   /*
    *  Combo box for all available keysyms
    */
   {
      GtkWidget	*cb;

      kw->combo = cb = gtk_combo_new ();
      gtk_combo_set_popdown_strings (GTK_COMBO (cb), keylist);
      gtk_widget_set_usize (GTK_COMBO (cb)->entry, 120, 0);
      gtk_widget_set_usize (cb, 120, 0);
      gtk_box_pack_start (GTK_BOX (lbox), cb, FALSE, TRUE, 5);
      if (infotext)
	 gtk_tooltips_set_tip (tooltips, GTK_COMBO (cb)->entry, infotext,
			       NULL);
      kw->keyname = GTK_COMBO (cb)->entry;
   }
   /*
    *  Eventbox for key grabber
    */
   {
      GtkWidget *bbox = gtk_hbutton_box_new ();
      GtkWidget *pixmap;

      pixmap = gtk_pixmap_new (p_array [P_KEYBOARD].pixmap,
			       p_array [P_KEYBOARD].mask);

      kw->toggle = gtk_toggle_button_new ();

      gtk_box_pack_start (GTK_BOX (bbox), kw->toggle, FALSE, TRUE, 0);
      gtk_box_pack_start (GTK_BOX (lbox), bbox, FALSE, TRUE, 0);
      gtk_widget_show (kw->toggle);
      
      gtk_container_add (GTK_CONTAINER (kw->toggle), pixmap);
      gtk_container_set_border_width (GTK_CONTAINER (kw->toggle), 8);

      gtk_tooltips_set_tip (tooltips, kw->toggle,
			    _("Press this button to activate capture mode "
			      "- then press the desired key sequence."), NULL);
      gtk_signal_connect (GTK_OBJECT(kw->toggle), "clicked",
			  GTK_SIGNAL_FUNC (toggle_pressed), kw);
      gtk_signal_connect (GTK_OBJECT(kw->toggle), "focus_out_event",
			  GTK_SIGNAL_FUNC (leave_focus), kw);
      gtk_signal_connect (GTK_OBJECT(kw->toggle), "key_press_event",
			  GTK_SIGNAL_FUNC (grab_key), kw);

      gtk_widget_show_all (kw->toggle);
   }
   
   gtk_widget_show_all (box);
   gtk_object_set_user_data (GTK_OBJECT (box), kw);

   update_keywidget (kw, keytext);

   gtk_signal_connect (GTK_OBJECT (kw->control), "toggled",
		       GTK_SIGNAL_FUNC (set_key), (gpointer) kw);
   for (n = 0; n < 5; n++)
      if (kw->mod [n])
	 gtk_signal_connect (GTK_OBJECT (kw->mod [n]), "toggled",
			     GTK_SIGNAL_FUNC (set_key), (gpointer) kw);
   gtk_signal_connect (GTK_OBJECT (kw->shift), "toggled",
		       GTK_SIGNAL_FUNC (set_key), (gpointer) kw);
   gtk_signal_connect (GTK_OBJECT (GTK_COMBO (kw->combo)->entry), "changed",
		       GTK_SIGNAL_FUNC (set_key), (gpointer) kw);
   
   return box;
}

void
update_keywidget (key_widget_t *kw, const char *keytext)
/*
 *  Update keywidget with given keyname (Window Maker string format)
 */
{
   bool_t	control, meta, shift, mod [5];
   unsigned	number, n;
   
   /*
    *  Extract modifiers and keysym from attribute 'key'
    */
   {
      char *s = g_strdup (keytext);

      control = meta = shift = mod [0] = mod [1] = mod [2]
	      = mod [3] = mod [4] = FALSE;
      number  = 0;

      if (!strcaseeq (s, "None"))
      {
	 char *next;
	 char *cur = s;
	 
	 while ((next = strchr (cur, '+')))
	 {
	    *next = 0;
	    if (strcaseeq (cur, "Control"))
	       control = TRUE;
	    else if (strcaseeq (cur, "Meta") || strcaseeq (cur, "Alt"))
	       meta = TRUE;
	    else if (strcaseeq (cur, "Mod1"))
	       mod [0] = TRUE;
	    else if (strcaseeq (cur, "Mod2"))
	       mod [1] = TRUE;
	    else if (strcaseeq (cur, "Mod3"))
	       mod [2] = TRUE;
	    else if (strcaseeq (cur, "Mod4"))
	       mod [3] = TRUE;
	    else if (strcaseeq (cur, "Mod5"))
	       mod [4] = TRUE;
	    else if (strcaseeq (cur, "Shift"))
	       shift = TRUE;
	    cur = next + 1;
	 }
	 {
	    GList *l = g_list_first (keylist);
	    
	    while (l)
	    {
	       if (strcaseeq ((char *) l->data, cur))
		  break;
	       number++;
	       l = l->next;
	    }
	    if (number > g_list_length (keylist))
	    {
	       number = 0;
	       control = meta = shift = mod [0] = mod [1] = mod [2]
		       = mod [3] = mod [4] = FALSE;
	    }
	 }
	    
      }
      kw->keytext = s;
   }

   gtk_list_select_item (GTK_LIST (GTK_COMBO (kw->combo)->list), number);

   gtk_widget_set_sensitive (kw->lcontrol, number);
   gtk_widget_set_sensitive (kw->lshift, number);
   for (n = 0; n < 5; n++)
      if (kw->mod [n])
	 gtk_widget_set_sensitive (kw->lmod [n], number);   
   gtk_widget_set_sensitive (kw->control, number);
   gtk_widget_set_sensitive (kw->shift, number);
   for (n = 0; n < 5; n++)
      if (kw->mod [n])
	 gtk_widget_set_sensitive (kw->mod [n], number);   

   {
      proplist_t plmod  = WMCreatePLString ("ModifierKey");
      char	 *modkey = WMGetFromPLString (WMGetFromPLDictionary (windowmaker,
							      plmod));
      
      for (n = 0; n < 5; n++)
      {
	 if (kw->mod [n])
	 {
	    char *modname = g_strdup_printf ("Mod%d", n + 1);

	    if (strcaseeq (modname, modkey))
	       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [n]),
					     mod [n] || meta);
	    else
	       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [n]),
					     mod [n]);
	    Free (modname);
	 }
      }
   
      WMReleasePropList (plmod);
   }
   
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->shift), shift);
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->control), control);
}

const char **
modifier_list (void)
/*
 *  Get list of defined modifiers
 */
{
   static const char **modifiers = NULL;

   if (!modifiers)
   {
      XModifierKeymap *modmap = XGetModifierMapping (GDK_DISPLAY());
      const char *modlist[9] = {"Shift", "Lock", "Control", "Mod1",
				"Mod2", "Mod3", "Mod4", "Mod5", NULL};
      unsigned   n = 0;

      if (modmap)
      {
	 unsigned mkey = modmap->max_keypermod;
	 unsigned modifier, k;

	 modifiers = Calloc (sizeof (modlist) / sizeof (modlist [0]),
			     sizeof (const char *));
	 for (modifier = 0; modifier < 8; modifier++)
	    for (k = 0; k < mkey; k++)
	    {
	       KeyCode c = modmap->modifiermap [modifier * mkey + k];
	       if (c != 0)
	       {
		  modifiers [n++] = g_strdup (modlist [modifier]);
		  break;
	       }
	    }
	 XFree (modmap);
	 modifiers [n++] = NULL;
      }
   }

   return modifiers;
}

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

				private code
  
*******************************************************************************/

static gint
leave_focus (GtkWidget *widget, GdkEventAny *event, gpointer ptr)
{
   GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);

   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
   gdk_window_set_cursor (main_window->window, cursor);
   gdk_cursor_destroy (cursor);

   return TRUE;
}

static void
toggle_pressed (GtkWidget *widget, gpointer ptr)
{
   GdkCursor *cursor = gdk_cursor_new (GTK_TOGGLE_BUTTON (widget)->active
				       ? GDK_HAND2 : GDK_LEFT_PTR);
   gdk_window_set_cursor (main_window->window, cursor);
   gdk_cursor_destroy (cursor);
}

static gint
grab_key (GtkWidget *widget, GdkEventKey *event, gpointer ptr)
{
   key_widget_t *kw = (key_widget_t *) ptr;
   
   if (GTK_TOGGLE_BUTTON (widget)->active && !IsModifierKey (event->keyval))
   {
      GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);

      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
      gdk_window_set_cursor (main_window->window, cursor);
      gdk_cursor_destroy (cursor);

      gtk_entry_set_text (GTK_ENTRY (kw->keyname),
			  XKeysymToString (XKeycodeToKeysym (GDK_DISPLAY(), XKeysymToKeycode (GDK_DISPLAY(), event->keyval), 0)));

      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->control),
				    event->state & GDK_CONTROL_MASK);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->shift),
				    event->state & GDK_SHIFT_MASK);
      if (kw->mod [0])
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [0]),
				       event->state & GDK_MOD1_MASK);
      if (kw->mod [1])
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [1]),
				       event->state & GDK_MOD2_MASK);
      if (kw->mod [2])
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [2]),
				       event->state & GDK_MOD3_MASK);
      if (kw->mod [3])
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [3]),
				       event->state & GDK_MOD4_MASK);
      if (kw->mod [4])
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [4]),
				       event->state & GDK_MOD5_MASK);
      return TRUE;
   }

   return FALSE;
}

static GList *
keysym_list (void)
/*
 *  Get list of available key symbols
 */
{
   int		m, M;			/* minimum and maximum keycode */
   int		kpk;			/* keys per keycode */
   int		i, k;			/* counter */
   KeySym	*keysyms;		/* all available keysyms */
   char		**keyname;		/* array of keynames */
   static GList	*list = NULL;		/* list of keynames */

   if (list)
      return list;			/* already computed */

   XDisplayKeycodes (GDK_DISPLAY(), &m, &M);
   keysyms = XGetKeyboardMapping (GDK_DISPLAY(), m, M - m + 1, &kpk);

   keyname = Calloc ((M - m + 1 + 1), sizeof (char *));
	 
   for (i = k = 0; i < (M - m + 1); i++)
      if (!IsModifierKey (keysyms [i * kpk]))
      {
	 char *name = XKeysymToString (keysyms [i * kpk]);

	 if (name)
	 {
	    keyname [k] = g_strdup (name);
#if 0
	    if (isalpha (*keyname [k]))
	       *keyname [k] = toupper (*keyname [k]);
#endif
	    k++;
	 }
      }
   XFree (keysyms);

   qsort (keyname, k, sizeof (char *), compare);

   list = g_list_append (NULL, "None");
   for (i = 0; i < k; i++)
      list = g_list_append (list, keyname [i]);

   Free (keyname);

   return list;
}

static int
compare (const void *string1, const void *string2)
/*
 *  Compare two keynames. Sort by 1) string length and 2) alphabet.
 *
 *  Return value:
 *	<  0 : string1 < string2
 *	== 0 : string1 = string2
 *	>  0 : string1 > string2
 */
{
   const char	*s1 = *(char **) string1;
   const char	*s2 = *(char **) string2;
   unsigned	l1  = strlen (s1);
   unsigned	l2  = strlen (s2);

   /*
    *  KP_ entries should be grouped together
    */
   {
      char *t1 = g_strdup (s1);
      char *t2 = g_strdup (s2);

      if (l1 > 3)
	 t1 [3] = 0;
      if (l2 > 3)
	 t2 [3] = 0;

      if (strcaseeq (t1, "KP_") && !strcaseeq (t2, "KP_"))
      {
	 Free (t1);
	 Free (t2);
	 return +1;
      }
      if (strcaseeq (t2, "KP_") && !strcaseeq (t1, "KP_"))
      {
	 Free (t1);
	 Free (t2);
	 return -1;
      }
      Free (t1);
      Free (t2);
   }
   /*
    *  Fnum entries should be grouped together
    */
   if (s1 [0] != 'F' && l1 > 1)
      l1 +=2;
   if (s2 [0] != 'F' && l2 > 1)
      l2 +=2;

   if (l1 > 3 && l2 > 3)
      return strcmp (s1, s2);
   
   if (l1 != l2)
      return l1 - l2;

   if (l1 == 1)				/* letter or digit */
      return *s1 - *s2;

   return 0;
}

static void
set_key (GtkWidget *widget, gpointer ptr)
/*
 *  Change key attribute
 */
{
   key_widget_t	*kw   = (key_widget_t *) ptr;
   const char	*text = gtk_entry_get_text (GTK_ENTRY (kw->keyname));
   unsigned 	 n;
   
   if (strcaseeq (text, "None"))
   {
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->control), FALSE);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->shift), FALSE);
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (kw->mod [n]),
					   FALSE);
      
      gtk_widget_set_sensitive (kw->control, FALSE);
      gtk_widget_set_sensitive (kw->shift, FALSE);
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	    gtk_widget_set_sensitive (kw->mod [n], FALSE);
      gtk_widget_set_sensitive (kw->lcontrol, FALSE);
      gtk_widget_set_sensitive (kw->lshift, FALSE);
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	    gtk_widget_set_sensitive (kw->lmod [n], FALSE);

      Free (kw->keytext);
      kw->keytext = g_strdup (text);
   }
   else
   {
      char tmp [MAXSTRLEN];

      gtk_widget_set_sensitive (kw->control, TRUE);
      gtk_widget_set_sensitive (kw->shift, TRUE);
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	     gtk_widget_set_sensitive (kw->mod [n], TRUE);

      gtk_widget_set_sensitive (kw->lcontrol, TRUE);
      gtk_widget_set_sensitive (kw->lshift, TRUE);
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	     gtk_widget_set_sensitive (kw->lmod [n], TRUE);

      tmp [0] = 0;
      if (GTK_TOGGLE_BUTTON (kw->control)->active)
	 strcat (tmp, "Control+");
      if (GTK_TOGGLE_BUTTON (kw->shift)->active)
	 strcat (tmp, "Shift+");
      for (n = 0; n < 5; n++)
	 if (kw->mod [n])
	    if (GTK_TOGGLE_BUTTON (kw->mod [n])->active)
	    {
	       char *mod = g_strdup_printf ("Mod%d+", n + 1);
	       strcat (tmp, mod);
	       Free (mod);
	    }
      
      
      strcat (tmp, text);
   
      Free (kw->keytext);
      kw->keytext = g_strdup (tmp);
   }

   kw->update ((const key_widget_t *) kw);
}

static bool_t
modifier_available (const char *modifier, const char **modifier_list)
{
   unsigned n;

   for (n = 0; *modifier_list; modifier_list++)
      if (strcaseeq (*modifier_list, modifier))
	 return TRUE;

   return FALSE;
}
