/* completer.c
 * Written by David Allen <s2mdalle@titan.vcu.edu>
 * http://opop.nols.com/
 * 
 * This file is basically the graphical front end for libwcomp written
 * by Dr. Tom.  
 */
/* 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 COMPLETER_C

#include "master.h"
#include "xpm/insert_completion.h"
#include "libwcomp/config.h"

#define CACHE_FILENAME_ERROR                     "none"

static GtkWidget *completer_window = NULL;
static GtkWidget *inputlabel = NULL;
static GtkWidget *slot[MAX_COMPLETION_SLOTS];
static GtkWidget *slotlabel[MAX_COMPLETION_SLOTS];
static GtkWidget *completion_slot[MAX_COMPLETION_SLOTS];
static char accumulator[MAX_WORD_LENGTH];
static int accumulator_point = 0;
static WComp *completer = NULL;
static GtkWidget *insert_trailing_space = NULL;
static GtkWidget *show_unused_slots = NULL;
static int EAVESDROP = 1;

/* static prototypes */
static gint insert_remainder         (GtkWidget *emitter, GdkEvent *event, 
                                      gpointer data);
static void empty_accumulator        (void);
static void update_slots             (GtkWidget *emitter, gpointer data);
static void find_cache_file          (GtkWidget *emitter, gpointer data);
static void load_cache_file          (GtkWidget *emitter, 
                                      GtkFileSelection *fs);
static void cacheify_wrapper         (GtkWidget *emitter, gpointer data);
static void cache_browser            (GtkWidget *emitter, gpointer data);
static void cache_browser_insert_file(GtkWidget *emitter, gpointer data);

/* Yeah, I'm lazy.  This WORD_FILE_ALTERNATIVES is the number of elements
 * in word_file_alternatives
 */
#define WORD_FILE_ALTERNATIVES                            3
char *word_file_alternatives[] = { "/usr/dict/words",
                                   "/usr/share/dict/words",
                                   "/usr/share/dict/american-english" };

void destroy_completer_window(GtkWidget *emitter, gpointer data)
{
     register int x = 0;

     if(completer_window && GTK_IS_WIDGET(completer_window))
          gtk_widget_destroy(completer_window);

     for(x=0; x<MAX_COMPLETION_SLOTS; x++)
     {
          slot[x]            = NO_WIDGET;
          slotlabel[x]       = NO_WIDGET;
          completion_slot[x] = NO_WIDGET;
     } /* End for */

     inputlabel              = NO_WIDGET;
     insert_trailing_space   = NO_WIDGET;
     show_unused_slots       = NO_WIDGET;

     if(completer)
     {
          wcomp_free(completer);
          completer = NULL;
     } /* End if */

     completer_window = NO_WIDGET;
} /* End destroy_completer_window() */

static void cache_browser_insert_file(GtkWidget *emitter, gpointer data)
{
     GtkWidget *toplevel = emitter;
     GtkWidget *entry    = (GtkWidget *)data;

     while(toplevel && !GTK_IS_FILE_SELECTION(toplevel))
          toplevel = toplevel->parent;

     if(toplevel)
     {
          gtk_entry_set_text(GTK_ENTRY(entry), 
                             gtk_file_selection_get_filename(
                                  GTK_FILE_SELECTION(GTK_FILE_SELECTION(
                                       toplevel))));
     } /* End if */
     else
     {
          gtkeyboard_error(1,"Couldn't find TOPLEVEL fileselection widget!\n");
          return;
     } /* End else */

     while(toplevel && !GTK_IS_WINDOW(toplevel))
          toplevel = toplevel->parent;

     if(toplevel)
          gtk_widget_destroy(toplevel);
} /* End cache_browser_insert_file() */

static void cache_browser(GtkWidget *emitter, gpointer data)
{
     /* Set up file selection dialog */
     GtkWidget *fileselection;

     fileselection = gtk_file_selection_new("Choose a File:");
     
     /* Hook up the action of clicking the OK button */
     gtk_signal_connect(GTK_OBJECT(
          GTK_FILE_SELECTION(fileselection)->ok_button),
                        "clicked", GTK_SIGNAL_FUNC(cache_browser_insert_file),
                        (GtkWidget *)data);
     gtk_signal_connect(GTK_OBJECT(
          GTK_FILE_SELECTION(fileselection)->cancel_button),
                        "clicked", GTK_SIGNAL_FUNC(smack),
                        fileselection);

     /* Set up the filename selection dialog */
     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fileselection), 
				     options.extrafiles);

     gtkeyboard_window_common_setup(fileselection);
     gtk_widget_show_all(fileselection);
} /* End cache_browser() */

static void empty_accumulator(void)
{
     int x = 0;
     for(x=0; x<MAX_WORD_LENGTH; x++)
          accumulator[x] = '\0';
     accumulator_point = 0;
} /* End empty_accumulator() */

typedef struct {
     GtkWidget *from;
     GtkWidget *to;
     GtkWidget *cache_as_lc;
} DoubleDialog;

/* It is important to make sure that the title of the resulting
 * window here has the word GTKeyboard in it.  If it doesn't, and
 * the redirect mode is explicit, then redirections will go to this
 * window (which doesn't do anybody any good) because it will be
 * the redirect window.
 * 
 * As with everywhere else, you can avoid ever being the redirect window 
 * by having GTKeyboard in your window title.
 */
void cacheify_file_dialog(GtkWidget *emitter, gpointer data)
{
     GtkWidget *popup       = gtk_dialog_new();
     GtkWidget *from        = gtk_entry_new_with_max_length(300);
     GtkWidget *to          = gtk_entry_new_with_max_length(300);
     GtkWidget *OK          = NULL;
     GtkWidget *CANCEL      = NULL;
     GtkWidget *browse_from = gtk_button_new_with_label("Browse");
     GtkWidget *browse_to   = gtk_button_new_with_label("Browse");
     GtkWidget *from_hbox   = gtk_hbox_new(FALSE, 0);
     GtkWidget *cache_as_lc = gtk_check_button_new_with_label(
          "Cache words in lower case");
     GtkWidget *to_hbox     = gtk_hbox_new(FALSE, 0);
     DoubleDialog *passer   = g_new(DoubleDialog, 1);
     char buffer[1024];

     sprintf(buffer,"%s%s%s",options.home, _DIR, COMPLETER_CACHE_FILENAME);

     gtk_entry_set_text(GTK_ENTRY(to), buffer);

     passer->from        = from;
     passer->to          = to;
     passer->cache_as_lc = cache_as_lc;

     OK     = create_ok_button(GTK_SIGNAL_FUNC(cacheify_wrapper), passer);
     CANCEL = create_cancel_button(popup);

     add_tooltip(browse_from, "Find a file to convert into a cache file.");
     add_tooltip(browse_to, "Specify the name of the new cache file.");
     add_tooltip(from, "Find a file to convert into a cache file.");
     add_tooltip(to, "Specify the new cache file's filename.");
     add_tooltip(OK, "Convert the specified text file into a cache file.");

     gtk_signal_connect(GTK_OBJECT(browse_from), "clicked",
                        GTK_SIGNAL_FUNC(cache_browser), from);
     gtk_signal_connect(GTK_OBJECT(browse_to), "clicked",
                        GTK_SIGNAL_FUNC(cache_browser), to);

     gtk_box_pack_start(GTK_BOX(from_hbox), from, FALSE, FALSE, 0);
     gtk_box_pack_end(GTK_BOX(from_hbox),   browse_from, FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(to_hbox), to, FALSE, FALSE, 0);
     gtk_box_pack_end(GTK_BOX(to_hbox),   browse_to, FALSE, FALSE, 0);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), 
                        gtk_label_new("Convert a text file to a cache file"),
                        FALSE, FALSE, 0);

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

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
                        gtk_label_new("File to convert:"), FALSE, FALSE, 0);

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

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

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
                        gtk_label_new("File to save as:"), FALSE, FALSE, 0);

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

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

     add_tooltip(cache_as_lc, "If this item is checked, all words in the file will be treated as lower case for the purposes of the cache file. (So the word 'Hello' will be converted to 'hello' for the cache)");
     
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cache_as_lc), FALSE);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
                        cache_as_lc, 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);

     gtkeyboard_window_common_setup(popup);
     gtk_widget_show_all(popup);
} /* End cacheify_file_dialog() */

/* Get the name of a valid cache file.  NULL is acceptable to pass to
 * wcomp_open, as it signifies the default wordlist.  Otherwise, try to 
 * find a system-wide file that we installed when the software went in
 */
static char *get_valid_cache_filename(void)
{
     char *default_file = NULL;
     int x = 0;
     char *ptr = (char *)NULL;
     
     for(x=0; x<WORD_FILE_ALTERNATIVES; x++) {
          default_file = word_file_alternatives[x];

#ifdef COMPLETER_DEBUGGING
          gtkeyboard_message(3, "Attempting to open ", default_file, 
                             " for reading as a wordlist...\n");
#endif /* COMPLETER_DEBUGGING */
          
          if(file_exists(default_file)) {
               gtkeyboard_message(3,"Found valid word list: ", default_file,
                                  "\n");
               ptr = g_strdup_(default_file);
               return(ptr);
          } /* End if */
     } /* End for */
     
     return(NULL);
} /* End get_valid_cache_filename() */

/* From is the filename to convert, to is the filename to save the new cache
 * as, and if all_lowercase is true, then words like 'Hello' will be converted
 * to 'hello' for the cache (all lowercase)
 */
int cacheify_file(char *from, char *to, int all_lowercase)
{
     FILE *fp;
     char word[100];
     char letter;
     register int x=0;
     WComp *comp = NULL;     
     char buffer[1024];
     char *cachefilename = NULL;

#define RESET_WORD               memset(&word[0], '\0', 100);

     if(!from)
     {
          gtkeyboard_error(1, "Cannot make cache from non-existant file.\n");
          return(0);
     } 
     else if(!file_exists(from) || !is_regular_file(from))
     {
          gtkeyboard_error(5, "Cannot make cache from file ", from, ": ",
                           "That file does not exist or ",
                           "isn't a regular file.\n");
          return(0);
     } /* End else if */

     fp = fopen(from, "r");

     if(!fp)
     {
          gtkeyboard_error(5, "Cannot open ", from, " for reading: ",
                           g_strerror(errno), "\n");
          return(0);
     } /* End if */

     gtkeyboard_message(1, "Creating new wordcache object...\n");

     /* cachefilename is NULL if DEFAULT_WORDLIST exists */
     cachefilename = get_valid_cache_filename();
     if(cachefilename && strcmp(cachefilename, CACHE_FILENAME_ERROR) == 0)
          return(0);
     
     comp = wcomp_open(cachefilename);
     
     if(cachefilename)
     {
          g_free_(cachefilename);
     } /* End if */

     x = 0;
     RESET_WORD;

     gtkeyboard_message(1, "Reading file...\n");

     while(!feof(fp))
     {
          letter = '\0';
          fread(&letter, sizeof(char), 1, fp);
          
          /* Don't allow more than 100 alphas in a given word.
           * If a word is more than 100 bytes long, ignore everything
           * after the 100th byte until the termination
           */
          if(isalpha(letter) && x < 100)
               word[x++] = letter;
          else 
          {
               if(word[0] && wcomp_lookup(comp, &word[0]))
               {
                    if(all_lowercase)
                    {
                         for(x=0; x<strlen(word); x++)
                              word[x] = tolower(word[x]);
                    } /* End if */
                    wcomp_use(comp, &word[0]);
               } /* End if */

               /* Move on ... */
               RESET_WORD;

               /* Reset word index */
               x = 0;
          } /* End else */
     } /* End while */
     
     fclose(fp);

     if(!to)
     {
          sprintf(buffer,"%s%s%s",options.home, _DIR, 
                  COMPLETER_CACHE_FILENAME);
          to = &buffer[0];
     } /* End if */

     fp = fopen(to, "w");
     
     gtkeyboard_message(1, "Writing cache file...\n");

     if(!fp)
     {
          wcomp_free(comp);
          gtkeyboard_error(5, "Can't open ", to, " for writing: ",
                           g_strerror(errno), "\n");
          return(0);
     } /* End if */

     wcomp_write_cache(comp, fp);

     fflush(fp);
     fclose(fp);

     gtkeyboard_message(5, "Cache successfully written from file ",
                        from," to file ", to, "\n");
     
     wcomp_free(comp);
     return(1);
} /* End cacheify_file() */

static void cacheify_wrapper(GtkWidget *emitter, gpointer data)
{
     DoubleDialog *passer = (DoubleDialog *)data;
     char *to   = NULL;
     char *from = NULL;
     gint cache_lowercase = 0;
     GtkWidget *parent;

     if(!passer || !passer->from || !passer->to || !passer->cache_as_lc)
     {
          fprintf(stderr,"Error: cacheify_wrapper: Some data not present.\n");
          fflush(stderr);
          return;
     } /* End if */

     parent = passer->from;

     while(parent->parent && !GTK_IS_WINDOW(parent))
          parent = parent->parent;
     
     cache_lowercase = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
          passer->cache_as_lc));

     from = gtk_entry_get_text(GTK_ENTRY(passer->from));
     to   = gtk_entry_get_text(GTK_ENTRY(passer->to));

     if(!cacheify_file(from, to, cache_lowercase))
          fprintf(Q,"Cachefying failed.\n");
     else fprintf(Q,"Cachefying was a success!\n");
     fflush(Q);

     gtk_widget_destroy(parent);
     g_free_(passer);
} /* End cacheify_wrapper() */

/* This should be called each time the user presses a key in order to
 * add this to the possible completion queue.  
 */
void completer_eavesdrop_on_letter(KeySym sym)
{
     char letter = (char)sym;
     char **returns;
     char *current_string;
     int x = 0;
     char buffer[100];

     if(!completer_window || !GTK_IS_WIDGET(completer_window))
     {
          /* The user isn't seeing completions, so why bother doing them? */
          return;
     } /* End if */

     /* If we're not supposed to eavesdrop, then don't. */
     if(!EAVESDROP)
     {
#ifdef DEBUGGING
          fprintf(Q,"Skipping KeySym on do not eavesdrop.\n");
          fflush(Q);
#endif /* DEBUGGING */
          return;
     } /* End if */

     if(!isalnum(letter) &&
        !ISBLANK(letter) && 
        letter != '\b')
     {
          /* We're not interested in this one.  
           * possibly a control character, whatever.
           */
#ifdef COMPLETER_DEBUGGING
          fprintf(Q,"Uninteresting character: %d.\n",(int)letter);
          fflush(Q);
#endif /* COMPLETER_DEBUGGING */
          return;
     } /* End if */

     if(isspace(letter))
     {
#ifdef COMPLETER_DEBUGGING
          fprintf(Q,"Emptying accumulator on space.\n");
          fflush(Q);
#endif /* COMPLETER_DEBUGGING */
          empty_accumulator();
          return;
     } /* End if */
     else  if(letter == '\b')
     {
          if(accumulator_point > 0 && 
             accumulator_point < MAX_WORD_LENGTH)
          {
               accumulator[--accumulator_point] = '\0';
          } /* End if */
          else empty_accumulator();
     } /* End else if */
     else if(letter == '\n' ||
             letter == '\r')
     {
          /* New line - they're not typing the same word */
          empty_accumulator();
     }
     else if(accumulator_point < MAX_WORD_LENGTH)
     {
          /* Maybe add a future option here to insert letters into this 
           * buffer as lowercase only so the cache stays a bit more
           * consistent.
           */
          accumulator[accumulator_point++] = letter;
     } /* End if */
     
     for(x=0; x<accumulator_point; x++)
     {
          if(accumulator[x] == '\0')
          {
               fprintf(Q,"Error:  Accumulator[%d] NULL.\n",x);
               fflush(Q);
               return;
          } /* End if */
     } /* End for */

#ifdef COMPLETER_DEBUGGING
     fprintf(Q,"Completing string that starts with \"%s\"...\n",
             accumulator);
     fflush(Q);
#endif
     returns = wcomp_complete(completer, &accumulator[0]);

     x = 0;

     /* Insert each of the returned strings into new labels */
     while((current_string = *returns++) && x < MAX_COMPLETION_SLOTS)
     {
#ifdef COMPLETER_DEBUGGING
          fprintf(Q,"Updating slot %d...(%s)\n",x, current_string);
          fflush(Q);
#endif /* COMPLETER_DEBUGGING */

          /* If we have text in the accumulator, skip any completed strings
           * with a length less than or equal to the length of the accumulator
           * because there's nothing to complete in those cases.
           * Otherwise just toss out bad strings with 0 length
           */
          if((accumulator[0] && strlen(current_string) <= strlen(accumulator))
             || (strlen(current_string) < 1))
               continue;
          
#ifdef COMPLETER_DEBUGGING
          fprintf(Q,"Filling slot %d with \"%s\"\n",
                  x, current_string); fflush(Q);
#endif /* COMPLETER_DEBUGGING */
          gtk_label_set_text(GTK_LABEL(slotlabel[x]), current_string);

          /* Make sure this entire slot is shown */
          gtk_widget_show_all(completion_slot[x]);
          x++;
     } /* End while */

     while(x < MAX_COMPLETION_SLOTS)
     {
          /* Fill out the remaining slots with nothing */
          gtk_label_set_text(GTK_LABEL(slotlabel[x]), " ");
          if(!gtk_toggle_button_get_active(
               GTK_TOGGLE_BUTTON(show_unused_slots)))
          {
               gtk_widget_hide_all(completion_slot[x]);
          } /* End if */
          else gtk_widget_show_all(completion_slot[x]);
          x++;
     } /* End while */

     /* Reset the label at the top of the completion window */
     sprintf(buffer,"Current Text: %s",
             ((accumulator[0] == '\0') ? 
              "None" : 
              accumulator));

     gtk_label_set_text(GTK_LABEL(inputlabel), buffer);
     gtk_widget_show(inputlabel);
} /* End completer_eavesdrop_on_letter() */

static gint insert_remainder(GtkWidget *emitter, GdkEvent *event, 
                             gpointer data)
{
     int index = GPOINTER_TO_INT(data);
     int len = strlen(accumulator);
     char *text = NULL;
     KEY *key = NULL;
     int maxlen = 0;
     int all_caps = 0;
     gtk_label_get(GTK_LABEL(slotlabel[index]), &text);

     if(event->type != GDK_BUTTON_PRESS)
          return(FALSE);
     if(event->button.button == MIDDLE_MOUSE_BUTTON)
          return(FALSE);
     else if(event->button.button == LEFT_MOUSE_BUTTON)
          all_caps = 0;
     else all_caps = 1;

     maxlen = strlen(text);

     if(text && strlen(text) <= len)
     {
          fprintf(Q,"Unable to complete:  Bad len.\n");
          fflush(Q);
          return(TRUE);
     } /* End if */
     
#ifdef COMPLETION_DEBUGGING
     fprintf(Q,"Got index %d:  Completion would have been \"%s\"\n",
             index, &text[len]);
     fflush(Q);
#endif /* COMPLETION_DEBUGGING */

     /* While we're inserting, don't update the display */
     EAVESDROP = 0;

     /* Insert the remainder of what's left to complete key by key */
     while(len < maxlen)
     {
          /* If the user clicked the right mouse button,
           * insert things as all upper case 
           */
          if(all_caps)
               text[len] = toupper(text[len]);

          key = gtkeyboard_new_key((KeySym)text[len],
                                   (KeySym)text[len],
                                   (KeySym)text[len],
                                   NULL);
          keysym_callback(NO_WIDGET, (gpointer)key);
          key = gtkeyboard_destroy_key(key);
          len++;
     } /* End while */

     if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(insert_trailing_space)))
     {
          /* Insert a final space */
          key = gtkeyboard_new_key(XK_space,
                                   XK_space,
                                   XK_space,
                                   " ");
          keysym_callback(NO_WIDGET, (gpointer)key);
          key = gtkeyboard_destroy_key(key);
     } /* End if */
     
     /* We just finished a word -- we shouldn't have anything on the 'stack' */
     empty_accumulator();

     EAVESDROP = 1;

     return(TRUE);
} /* End insert_remainder() */

static void load_cache_file(GtkWidget *emitter, GtkFileSelection *fs)
{
     char *filename = NULL;
     FILE *fp       = NULL;

     if(fs && GTK_IS_FILE_SELECTION(fs))
     {
          filename = g_strdup_(gtk_file_selection_get_filename(
               GTK_FILE_SELECTION(fs)));
     } /* End if */

     gtk_widget_destroy((GtkWidget *)fs);

     if(filename)
     {
          if(is_regular_file(filename))
          {
               fp = fopen(filename, "r");
               
               if(!fp)
               {
                    gtkeyboard_error(4, "Error:  Cannot open ",filename,
                                     " for reading: ", g_strerror(errno));
               } /* End if */
               else
               {
                    wcomp_read_cache(completer, fp);
                    fclose(fp);
                    gtkeyboard_message(3, "Successfully read cache file at ",
                                       filename, "\n");
               } /* End else */
          } /* End if */
          else 
          {
               gtkeyboard_error(3, "Error loading cache file: ",filename, 
                                " is not a regular file.  Load aborted.\n");
          } /* End else */

          g_free_(filename);
     } /* End if */
     else 
     {
          gtkeyboard_error(1,"Error:  Cannot load nonexistant cache file!\n");
     } /* End else */

     return;
} /* End load_cache_file() */

static void find_cache_file(GtkWidget *emitter, gpointer data)
{
     GtkWidget *fs = gtk_file_selection_new("GTKeyboard: Choose Cache File:");
          
     char buffer[1024];

     sprintf(buffer,"%s%s%s",options.home, _DIR, COMPLETER_CACHE_FILENAME);

     /* Hook up the action of clicking the OK button */
     gtk_signal_connect(GTK_OBJECT(
	  GTK_FILE_SELECTION(fs)->ok_button),
			"clicked", GTK_SIGNAL_FUNC(load_cache_file),
			fs);
     gtk_signal_connect(GTK_OBJECT(
	  GTK_FILE_SELECTION(fs)->cancel_button),
			"clicked", GTK_SIGNAL_FUNC(smack),
			fs);

     /* Set up the filename selection dialog */
     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), 
                                     buffer);

     gtkeyboard_window_common_setup(fs);
     gtk_widget_show_all(fs);
} /* End find_cache_file() */

/* It is important to make sure that the title of the resulting
 * window here has the word GTKeyboard in it.  If it doesn't, and
 * the redirect mode is explicit, then redirections will go to this
 * window (which doesn't do anybody any good) because it will be
 * the redirect window.
 * 
 * As with everywhere else, you can avoid ever being the redirect window 
 * by having GTKeyboard in your window title.
 */
/* Construct and show a new completer window */
void completer_window_new(void)
{
     int x = 0;
     GtkWidget *vbox, *close;
     GtkWidget *slotrank[MAX_COMPLETION_SLOTS];
     GtkWidget *pixmap;
     GtkWidget *load_cache;
     GtkWidget *cacheify = NULL;
     FILE *cache = NULL;
     char cache_filename[1024];
     char *default_file = NULL;
     char buf[10];

     if(completer_window || (slot[0] && GTK_IS_WIDGET(slot[0])) )
     {
          /* If we don't do this, we'll run into some exceedingly nasty
           * problems with one window's data walking all over the data
           * of another window.  That sucks, so let's refuse to do it.
           * If anybody can think of why a user could possibly need more
           * than one completer window, let me know and I'll think about
           * changing this policy
           */
          gtkeyboard_error(1, "One completer window is already running.");
          return;
     } /* End if */
     
     gtkeyboard_message(1, "Opening wordlist...\n");

     /* Do some error checking to make sure we can proceed before creating
      * skads of widgets.  If DEFAULT_WORDLIST doesn't exist, and we also
      * can't find options.extrafiles/DEFAULT_CACHE_FILENAME, then 
      * refuse to pop up the dialog.  (It would cause a segfault)
      */

     completer_window = gtk_dialog_new();
     vbox = gtk_vbox_new(FALSE, 0);
     gtk_widget_realize(completer_window);

     for(x=0; x<MAX_COMPLETION_SLOTS; x++)
     {
          slot[x]      = gtk_button_new();
          pixmap       = xpm_to_widget(completer_window, 
                                       (gchar **)insert_completion_xpm);
          
          add_tooltip(slot[x], "Left click here to use this shortcut.  Clicking the right mouse button will use this shortcut, but in all capital letters.");

          if(!pixmap)
               gtk_container_add(GTK_CONTAINER(slot[x]),
                                 gtk_label_new("Insert"));
          else gtk_container_add(GTK_CONTAINER(slot[x]),
                                 pixmap);
          
          /* The current term in this slot.  (Nothing initially) */
          slotlabel[x]            = gtk_label_new(" ");
          completion_slot[x]      = gtk_hbox_new(FALSE, 0);
          
          sprintf(buf,"#%d:  ",(x+1));
          slotrank[x]  = gtk_label_new(buf);

          gtk_signal_connect(GTK_OBJECT(slot[x]), "button_press_event",
                             GTK_SIGNAL_FUNC(insert_remainder),
                             (gpointer)x);

          gtk_box_pack_start(GTK_BOX(completion_slot[x]), 
                             slotrank[x], FALSE, FALSE, 0);
          gtk_box_pack_start(GTK_BOX(completion_slot[x]), 
                             slotlabel[x], FALSE, FALSE, 0);
          gtk_box_pack_end(GTK_BOX(completion_slot[x]),   
                           slot[x], FALSE, FALSE, 0);
          gtk_box_pack_start(GTK_BOX(vbox),    
                             completion_slot[x], FALSE, FALSE, 0);
     } /* End for */

     /* Nuke the contents of the accumulator */
     memset(accumulator, '\0', MAX_WORD_LENGTH);

     inputlabel = gtk_label_new("Current Text:  None");
     close = gtk_button_new_with_label("Close");

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                        inputlabel, FALSE, FALSE, 0);

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

     insert_trailing_space = 
          gtk_check_button_new_with_label("Insert space after completion");
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(insert_trailing_space),
                                  TRUE);

     show_unused_slots = 
          gtk_check_button_new_with_label("Show empty completion slots");
     gtk_signal_connect(GTK_OBJECT(show_unused_slots), "toggled",
                        GTK_SIGNAL_FUNC(update_slots), show_unused_slots);
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_unused_slots),
                                  TRUE);

     load_cache = gtk_button_new_with_label("Load Cache File");

     gtk_signal_connect(GTK_OBJECT(load_cache), "clicked",
                        GTK_SIGNAL_FUNC(find_cache_file), completer_window);
     
     cacheify = gtk_button_new_with_label("Convert text file to word cache");
     
     gtk_signal_connect(GTK_OBJECT(cacheify), "clicked",
                        GTK_SIGNAL_FUNC(cacheify_file_dialog), NULL);

     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                      cacheify, FALSE, FALSE, 0);
     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                      load_cache, FALSE, FALSE, 0);

     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                      insert_trailing_space, FALSE, FALSE, 0);

     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                      show_unused_slots, FALSE, FALSE, 0);

     gtk_box_pack_end(GTK_BOX(GTK_DIALOG(completer_window)->vbox),
                      gtk_label_new("Options:"), FALSE, FALSE, 0);
                      
     /* Cause the close button to destroy the completer window */
     gtk_signal_connect(GTK_OBJECT(close), "clicked",
                        GTK_SIGNAL_FUNC(destroy_completer_window),
                        completer_window);
     /* Pack the close button */
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(completer_window)->action_area),
                        close, FALSE, FALSE, 0);

     /* Actually open the new file thingy.  default_file will be NULL if
      * the default filename exists 
      */
     
     default_file = get_valid_cache_filename();
     if(default_file && strcmp(default_file, CACHE_FILENAME_ERROR) == 0)
     {
          gtk_widget_destroy(completer_window);
          gtkeyboard_error(1,"There was an error finding a cache filename");
          return;
     } /* End if */
     
     fprintf(Q,"Opening default cache file: \"%s\"\n",
             default_file ? default_file : "NULL");
     fflush(Q);
     completer = wcomp_open(default_file);
     /* Whoops...the completer wasn't able to be created for one reason or
      * another
      */
     if(!completer) {
          gtkeyboard_error(6, "There was an error opening a valid cache file ",
                           "for use with the completion mechanism.  Please ",
                           "make sure that the system dictionary is in place ",
                           "in either /usr/dict/words, /usr/share/dict/words,",
                           " or specify a default cache file through the ",
                           "GTKeyboard configuration file.");
          gtk_widget_destroy(completer_window);
          return;
     } /* End if */

     if(default_file)
     {
          g_free_(default_file);
     } /* End if */
     
     wcomp_set_topnsize(completer, 10);

     /* ~/.gtkeyboard-wordcache */
     if(options.cache_file)
          strcpy(cache_filename, options.cache_file);
     else
          sprintf(cache_filename, "%s%s%s",
                  options.home, _DIR, COMPLETER_CACHE_FILENAME);
     
     /* Try to read a cache file */
     if(options.cache_file)
          cache = fopen(options.cache_file, "r");
     else
          cache = fopen(cache_filename, "r");

     if(!cache)
     {
          gtkeyboard_message(5,"Warning:  Could not open ",
                             cache_filename, ": ", g_strerror(errno), "\n");
     } /* End if */
     else
     {
          wcomp_read_cache(completer, cache);
          gtkeyboard_message(3, "Read wordlist cache from ",
                             cache_filename,"\n");
          fclose(cache);
     } /* End else */

     gtk_signal_connect(GTK_OBJECT(completer_window),
                        "delete_event", 
                        GTK_SIGNAL_FUNC(destroy_completer_window),
                        completer_window);

     gtkeyboard_window_common_setup(completer_window);
     gtk_widget_show_all(completer_window);
} /* End completer_window_new() */

static void update_slots(GtkWidget *emitter, gpointer data)
{
     GtkWidget *wid = (GtkWidget *)data;
     int active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
     int x = 0;
     char *text = NULL;

     if(active)
     {
          /* Show 'em all.  Who cares if they're empty or not. */
          for(x=0; x<MAX_COMPLETION_SLOTS; x++)
               gtk_widget_show_all(completion_slot[x]);
     } /* End if */
     else
     {
          for(x=0; x<MAX_COMPLETION_SLOTS; x++)
          {
               gtk_label_get(GTK_LABEL(slotlabel[x]), &text);

               /* Undefined text or a text of " " means that it's empty
                * and shouldn't be shown
                */
               if(!text || strcmp(text," ") == 0)
                    gtk_widget_hide_all(completion_slot[x]);
               else gtk_widget_show_all(completion_slot[x]);
          } /* End for */
     } /* End else */
} /* End update_slots */
