/* build_keyboard.c
 * For use with GTKeyboard
 * Written by David Allen, s2mdalle@titan.vcu.edu
 * http://opop.nols.com/
 * 
 * This code controls the building of keyboard layouts.  It can destroy
 * and rebuild a keyboard layout, create an entire keyboard widget (which 
 * is a bunch of containers containing containers containing containers 
 * containing buttons - see build_an_entire_keyboard() ) and do other 
 * spiffy things.  It also has code to switch the memory pointers in the
 * options structure to point to the correct keyboard layout strings.
 */
/* GTKeyboard - A Graphical Keyboard For X
 * Copyright (C) 1999, 2000 David Allen  
 *
 * 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 Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#define BUILD_KEYBOARD_C
#include "master.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>

/* XPM pixmaps for build_cursorpad */
#include "xpm/about.h"
#include "xpm/license.h"
#include "share/tux.xpm"

#ifndef _RANDOMIZE_XPMS
#   define _RANDOMIZE_XPMS
#endif /* _RANDOMIZE_XPMS */

static char      *string_special_cases   (KeySym input);
static GtkWidget *build_keypad           (GtkWidget *input);

static GtkWidget *popup;

static void rotate_keyboard_definitions(KEYBOARD *new_keyboard)
{
     if(!new_keyboard)
     {
          fprintf(stderr,"rotate_keyboard_definitions Error: Bad keyboard.\n");
          fflush(stderr);
          return;
     } /* End if */

     /* If there is an old definition laying around, destroy it to free
      * up the memory.
      */
     if(options.old_keyboard)
     {
          options.old_keyboard = gtkeyboard_destroy_keyboard(
               options.old_keyboard);
#ifdef REMAPPING_DEBUGGING
          fprintf(Q,"Rotated old keyboard out...");
          fflush(Q);
#endif /* REMAPPING_DEBUGGING */
     } /* End if */
     
     /* Copy the current keyboard definition to the 'old' keyboard */
     options.old_keyboard = options.keyboard;

     /* Make the argument to this function the 'current' keyboard */
     options.keyboard = new_keyboard;
     
#ifdef REMAPPING_DEBUGGING
     fprintf(Q,"Old keyboard is now %s, Current keyboard is now %s\n",
             (options.old_keyboard ? "valid" : "dead"),
             (options.keyboard ? "valid" : "dead"));
     fflush(Q);
#endif /* REMAPPING_DEBUGGING */

     return;
} /* End rotate_keyboard_definitions() */

/* This destroys the key attached as object data to a lot of the buttons
 * when they get destroyed
 */
void destroy_key_widget_data(gpointer data)
{
     KEY *key = (KEY *)data;

     if(!key)
     {
          fprintf(stderr,"GTKeyboard error (destroy_key_widget_data) ");
          fprintf(stderr,"data == NULL!\n"); fflush(stderr);
          return;
     } /* End if */

     key = gtkeyboard_destroy_key(key);
} /* End destroy_key_widget_data() */

/* Call this for every widget that has a KEY structure as its object data
 * this makes sure that when the widget is destroyed, destroy_key_widget_data
 * gets called on the object data
 */
static void connect_destroy_signal(GtkWidget *widget, gpointer data)
{
     gtk_signal_connect_full(GTK_OBJECT(widget),
                             "destroy",
                             GTK_SIGNAL_FUNC(destroy_key_widget_data),
                             (GtkCallbackMarshal)gtk_signal_default_marshaller,
                             data,
                             (GtkDestroyNotify)destroy_key_widget_data,
                             FALSE,
                             TRUE);
} /* End connect_destroy_signal() */

void grab_smack(GtkWidget *e, gpointer d)
{
     GtkWidget *foo = e;
     GtkWidget *entry = (GtkWidget *)d;
     char *ptr;

     while(foo && !GTK_IS_FILE_SELECTION(foo) && !GTK_IS_WINDOW(foo))
	  foo = foo->parent;

     if(!foo || !GTK_IS_FILE_SELECTION(foo))
     {
	  fprintf(stderr,"Error:  Can't get file selection dialog.\n");
	  fflush(stderr);
	  return;
     } /* End if */
     else
     {
	  if(options.tempslot)
	  {
	       g_free_(options.tempslot);
	       options.tempslot = NULL;
	  } /* End if */

	  ptr = gtk_file_selection_get_filename(GTK_FILE_SELECTION(foo));

	  if(ptr)
	  {
               CONDFREE(options.tempslot);
	       options.tempslot = g_strdup_(ptr);
	  } /* End if */
	  else options.tempslot = NULL;

#ifdef DEBUGGING
	  fprintf(Q,"Grab_smack just pilfered \"%s\"\n",
		  options.tempslot); fflush(Q);
#endif
     } /* End else */
     
     if(options.tempslot && entry && GTK_IS_ENTRY(entry))
     {
	  gtk_entry_set_text(GTK_ENTRY(entry), options.tempslot);
	  gtk_widget_destroy(foo);
     } /* End if */
     else
     {
	  fprintf(Q,"Grab_smack():  Generic error, please report me.\n");
	  fflush(Q);
     } /* End else */
} /* End grab_smack() */

static gint triple_callback(GtkWidget *emitter, GdkEvent *event, gpointer data)
{
     KEY *k   = (KEY *)data;
     KEY *key = NO_KEY;

     if(!k)
     {
	  fprintf(stderr,"GTKeyboard internal error: %s: NULL \"KEY\" arg.\n",
		  "(triple_callback)");
	  fflush(stderr);
	  return TRUE;
     } /* End if */

     if(event->type == GDK_BUTTON_PRESS)
     {
	  key = gtkeyboard_key_copy(k);

	  if(event->button.button == LEFT_MOUSE_BUTTON)
	  {
	       /* Regular keypress, deal with it as normal */
	       keysym_callback((GtkWidget *)NULL, (gpointer)key);
	       key = gtkeyboard_destroy_key(key);
	       return TRUE;
	  } /* End if */
	  else if(event->button.button == MIDDLE_MOUSE_BUTTON)
	  {
	       KeySym lower, upper;

	       /* Always generate the "Alt-GR" keysym */
	       if(!key->alt_gr || key->alt_gr == NoSymbol)
	       {
		    chocolate("NoSym default to lower case.\n");
		    key->alt_gr = key->lower_case;
	       } /* End if */
	       key->lower_case = key->upper_case = key->alt_gr;

	       /* Not sure whether this is upper case or lower case.  Try to
		* find out by seeing if what XConvertCase returns is the same
		* or different.
		*/
	       XConvertCase(key->alt_gr, &lower, &upper);

	       /* If upper is the same as alt_gr, then we need shift to be
		* on.  Otherwise leave it however it is
		*/
	       if(key->alt_gr == upper)
		    options.SHIFT = ON;

	       keysym_callback((GtkWidget *)NULL, (gpointer)key);
	       /* Free the memory */
	       key = gtkeyboard_destroy_key(key);
	       return TRUE;
	  } /* End else if */
	  else if(event->button.button == RIGHT_MOUSE_BUTTON) 
	  {
	       /* Always generate the "uppercase" Keysym */
	       key->lower_case = key->alt_gr = key->upper_case;

	       options.SHIFT = ON;

	       keysym_callback((GtkWidget *)NULL, (gpointer)key);
	       key = gtkeyboard_destroy_key(key);
	       return TRUE;
	  } /* End if */
	  else
          {
               key = gtkeyboard_destroy_key(key);
               return FALSE;
          } /* End else */
     } /* End if */
     
     /* Tell calling code that we have not handled this event; pass it on. */
     return FALSE;
} /* End triple_callback() */

static void build_custom_keyboard(GtkWidget *emitter, gpointer data)
{
     GtkWidget *entry  = (GtkWidget *)data;
     char *filename = NULL;

     if(entry && GTK_IS_ENTRY(entry))
     {
	  filename = g_strdup_(gtk_entry_get_text(GTK_ENTRY(entry)));
     } /* End if */
     else
     {
	  fprintf(Q,"Error:  No entry widget:  build_custom_keyboard()\n");
	  fflush(Q);
	  return;
     } /* End else */

     if(popup && GTK_IS_WIDGET(popup))
     {
	  gtk_widget_destroy(popup);
	  popup = NULL;
     } /* End if */

     switch_keyboard((GtkWidget *)NULL, filename);

     CONDFREE(filename);
} /* End build_custom_keyboard() */

/* Calls build_custom_keyboard() */
void custom_keyboard_file_dialog(GtkWidget *emitter, gpointer data)
{
     GtkWidget *label;
     GtkWidget *browse;
     GtkWidget *entry_fu = gtk_entry_new_with_max_length(1024);
     GtkWidget *hbox     = gtk_hbox_new(FALSE, 0);
     GtkWidget *ok;
     GtkWidget *cancel;

     popup  = gtk_dialog_new();
     cancel = create_cancel_button(popup);
     ok     = create_ok_button(GTK_SIGNAL_FUNC(build_custom_keyboard), 
                               entry_fu);

     label = gtk_label_new("Specify a keyboard layout file. (*.key)\n");

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label,
			FALSE, FALSE, 0);

     browse = make_browse_button(entry_fu);

     gtk_box_pack_start(GTK_BOX(hbox), entry_fu, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("  "), FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(hbox), browse, FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), hbox, 
			FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			ok, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			cancel, FALSE, FALSE, 0);

     gtk_window_set_position(GTK_WINDOW(popup), GTK_WIN_POS_CENTER);
     gtkeyboard_window_common_setup(popup);
     gtk_widget_show_all(popup);
} /* End custom_keyboard_file_dialog() */

/* Takes an xpm, (it must be 30x30 10 colors right now) and randomizes its
 * colors through GUI.custom_colors[], changes some of the pointers in the
 * array of xpm[] to the new custom colors, and returns nothing.  This stuff
 * only happens if the user chose to set (RANDOMIZE_XPMS|EYECANDY) ON in 
 * the .gtkeyboardrc
 */
static void randomize_xpm(char *xpm[], const char *name, const int colors)
{
     long SIXTEEN_TO_THE_6TH = 16777216;    /* 16**6 */

     register int x=0;
     char ACHAR = 'a';

     for(x=0; x<colors; x++)
     {
          /* Get rid of it if it's already present */
          CONDFREE(GUI.custom_colors[x]);
	  GUI.custom_colors[x] = g_new_(char, 20);
#ifdef HAVE_RAND
	  sprintf(GUI.custom_colors[x],"%c       c #%lx", ACHAR, 
		  (rand() % SIXTEEN_TO_THE_6TH));
#else
	  /* Pretty boring if you don't have rand. :) */
	  sprintf(GUI.custom_colors[x],"%c       c #FFFFFF",ACHAR);
#endif /* HAVE_RAND */
	  
	  /* Replace with random GUI.custom_colors */
	  xpm[(x+2)] = GUI.custom_colors[x];
	  ACHAR++;
     } /* End for */
} /* End randomize_xpm */

void keysym_callback(GtkWidget *emitter, gpointer data)
{
     KEY *key = (KEY *)data;
     KeySym sym;
     char let;
     char buffer[1024];
     char *keyname = (char*)NULL;
     char *altkeyname = (char*)NULL;
     KeySym lower=(KeySym)NULL, upper=(KeySym)NULL;

#ifdef PARANOID_DEBUGGING
     fprintf(Q,"keysym_callback():  Got (%s, %s, %s).\n",
	     XKeysymToString(key->lower_case),
	     XKeysymToString(key->upper_case),
	     XKeysymToString(key->alt_gr)); fflush(Q);
#endif /* PARANOID_DEBUGGING */

     /* Determine which of the syms in the KEY * structure to use. */
     keyname    = XKeysymToString(key->lower_case);
     altkeyname = XKeysymToString(key->alt_gr);

     if(options.ALT_GR) 
     {
	  sym = key->alt_gr;
          /* We have only three symbols, and we have to generate
           * the fourth one for cyrillic letters.
           */
          if(strstr(altkeyname, "Cyrillic_"))
          {
               XConvertCase(sym, &lower, &upper);

               if(options.SHIFT || options.CAPS_LOCK)
               { 
                    sym = upper;
               } /* End if */
          } /* End if */
     } /* End if */
     else if(strstr(keyname, "KP"))
     {
	  if(options.NUMLOCK)
	       sym = key->upper_case;
	  else sym = key->lower_case;
     } /* End else if */
     else if(options.SHIFT)
	  sym = key->upper_case;
     else if(options.CAPS_LOCK)
     {
	  if(isalpha((char)key->upper_case))
	       sym = key->upper_case;
	  else sym = key->lower_case;
     } /* End else if */
     else sym = key->lower_case;

     completer_eavesdrop_on_letter(sym);

     if(options.redirect_window_name || options.SEND_TO_BOTH_WINDOWS)
     {
	  send_redirect_a_keysym(sym);

	  if(!options.SEND_TO_BOTH_WINDOWS)
	  {
	       /* We're done */
	       return;
	  } /* End if */
     } /* End if */

     /* Get that label krankin' */
     let = (char)sym;

     /* Check to see if irxevent turned it off.  If it didn't, we've already
      * used it, so turn it back off
      */
     if(options.SHIFT)
	  options.SHIFT = OFF;

     if(key->lower_case == XK_BackSpace)  /* Special cases, ho hum */
     {
	  gtk_text_backward_delete(GTK_TEXT(GUI.main_output_text), 1);
	  return;
     } /* End if */

     if(key->aux_string)                     /* prefs to special strings */
     {
	  sprintf(buffer,"%s",key->aux_string);
     } /* End else if */
     else if(sym == XK_Return)               /* I HATE SPECIAL CASES :) */
	  sprintf(buffer,"\n");
     else if(isalpha(let))
	  sprintf(buffer,"%c",let);
     else
	  sprintf(buffer,"%c",let);

     options.document_modified = ON;

     /* Cram the text into the "editing" buffer */
     gtk_text_insert(GTK_TEXT(GUI.main_output_text), NULL, 
		     NULL, NULL, buffer, strlen(buffer));
     
     if(ELEMENTS.text)
          gtk_widget_show_all(GUI.main_output_text);

     return;
} /* End keysym_callback() */

static int isspecial(KeySym input)
{
     char *ptr = XKeysymToString(input);

     if(input == NoSymbol || !ptr) return(1);
     if(strstr(ptr,"_L") || strstr(ptr,"_R")) return(1);
     if(strstr(ptr,"ontrol") || strstr(ptr,"Alt")) return(1);
     if(strstr(ptr,"ode")) return(1);
     if(strstr(ptr,"Tab") || strstr(ptr,"Lock") || strstr(ptr,"pace")) 
	  return(1);

     return(0);
} /* End isspecial */

static GtkWidget *build_keyboard(GtkWidget *input, char *filename)
{
     /* NEW BUILD_KEYBOARD() */
     GtkWidget *mainbox = gtk_vbox_new(FALSE, 0);
     GtkWidget *hbox    = (GtkWidget *)NULL;
     GtkWidget *button  = (GtkWidget *)NULL;
     char *name         = (char *)NULL;
     char *altname      = (char *)NULL;
     GtkWidget *align   = (GtkWidget *)NULL;
     char label[512];
     char tooltip_label[1024];
     char *errbuf = NULL;
     char *ptr;
     int rowno;
     int index;
     char letter = '\0';
     char cyrletter = '\0';
     KEY *key;
     KeySym s;
     KeySym altlower, altupper;

     /* Create the current keyboard in a new place. -- This takes care of
      * destroying our old ones for us.
      */
     rotate_keyboard_definitions(read_keyboard_template(filename));

     if(!options.keyboard)
     {
	  fprintf(stderr,"Couldn't read keyboard:  Bummer.\n");
	  fflush(stderr);
	  
	  errbuf = g_new0(char, strlen(filename) + 100);
	  sprintf(errbuf,"Couldn't create keyboard from file:\n%s!\nCheck the file format!",filename);

	  button = gtk_button_new_with_label(errbuf);
          
          CONDFREE(errbuf);
          
          /* Load the .key file loader when the user clicks this bogus 
           * button
           */
          gtk_signal_connect(GTK_OBJECT(button), "clicked",
                             GTK_SIGNAL_FUNC(custom_keyboard_file_dialog), 
                             NULL);
	  return(button);
     } /* End if */
     else if(options.keyboard->keycount <= 0)
     {
	  errbuf = g_new0(char, strlen(filename) + 100);
	  sprintf(errbuf,"Couldn't create keyboard from file:\n%s!\nCheck the file format!",filename);

	  button = gtk_button_new_with_label(errbuf);
          CONDFREE(errbuf);
	  return(button);
     } /* End else if */	  

     for(rowno = 0; rowno<MAXIMUM_ROWS; rowno++)
     {
	  hbox = gtk_hbox_new(FALSE, 0);
	  align = gtk_alignment_new(0.5, 0.5, 0, 0);

	  for(index = 0; index < options.keyboard->row_values[rowno]; index++)
	  {
	       key = gtkeyboard_keyboard_get_key(options.keyboard, 
						 rowno, index);
	       
	       letter = (int)key->upper_case;
	       name = XKeysymToString(key->upper_case);
               altname = XKeysymToString(key->alt_gr);

	       if(key->upper_case == XK_Mode_switch ||
		  key->lower_case == XK_Mode_switch)
               {
		    sprintf(label," Alt Gr");
               } /* End if */
               else if(strstr(altname,"Cyrillic_")) 
               {
                    /* We have only lower case letter, let us
                     * ask X to convert it to upper case.
                     */
                    XConvertCase(key->alt_gr, &altlower, &altupper);

                    /* FIXME: Yes, this is totally wrong method to get
                     * the cyrillic letter. It just happen to to
                     * yield the right letter in koi8-r encoding.
                     */   
                    cyrletter = (char)altupper;
                    if(!isalpha(letter))
                    {
                         sprintf(label," %c   \n %c  %c",
                                 (char)key->upper_case,
                                 (char)key->lower_case,
                                 cyrletter);
                    } /* End if */
                    else
                    {
                         sprintf(label," %c   \n    %c",
                                 (char)key->upper_case, cyrletter);
                    } /* End else */
               } /* End else if */
	       else if((isalnum(letter) || ispunct(letter)) && (letter > 0))
	       {
		    if(!isalpha(letter))
			 sprintf(label,"  %c  \n  %c",
				 (char)key->upper_case,
				 (char)key->lower_case);
		    else
			 sprintf(label,"  %c  \n",(char)key->upper_case);
	       } /* End if */
	       else if(letter != 0)
	       {
		    if(!iscntrl(letter) && !isspecial(key->upper_case) &&
		       letter != ' ')
			 sprintf(label,"  %c  \n  %c",
				 (char)key->upper_case, 
				 (char)key->lower_case);
		    else
		    {
			 ptr = string_special_cases(key->lower_case);
			 strncpy(label,ptr,512);
			 g_free_(ptr);
		    } /* End else */
	       } /* End else if */
	       else
	       {
		    ptr = string_special_cases(key->lower_case);
		    strncpy(label, ptr, 512);
		    g_free_(ptr);
	       } /* End else */

	       s = key->lower_case;
	       
	       /* Make the correct key, and attach the correct signal
		* function to it.  Toggle/normal button/function
		*/
	       if(s == XK_Caps_Lock || s == XK_Control_L ||
		  s == XK_Control_R || s == XK_Alt_L ||
		  s == XK_Alt_R || s == XK_Mode_switch)
		    button = gtk_toggle_button_new_with_label(label);
	       else
		    button = gtk_button_new_with_label(label);
	       
	       if(key->code != 0)
		    sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
			    key->code,
			    XKeysymToString(key->lower_case),
			    XKeysymToString(key->upper_case),
			    XKeysymToString(key->alt_gr));
	       else sprintf(tooltip_label,"KeyCode unknown:\n%s\n%s\n%s",
			    XKeysymToString(key->lower_case),
			    XKeysymToString(key->upper_case),
			    XKeysymToString(key->alt_gr));
	       
	       /* Each key should have a tooltip describing what it does */
	       add_tooltip(button, tooltip_label);

	       switch(key->lower_case)
	       {
	       case XK_Caps_Lock:   
		    gtk_signal_connect(GTK_OBJECT(button), "clicked",
				       GTK_SIGNAL_FUNC(capslock_toggle), 
				       input); 
                    /* Key unused in signalling */
                    key = gtkeyboard_destroy_key(key);
                    break;
	       case XK_Alt_L: case XK_Alt_R:
		    gtk_signal_connect(GTK_OBJECT(button), "clicked",
				       GTK_SIGNAL_FUNC(alt_toggle), 
				       GUI.status); 
                    /* Key unused in signalling */
                    key = gtkeyboard_destroy_key(key);
                    break;
	       case XK_Control_L: case XK_Control_R:
		    gtk_signal_connect(GTK_OBJECT(button), "clicked",
				       GTK_SIGNAL_FUNC(control_toggle),
				       GUI.status); 
                    /* Key unused in signalling */
                    key = gtkeyboard_destroy_key(key);
                    break;
	       case XK_Shift_L: case XK_Shift_R:
		    gtk_signal_connect(GTK_OBJECT(button), "clicked",
				       GTK_SIGNAL_FUNC(shift_on), 
				       input); 
                    /* Key unused in signalling */
                    key = gtkeyboard_destroy_key(key);
                    break;
	       case XK_Mode_switch: 
		    gtk_signal_connect(GTK_OBJECT(button), "clicked",
				       GTK_SIGNAL_FUNC(alt_gr_toggle), 
				       GUI.status); 
                    /* Key unused in signalling */
                    key = gtkeyboard_destroy_key(key);
                    break;
	       default:
		    gtk_signal_connect(GTK_OBJECT(button),
				       "button_press_event",
				       GTK_SIGNAL_FUNC(triple_callback),
				       key);
                    connect_destroy_signal(button, key);
		    break; 
	       } /* End switch */

	       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	  } /* End for */

	  gtk_container_add(GTK_CONTAINER(align), hbox);
	  gtk_box_pack_start(GTK_BOX(mainbox), align, FALSE, FALSE, 0);
     } /* End for */

     if(filename)
     {
          if(options.keyboard_file)
          {
               /* We just built a complete keyboard with this file, so save its
                * name for future use.
                */
               /* This weird indirect freeing and copying of the string is
                * due to the fact that the filename argument to this function
                * may in fact be options.keyboard_file itself, so in that
                * case it wouldn't be that bright to try to free it and 
                * copy something that's pointing to the same location.  So
                * instead we copy it to an intermediate spot, free the 
                * original, and copy the new value back.
                *
                * When the value actually is options.keyboard_file, we do a 
                * bit of redundant work, but oh well.
                */
               char *tptr;
               tptr = g_strdup_(filename);
               g_free_(options.keyboard_file);
               options.keyboard_file = g_strdup_(tptr);
               g_free_(tptr);
               fprintf(Q,"options.keyboard_file set to be \"%s\"\n",
                       options.keyboard_file);
               fflush(Q);
          } /* End if */
          else 
          {
               /* No need to free it - just copy */
               options.keyboard_file = g_strdup_(filename);
               fprintf(Q,"options.keyboard_file set to be \"%s\"\n",
                       options.keyboard_file);
               fflush(Q);
          } /* End else */
     } /* End if */

     /* gtk_widget_show_all(mainbox); */
     return(mainbox);
} /* End build_keyboard() */

static char *string_special_cases(KeySym input)
{
     char label[1024];
     int len, x;
     char *ptr;
     
     if(input == XK_space)
     {
	  sprintf(label,"                                               \n");
     } /* End if */
     else
     {
	  /* Space out the output a bit depending on string target
	   * length so the buttons will look right.
	   */
	  ptr = XKeysymToString(input);
	  if(strlen(ptr) > 4)
	       sprintf(label," %s \n",ptr);
	  else sprintf(label,"  %s  \n",ptr);
     } /* End else */

     len = strlen(label);

     /* Special cases */
     if(strstr(label,"Control") || strstr(label,"control"))
     {
	  strcpy(label," Ctrl ");
     } /* End if */
     else if(input == XK_Mode_switch)
	  strcpy(label," Alt Gr");
     else 
     {
	  /* Remove the sillyness from XK_ names from the string. */
	  for(x=0; x<len; x++)
	  {
	       /* Make everything uppercase */
	       label[x] = toupper(label[x]);
	       
	       /* Get rid of the _R or _L that may be there. */
	       if(label[x] == '_' &&
		  (label[x+1] == 'R' ||
		   label[x+1] == 'L'))
	       {
		    label[x] = '\0';
	       } /* End if */
	  } /* End for */
     } /* End else */

     ptr = g_strdup_(label);

     return(ptr);
} /* End string_special_cases() */

/* Builds the entire keypad into a large table, connects all the signals,
 * and returns - there's some ugly table work in here to make everything
 * look purty on the screen.
 */
static GtkWidget *build_keypad(GtkWidget *input)
{
     GtkWidget *number[10], *numlock, *div, *mul, *minus, *plus,
	  *enter, *dot;
     KEY *number_key[10], *numlock_key, *div_key, *mul_key, *minus_key, 
	  *plus_key, *enter_key, *dot_key;
     KeySym dummy_sym;
     register int x=0;
     char buttonlabel[20];
     char tooltip_label[1024];
     GtkWidget *ktable;
     char passing[512];

     KeySym altsyms[10] = { XK_KP_Insert, /* 0 1 2 3 4 5 6 7 8 9 */
			    XK_KP_End,    XK_KP_Down,   XK_KP_Page_Down,
			    XK_KP_Left,   XK_KP_5,      XK_KP_Right,
			    XK_KP_Home,   XK_KP_Up,     XK_KP_Page_Up };

     for(x=0; x<10; x++)
     {
	  sprintf(buttonlabel," %d \n",x);

	  sprintf(passing,"KP_%d",x);
	  dummy_sym = XStringToKeysym(passing);
	  
	  sprintf(passing,"%d",x);

	  number_key[x] = gtkeyboard_new_key(altsyms[x],
					     dummy_sym, 
					     dummy_sym,
					     passing);

	  number[x] = gtk_button_new_with_label(buttonlabel);

	  sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
		  XKeysymToKeycode(GDK_DISPLAY(), number_key[x]->lower_case),
		  XKeysymToString(number_key[x]->lower_case),
		  XKeysymToString(number_key[x]->upper_case),
		  XKeysymToString(number_key[x]->alt_gr));

	  add_tooltip(number[x], tooltip_label);

	  gtk_signal_connect(GTK_OBJECT(number[x]),
			     "button_press_event",
			     GTK_SIGNAL_FUNC(triple_callback),
			     number_key[x]);
          connect_destroy_signal(number[x], number_key[x]);
     } /* End for */

     numlock     = gtk_toggle_button_new_with_label("Num\nLck");
     numlock_key = gtkeyboard_new_key(XK_Num_Lock, XK_Num_Lock, XK_Num_Lock,
				      "Num_Lock");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), numlock_key->lower_case),
	     XKeysymToString(numlock_key->lower_case),
	     XKeysymToString(numlock_key->upper_case),
	     XKeysymToString(numlock_key->alt_gr));
     add_tooltip(numlock, tooltip_label);

     div         = gtk_button_new_with_label(" / ");
     div_key     = gtkeyboard_new_key(XK_KP_Divide, XK_KP_Divide, 
				      XK_KP_Divide, "/");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), div_key->lower_case),
	     XKeysymToString(div_key->lower_case),
	     XKeysymToString(div_key->upper_case),
	     XKeysymToString(div_key->alt_gr));
     add_tooltip(div, tooltip_label);

     minus       = gtk_button_new_with_label(" - ");
     minus_key   = gtkeyboard_new_key(XK_KP_Subtract, XK_KP_Subtract, 
				      XK_KP_Subtract, "-");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), minus_key->lower_case),
	     XKeysymToString(minus_key->lower_case),
	     XKeysymToString(minus_key->upper_case),
	     XKeysymToString(minus_key->alt_gr));
     add_tooltip(minus, tooltip_label);

     plus        = gtk_button_new_with_label(" + ");
     plus_key    = gtkeyboard_new_key(XK_KP_Add, XK_KP_Add, XK_KP_Add, "+");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), plus_key->lower_case),
	     XKeysymToString(plus_key->lower_case),
	     XKeysymToString(plus_key->upper_case),
	     XKeysymToString(plus_key->alt_gr));
     add_tooltip(plus, tooltip_label);

     mul         = gtk_button_new_with_label(" * ");
     mul_key     = gtkeyboard_new_key(XK_KP_Multiply, XK_KP_Multiply,
				      XK_KP_Multiply, "*");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), mul_key->lower_case),
	     XKeysymToString(mul_key->lower_case),
	     XKeysymToString(mul_key->upper_case),
	     XKeysymToString(mul_key->alt_gr));
     add_tooltip(mul, tooltip_label);

     enter       = gtk_button_new_with_label("Enter");
     enter_key   = gtkeyboard_new_key(XK_KP_Enter, XK_KP_Enter, XK_KP_Enter,
				      "\n");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), enter_key->lower_case),
	     XKeysymToString(enter_key->lower_case),
	     XKeysymToString(enter_key->upper_case),
	     XKeysymToString(enter_key->alt_gr));
     add_tooltip(enter, tooltip_label);

     dot         = gtk_button_new_with_label(" . ");
     dot_key     = gtkeyboard_new_key(XK_KP_Decimal, XK_KP_Decimal,
				      XK_KP_Decimal, ".");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), dot_key->lower_case),
	     XKeysymToString(dot_key->lower_case),
	     XKeysymToString(dot_key->upper_case),
	     XKeysymToString(dot_key->alt_gr));
     add_tooltip(dot, tooltip_label);

     /* Connect some signals, yo */

     gtk_signal_connect(GTK_OBJECT(enter), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), enter_key);
     connect_destroy_signal(enter, enter_key);
     gtk_signal_connect(GTK_OBJECT(div), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), div_key);
     connect_destroy_signal(div, div_key);
     gtk_signal_connect(GTK_OBJECT(mul), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), mul_key);
     connect_destroy_signal(mul, mul_key);
     gtk_signal_connect(GTK_OBJECT(minus), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), minus_key);
     connect_destroy_signal(minus, minus_key);
     gtk_signal_connect(GTK_OBJECT(dot), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), dot_key);
     connect_destroy_signal(dot, dot_key);
     gtk_signal_connect(GTK_OBJECT(plus), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), plus_key);
     connect_destroy_signal(plus, plus_key);
     gtk_signal_connect(GTK_OBJECT(numlock), "clicked",
			GTK_SIGNAL_FUNC(toggle_numlock), NULL);
     connect_destroy_signal(numlock, numlock_key);

     /* Since this is an all-around equal grid, make homogenous TRUE */
     ktable = gtk_table_new((gint)5, (gint)4, TRUE);

     /* 0 */
     gtk_table_attach(GTK_TABLE(ktable), number[0], 0, 2, 4, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 1 */
     gtk_table_attach(GTK_TABLE(ktable), number[1], 0, 1, 3, 4,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 2 */
     gtk_table_attach(GTK_TABLE(ktable), number[2], 1, 2, 3, 4,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 3 */
     gtk_table_attach(GTK_TABLE(ktable), number[3], 2, 3, 3, 4,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 4 */
     gtk_table_attach(GTK_TABLE(ktable), number[4], 0, 1, 2, 3, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 5 */
     gtk_table_attach(GTK_TABLE(ktable), number[5], 1, 2, 2, 3, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 6 */
     gtk_table_attach(GTK_TABLE(ktable), number[6], 2, 3, 2, 3,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 7 */
     gtk_table_attach(GTK_TABLE(ktable), number[7], 0, 1, 1, 2, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 8 */
     gtk_table_attach(GTK_TABLE(ktable), number[8], 1, 2, 1, 2,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* 9 */
     gtk_table_attach(GTK_TABLE(ktable), number[9], 2, 3, 1, 2,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);

     /* Table packing of non-number keys */
     /* . */
     gtk_table_attach(GTK_TABLE(ktable), dot, 2, 3, 4, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* \n */
     gtk_table_attach(GTK_TABLE(ktable), enter, 3, 4, 3, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* + */
     gtk_table_attach(GTK_TABLE(ktable), plus, 3, 4, 1, 3,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* - */
     gtk_table_attach(GTK_TABLE(ktable), minus, 3, 4, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* NUMLOCK */
     gtk_table_attach(GTK_TABLE(ktable), numlock, 0, 1, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* / */
     gtk_table_attach(GTK_TABLE(ktable), div, 1, 2, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* * */
     gtk_table_attach(GTK_TABLE(ktable), mul, 2, 3, 0, 1, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,  0, 0);
     /* gtk_widget_show_all(ktable); */
     
     return (ktable);
} /* End build_keypad */

/* Builds the cursor pad, which includes HOME, INSERT, PAGE_(UP|DOWN),
 * DELETE, END, UP, DOWN, LEFT, RIGHT, and either randomized xpms or 
 * boring simple labels.
 */
static GtkWidget *build_cursorpad(GtkWidget *foo)
{
     GtkWidget *left, *right, *up, *down;
     GtkWidget *arrows[4];
     GtkWidget *oddballs[3];
     KEY *oddball_keys[3];
     KEY *key;
     GtkWidget *cbuttons[60];
     GtkWidget *buttonpropaganda;
     char buttonlabel[512];
     
     GtkWidget *about   = gtk_button_new();
     GtkWidget *license = gtk_button_new();
     GtkWidget *aboutpixmap = NULL, *licensepixmap = NULL;
     GtkWidget *ctable;
     GtkWidget *label1 = gtk_label_new("About");
     GtkWidget *label2 = gtk_label_new("License");
     char tooltip_label[1024];

     about         = gtk_button_new();
     license       = gtk_button_new();

     sprintf(tooltip_label,"About\nGTKeyboard");
     add_tooltip(about, tooltip_label);
     sprintf(tooltip_label,"GTKeyboard\nLicense");
     add_tooltip(license,tooltip_label);

     oddballs[0]     = gtk_button_new_with_label("Print\nScreen");
     oddball_keys[0] = gtkeyboard_new_key(XK_Print,
					  XK_Print,
					  XK_Print,
					  "Print Screen");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), oddball_keys[0]->lower_case),
	     XKeysymToString(oddball_keys[0]->lower_case),
	     XKeysymToString(oddball_keys[0]->upper_case),
	     XKeysymToString(oddball_keys[0]->alt_gr));
     add_tooltip(oddballs[0], tooltip_label);

     oddballs[1]     = gtk_button_new_with_label("Scroll\nLock");
     oddball_keys[1] = gtkeyboard_new_key(XK_Scroll_Lock, XK_Scroll_Lock,
					  XK_Scroll_Lock, "Scroll Lock");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), oddball_keys[0]->lower_case),
	     XKeysymToString(oddball_keys[1]->lower_case),
	     XKeysymToString(oddball_keys[1]->upper_case),
	     XKeysymToString(oddball_keys[1]->alt_gr));
     add_tooltip(oddballs[1], tooltip_label);

     oddballs[2]     = gtk_button_new_with_label("Pause\nBreak");
     oddball_keys[2] = gtkeyboard_new_key(XK_Pause, XK_Break, XK_Break,
					  "Pause/Break");

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), oddball_keys[2]->lower_case),
	     XKeysymToString(oddball_keys[2]->lower_case),
	     XKeysymToString(oddball_keys[2]->upper_case),
	     XKeysymToString(oddball_keys[2]->alt_gr));
     add_tooltip(oddballs[2], tooltip_label);

     gtk_signal_connect(GTK_OBJECT(oddballs[0]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), oddball_keys[0]);
     connect_destroy_signal(oddballs[0], oddball_keys[0]);
     gtk_signal_connect(GTK_OBJECT(oddballs[1]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), oddball_keys[1]);
     connect_destroy_signal(oddballs[1], oddball_keys[1]);
     gtk_signal_connect(GTK_OBJECT(oddballs[2]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), oddball_keys[2]);
     connect_destroy_signal(oddballs[2], oddball_keys[2]);

     if(options.RANDOMIZE_XPMS)   /* Use xpms, and randomize them */
     {
	  randomize_xpm(xpm_about, "(About)", 9);
	  randomize_xpm(xpm_license, "(License)", 9);
	  aboutpixmap   = xpm_to_widget(window, (gchar **)xpm_about);
	  licensepixmap = xpm_to_widget(window, (gchar **)xpm_license);
	  gtk_container_add(GTK_CONTAINER(about), aboutpixmap);
	  gtk_container_add(GTK_CONTAINER(license), licensepixmap);
     } /* End if */
     else                        /* Use plain, boring labels */
     {
	  gtk_container_add(GTK_CONTAINER(about), label1);
	  gtk_container_add(GTK_CONTAINER(license), label2);
     } /* End else */

     gtk_signal_connect(GTK_OBJECT(about), "button_press_event",
			GTK_SIGNAL_FUNC(program_info), 
                        GINT_TO_POINTER(INFO_CHANGELOG));

     gtk_signal_connect(GTK_OBJECT(license), "button_press_event",
			GTK_SIGNAL_FUNC(program_info), 
			GINT_TO_POINTER(INFO_LICENSE));

     /* Make me some arrows, dammit. */
     arrows[0] = xpm_to_widget(foo, (gchar **)xpm_cursor_left);
     arrows[1] = xpm_to_widget(foo, (gchar **)xpm_cursor_right);
     arrows[2] = xpm_to_widget(foo, (gchar **)xpm_cursor_up);
     arrows[3] = xpm_to_widget(foo, (gchar **)xpm_cursor_down);

     sprintf(buttonlabel,"-= %s %s =-\n%s",APPNAME, VERSION, APPURL);
     buttonpropaganda = gtk_button_new_with_label(buttonlabel);

     gtk_signal_connect(GTK_OBJECT(buttonpropaganda), "button_press_event",
			GTK_SIGNAL_FUNC(splash), NULL);

     cbuttons[0] = gtk_button_new_with_label("Insert\n");
     cbuttons[1] = gtk_button_new_with_label("Home  \n");
     cbuttons[2] = gtk_button_new_with_label("Page\n Up");
     cbuttons[3] = gtk_button_new_with_label("Delete\n");
     cbuttons[4] = gtk_button_new_with_label(" End  \n");
     cbuttons[5] = gtk_button_new_with_label("Page\nDown");

     key = gtkeyboard_new_key(XK_Insert, XK_Insert, XK_Insert,
			      "Insert");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), key->lower_case),
	     XKeysymToString(key->lower_case),
	     XKeysymToString(key->upper_case),
	     XKeysymToString(key->alt_gr));
     add_tooltip(cbuttons[0], tooltip_label);

     /* Connect signals to the top two rows of keys */
     gtk_signal_connect(GTK_OBJECT(cbuttons[0]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), key);
     connect_destroy_signal(cbuttons[0], key);

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), XK_Home),
	     XKeysymToString(XK_Home),
	     XKeysymToString(XK_Home),
	     XKeysymToString(XK_Home));
     add_tooltip(cbuttons[1], tooltip_label);
     gtk_signal_connect(GTK_OBJECT(cbuttons[1]), "clicked",
			GTK_SIGNAL_FUNC(goto_bol), NULL);

     key = gtkeyboard_new_key(XK_Page_Up, XK_Page_Up, XK_Page_Up,
			      "Page Up");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), key->lower_case),
	     XKeysymToString(key->lower_case),
	     XKeysymToString(key->upper_case),
	     XKeysymToString(key->alt_gr));
     add_tooltip(cbuttons[2], tooltip_label);

     gtk_signal_connect(GTK_OBJECT(cbuttons[2]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), key);
     connect_destroy_signal(cbuttons[2], key);

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), XK_Delete),
	     XKeysymToString(XK_Delete),
	     XKeysymToString(XK_Delete),
	     XKeysymToString(XK_Delete));
     add_tooltip(cbuttons[3], tooltip_label);
     gtk_signal_connect(GTK_OBJECT(cbuttons[3]), "clicked",
			GTK_SIGNAL_FUNC(delete_key), GUI.main_output_text);

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), XK_End),
	     XKeysymToString(XK_End),
	     XKeysymToString(XK_End),
	     XKeysymToString(XK_End));
     add_tooltip(cbuttons[4], tooltip_label);     
     gtk_signal_connect(GTK_OBJECT(cbuttons[4]), "clicked",
			GTK_SIGNAL_FUNC(goto_eol), NULL);
     
     key = gtkeyboard_new_key(XK_Page_Down, XK_Page_Down, 
			      XK_Page_Down, "Page Down");
     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     XKeysymToKeycode(GDK_DISPLAY(), key->lower_case),
	     XKeysymToString(key->lower_case),
	     XKeysymToString(key->upper_case),
	     XKeysymToString(key->alt_gr));
     add_tooltip(cbuttons[5], tooltip_label);

     gtk_signal_connect(GTK_OBJECT(cbuttons[5]), "button_press_event",
			GTK_SIGNAL_FUNC(triple_callback), key);
     connect_destroy_signal(cbuttons[5], key);

     ctable = gtk_table_new((gint)6, (gint)3, TRUE);

     /* Ugly table code.  Wish there was a decent non kludge way to automate
      * this crap.
      */

     gtk_table_attach(GTK_TABLE(ctable), oddballs[0], 0, 1, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), oddballs[1], 1, 2, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), oddballs[2], 2, 3, 0, 1,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     gtk_table_attach(GTK_TABLE(ctable), cbuttons[0], 0, 1, 1, 2,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), cbuttons[1], 1, 2, 1, 2,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), cbuttons[2], 2, 3, 1, 2,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     gtk_table_attach(GTK_TABLE(ctable), cbuttons[3], 0, 1, 2, 3,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), cbuttons[4], 1, 2, 2, 3,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), cbuttons[5], 2, 3, 2, 3,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     gtk_table_attach(GTK_TABLE(ctable), buttonpropaganda, 0, 3, 3, 4,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     left  = gtk_button_new();
     right = gtk_button_new();
     up    = gtk_button_new();
     down  = gtk_button_new();

     /* Add the arrow XPMs to the widgets */
     gtk_container_add(GTK_CONTAINER(left), arrows[0]);
     gtk_container_add(GTK_CONTAINER(right), arrows[1]);
     gtk_container_add(GTK_CONTAINER(up), arrows[2]);
     gtk_container_add(GTK_CONTAINER(down), arrows[3]);
     
     /* Signals for the arrow keys */
     gtk_signal_connect(GTK_OBJECT(up), "clicked",
			GTK_SIGNAL_FUNC(cursor_up), NULL);
     gtk_signal_connect(GTK_OBJECT(left), "clicked",
			GTK_SIGNAL_FUNC(cursor_left), NULL);
     gtk_signal_connect(GTK_OBJECT(right), "clicked",
			GTK_SIGNAL_FUNC(cursor_right), NULL);
     gtk_signal_connect(GTK_OBJECT(down), "clicked",
			GTK_SIGNAL_FUNC(cursor_down), NULL);

     /* More table-fu */
     gtk_table_attach(GTK_TABLE(ctable), about, 0, 1, 4, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), up, 1, 2, 4, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), license, 2, 3, 4, 5,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     gtk_table_attach(GTK_TABLE(ctable), left, 0, 1, 5, 6,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), down, 1, 2, 5, 6,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     gtk_table_attach(GTK_TABLE(ctable), right, 2, 3, 5, 6,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     
     /* gtk_widget_show_all(ctable); */
     return(ctable);
} /* End build_cursorpad */

/* 
 * Builds the F-KEYS (F1-F12) and Print Screen, Scroll Lock, Pause/Break
 */
static GtkWidget *build_fkeys(void)
{
     /* The strings that get assigned to these various keys are what I
      * got out of keysymdef.h which came with the X11R6 headers
      */
     GtkWidget *buttons[13];
     KEY *buttons_key[13];
     int x=0;
     GtkWidget *container = gtk_hbox_new(FALSE, 0);
     GtkWidget *dummy1, *dummy2, *dummy3, *dummy4;
     KeySym sym;
     KeyCode code;
     char Fnum[8];
     char foobar[10];
     char tooltip_label[1024];
     char *ptr;
     
     dummy1 = gtk_label_new("   ");
     dummy2 = gtk_label_new("   ");
     dummy3 = gtk_label_new("   ");
     dummy4 = gtk_label_new("   ");
     
     buttons[0]     = gtk_button_new_with_label(" ESC ");
     buttons_key[0] = gtkeyboard_new_key(XK_Escape, XK_Escape, 
					 XK_Escape, "ESC");
     code = XKeysymToKeycode(GDK_DISPLAY(), buttons_key[0]->lower_case);

     sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
	     code, 
	     XKeysymToString(buttons_key[0]->lower_case),
	     XKeysymToString(buttons_key[0]->upper_case),
	     XKeysymToString(buttons_key[0]->alt_gr));
     add_tooltip(buttons[0], tooltip_label);

     gtk_signal_connect(GTK_OBJECT(buttons[0]),
			"button_press_event",
			GTK_SIGNAL_FUNC(triple_callback),
			buttons_key[0]);
     connect_destroy_signal(buttons[0], buttons_key[0]);

     for(x=1; x<13; x++)
     {
	  sprintf(foobar," F%d \n",x);
	  sprintf(Fnum,"F%d",x);
	  
	  sym = XStringToKeysym(Fnum);
	  code = XKeysymToKeycode(GDK_DISPLAY(), sym);
	  ptr = XKeysymToString(sym);

	  sprintf(tooltip_label,"KeyCode %d:\n%s\n%s\n%s",
		  code, ptr, ptr, ptr);

	  buttons[x] = gtk_button_new_with_label(foobar);
	  add_tooltip(buttons[x], tooltip_label);

	  buttons_key[x] = gtkeyboard_new_key(sym, sym, sym, Fnum);
	  /* Display_key handles whether or not we have a redirect_window */
	  gtk_signal_connect(GTK_OBJECT(buttons[x]),
			     "button_press_event",
			     GTK_SIGNAL_FUNC(triple_callback),
			     buttons_key[x]);
          connect_destroy_signal(buttons[x], buttons_key[x]);
     } /* End for */
     
     /* Pack the buttons into a row with the four dummy labels as spacers.
      * packing goes like this:
      * ESC    F1 F2 F3 F4    F5 F6 F7 F8    F9 F10 F11 F12  PrScr Scrl Pause
      */

     gtk_box_pack_start(GTK_BOX(container), buttons[0], FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(container), dummy1, FALSE, FALSE, 0);

     /* Stack the fkeys together with spaces interspersed */
     for(x=1; x<5; x++)
	  gtk_box_pack_start(GTK_BOX(container), buttons[x], FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(container), dummy2, FALSE, FALSE, 0);

     for(x=5; x<9; x++)
	  gtk_box_pack_start(GTK_BOX(container), buttons[x], FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(container), dummy3, FALSE, FALSE, 0);

     for(x=9; x<13; x++)
	  gtk_box_pack_start(GTK_BOX(container), buttons[x], FALSE, FALSE, 0);

     /* gtk_widget_show_all(container); */
     return(container);
} /* End build_fkeys */

/* Changes the onscreen keyboard on the fly to whatever the user chooses
 * through the menu system.  keyboardID is going to be a predefined number
 * in master.h (or maybe in an enum) casted to a gint for this function
 */
void switch_keyboard(GtkWidget *emitter, gpointer data)
{
     char *keybname = (char *)data;

     if(!data)
     {
	  fprintf(stderr,"switch_keyboard():  bad keyboard definiton.\n");
	  fflush(stderr);
	  return;
     } /* End if */

     gtk_widget_hide(GUI.KEYBOARD);
     gtk_widget_destroy(GUI.KEYBOARD);
     
     /* Eliminate the GUI.keyboard_element references.  It's possible
      * that the next keyboard won't happen correctly and then the user
      * would reference them, which would suck.  :)
      */
     GUI.keyboard_elements.cursor_keys = GUI.keyboard_elements.keyboard =
	  GUI.keyboard_elements.number_pad = GUI.keyboard_elements.f_keys = 
	  (GtkWidget *)NULL;

#ifdef DEBUGGING
     fprintf(Q,"Switching to keyboard layout located at \"%s\"\n",
	     keybname); fflush(Q);
#endif
     
     GUI.KEYBOARD = build_an_entire_keyboard((GtkWidget *)NULL, 
					     keybname);
     
     gtk_box_pack_start(GTK_BOX(GUI.keyboard_socket), GUI.KEYBOARD, 
			FALSE, FALSE, 0);
     
     gtk_widget_show(GUI.KEYBOARD);
     gtk_widget_show(GUI.keyboard_socket);
     
     do_keyboard_sublayout();
} /* End switch_keyboards */

GtkWidget *build_an_entire_keyboard(GtkWidget *emitter, char *filename)
{
     GtkWidget *KBD, *CURSORS, *FKEYS, *KEYPAD;
     GtkWidget *v1, *v2, *h1, *bb, *nlbox;
     GtkWidget *pulloff;
     GtkWidget *center = gtk_alignment_new(0.5, 0.5, 0, 0);
     GTKeyboardStyle stylish = { NULL, 0 };

     if(GUI.use_handleboxes)
     {
          pulloff  = gtk_handle_box_new();
          set_handle_box_opts(pulloff);
     } /* End if */
     else pulloff  = gtk_hbox_new(FALSE, 0);
     
     KBD      = build_keyboard(GUI.status, filename);
     CURSORS  = build_cursorpad(window);
     FKEYS    = build_fkeys();
     KEYPAD   = build_keypad(GUI.status);

     GUI.keyboard_elements.keyboard    = KBD;
     GUI.keyboard_elements.cursor_keys = CURSORS;
     GUI.keyboard_elements.f_keys      = FKEYS;
     GUI.keyboard_elements.number_pad  = KEYPAD;

     v1 = gtk_vbox_new(FALSE, 0);
     v2 = gtk_vbox_new(FALSE, 0);
     h1 = gtk_hbox_new(FALSE, 0);
     bb = gtk_hbox_new(FALSE, 0);
     nlbox = gtk_vbox_new(FALSE, 0);

     /* pop-up menu signals connected to container widgets */
     gtk_signal_connect_object(GTK_OBJECT(v1), "button_press_event",
			       GTK_SIGNAL_FUNC(button_press), 
			       GTK_OBJECT(GUI.popup_menu));
     gtk_signal_connect_object(GTK_OBJECT(v2), "button_press_event",
			       GTK_SIGNAL_FUNC(button_press), 
			       GTK_OBJECT(GUI.popup_menu));
     gtk_signal_connect_object(GTK_OBJECT(h1), "button_press_event",
			       GTK_SIGNAL_FUNC(button_press), 
			       GTK_OBJECT(GUI.popup_menu));
     gtk_signal_connect_object(GTK_OBJECT(bb), "button_press_event",
			       GTK_SIGNAL_FUNC(button_press), 
			       GTK_OBJECT(GUI.popup_menu));
     gtk_signal_connect_object(GTK_OBJECT(nlbox), "button_press_event",
			       GTK_SIGNAL_FUNC(button_press), 
			       GTK_OBJECT(GUI.popup_menu));

     gtk_box_pack_start(GTK_BOX(v1), FKEYS, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(v1), KBD, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(h1), CURSORS, FALSE, FALSE, 0);

     gtk_box_pack_end(GTK_BOX(nlbox), KEYPAD, FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(h1), nlbox, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(v2), h1, FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(bb), v1, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(bb), v2, FALSE, FALSE, 0);

     gtk_container_add(GTK_CONTAINER(center), bb);
     gtk_container_add(GTK_CONTAINER(pulloff), center);

     /* Show all containers and non-visible widgets, but not the containers
      * with the buttons.  They will be shown later.
      */
     gtk_widget_show(nlbox);
     gtk_widget_show(center);
     gtk_widget_show(pulloff);
     gtk_widget_show(v1);
     gtk_widget_show(v2);
     gtk_widget_show(h1);
     gtk_widget_show(bb);

     if(GUI.style)
          stylish.style = GUI.style;
     else stylish.style = gtk_widget_get_default_style();

     /* 
      * If we already have a keyboard font specified, copy it.  That's
      * what the current widget is using, and that's what the user will
      * expect
      */
     if(GUI.kfont) /* Keyboard font */
          stylish.style->font = GUI.kfont;

     stylish.mask = 0;

     SetStyleRecursively(pulloff, (gpointer)&stylish);

     return(pulloff);
} /* End build_an_entire_keyboard */

/* EOF */

