/* callbacks.c
 * This file is for use with GTKeyboard written by David Allen
 * s2mdalle@titan.vcu.edu
 * http://opop.nols.com/
 *
 * Contains a bunch of functions for dealing with memory management, 
 * widget callbacks, and a bunch of the really nasty guts of the program.
 */
/* 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 CALLBACKS_C 1
#define APP_C 1   /* So this file can get at app.c's data in master.h */
#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>

#define MODE_WRITE

/* Do not allow both MODE_WRITE and MODE_APPEND to be defined.  If both are
 * defined, default to MODE_WRITE.  The End Of Earth As We Know It (TM) occurs
 * (or maybe just a compile error) MODE_WRITE is spiffier.
 */
#ifdef MODE_APPEND
#  ifdef MODE_WRITE
#    undef MODE_APPEND
#  endif /* MODE_WRITE */
#endif /* MODE_APPEND */

#ifndef __USE_GNU
#  define __USE_GNU      
#endif /* __USE_GNU */

#define MESSAGE_WIDTH  200
#define MESSAGE_HEIGHT 100

/* Copy, paste, cut functions */
void CutText(GtkWidget *w, gpointer data)
{
     gtk_editable_cut_clipboard(GTK_EDITABLE(GUI.main_output_text));
     chocolate("Text cut to the clipboard.\n");
     return;
} /* End CutText() */

void CopyText(GtkWidget *w, gpointer data)
{
     gtk_editable_copy_clipboard(GTK_EDITABLE(GUI.main_output_text));
     chocolate("Text copied to the clipboard.\n");
     return;
} /* End CopyText() */

void PasteText(GtkWidget *w, gpointer data)
{
     gtk_editable_paste_clipboard(GTK_EDITABLE(GUI.main_output_text));
     chocolate("Text pasted from the clipboard.\n");
     return;
} /* End PasteText() */

long sizeof_text_widget(GtkWidget *widget)
{
     char *buffer = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
     long rep = (long)strlen(buffer) * sizeof(char);
     g_free_(buffer);
     return(rep);
} /* End sizeof_text_widget */

void toggle_tooltips(void)
{
     if(options.SHOW_TOOLTIPS) 
     {
          disable_tooltips();
          options.SHOW_TOOLTIPS = 0;
     }
     else 
     {
          enable_tooltips();
          options.SHOW_TOOLTIPS = 1;
     } /* End else */
} /* End toggle_tooltips() */

void disable_tooltips(void)
{
     if(GUI.tooltips)
	  gtk_tooltips_disable(GUI.tooltips);
     else gtkeyboard_message(1,"No tooltips defined.  This may be a bug.\n");

     /* Toolbar tooltips are separate from the rest of the apps tooltips.
      * Damn.
      */
     if(GUI.toolbar_gtktb)
          gtk_toolbar_set_tooltips(GTK_TOOLBAR(GUI.toolbar_gtktb), FALSE);
} /* End disable_tooltips() */

void enable_tooltips(void)
{
     if(GUI.tooltips)
	  gtk_tooltips_enable(GUI.tooltips);
     else gtkeyboard_message(1,"No tooltips defined.  This may be a bug.\n");
     
     /* Toolbar tooltips are separate from the rest of the apps tooltips.
      * Damn.
      */
     if(GUI.toolbar_gtktb)
          gtk_toolbar_set_tooltips(GTK_TOOLBAR(GUI.toolbar_gtktb), TRUE);
} /* End enable_tooltips() */

/* Status report just prints out what options.CAPS_LOCK and options.SHIFT are 
 * to the status widget among other things.
 */
void status_report(GtkWidget *widget, gpointer data)
{
     char temp[2048];
     char on[]="ON ", 
	  off[]="OFF ";

     sprintf(temp,"----====< STATUS >====----");
     if(options.ASK_SAVE_ON_EXIT)
	  strcat(temp,"\nYou will be asked if you want to save on exit.");
     else
	  strcat(temp,"\nYou will not be asked if you want to save on exit.");

     strcat(temp,"\nSTATUS LOGGING is ");
     if(options.STATUS_LOGGING==1)
	  strcat(temp,on);
     else
	  strcat(temp,off);
     
     strcat(temp,"\nWORD WRAP is ");
     if(options.WORD_WRAP)
	  strcat(temp, on);
     else
	  strcat(temp, off);

     strcat(temp,"\nNUM LOCK is ");
     if(options.NUMLOCK)
	  strcat(temp, on);
     else
	  strcat(temp, off);

     strcat(temp,"\nOur workingfile is ");
     if(!options.workingfile)
	  strcat(temp,"(None)");
     else
	  strcat(temp,options.workingfile);

     strcat(temp,"\n");
     strcat(temp,"Our text space has ");
     sprintf(temp,"%s %ld ",temp,
             sizeof_text_widget(GUI.main_output_text));
     strcat(temp,"characters in it\n");

     /* Report the message to the screen regardless */
     gtkeyboard_message(1,temp);

     return;
} /* End status report */

void smack(GtkWidget *w, gpointer data)
{
     GtkWidget *prisoner = (GtkWidget *)data;
     /* Get it out of our faces */
     gtk_widget_destroy(w);
     gtk_widget_destroy(prisoner);

     prisoner = (GtkWidget *)NULL;
     w        = (GtkWidget *)NULL;
     return;
} /* End smack */

/* Pop up a save file as dialog */
void save_file_as(GtkWidget *emitter, gpointer data)
{
     /* Set up file selection dialog */
     GtkWidget *fileselection;
     fileselection = gtk_file_selection_new("Save File As...");
     
     /* Hook up the action of clicking the OK button */
     gtk_signal_connect(GTK_OBJECT(
	  GTK_FILE_SELECTION(fileselection)->ok_button),
			"clicked", 
                        GTK_SIGNAL_FUNC(gtkeyboard_grab_filename_save),
			fileselection);
     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), "");

     gtkeyboard_window_common_setup(fileselection);
     gtk_window_set_position(GTK_WINDOW(fileselection), GTK_WIN_POS_CENTER);
     gtk_widget_show_all(fileselection);

     MEM_REPORT;
} /* End save_file_as */

/* This is an "are you sure?" dialog for remapping the keyboard since
 * people might not want to actually do it. The ok button maps onto
 * gtkeyboard_remap_keyboard()
 */
void remap_keyboard_really(GtkWidget *emitter, gpointer data)
{
     GtkWidget *popup = gtk_dialog_new();
     GtkWidget *ok;
     GtkWidget *cancel;
     GtkWidget *label;
     GtkWidget *label2;
     char txt[1024 * 5];
     char warn[512];

     ok = create_ok_button(GTK_SIGNAL_FUNC(gtkeyboard_remap_keyboard), NULL);

     sprintf(txt,"Would you like to remap the keyboard to the\n");
     
     if(options.keyboard->name)
     {
	  strcat(txt,"keyboard definition in the file:\n");
	  strcat(txt,options.keyboard->name);
	  strcat(txt,"?\n");
     } /* End if */
     else
	  strcat(txt,"currently shown layout?\n");

     sprintf(warn,"THIS WILL CHANGE YOUR PHYSICAL KEYBOARD'S MAPPING.\n%s",
             "(It may also take a few seconds)");

     /* Cancel button is a regular gtk_widget_destroy(popup) */
     cancel = create_cancel_button(popup);

     label = gtk_label_new(txt);
     label2 = gtk_label_new(warn);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label,
			FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label2,
			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);

     gtk_widget_show_all(popup);
} /* End remap_keyboard_really() */

/* Grab the changed filename from the save_as dialog and save the file
 * that the user specified.
 */
void gtkeyboard_grab_filename_save(GtkWidget *emitter, gpointer data)
{
     GtkFileSelection *fs = (GtkFileSelection *)data;
     char *new_filename = (char *)NULL;

     if(!fs || !GTK_IS_FILE_SELECTION(fs))
     {
	  gtkeyboard_error(3,"Internal GTKeyboard error.  Please report ",
			   "this error along with what you were doing at ",
			   "the time.\n");
	  return;
     } /* End if */

     new_filename = gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs));

     if(options.workingfile)
     {
	  g_free_(options.workingfile);
	  options.workingfile = NULL;
     } /* End if */

     options.workingfile = g_strdup_(new_filename);

     gtk_widget_destroy(GTK_WIDGET(fs));

     save_output_text((GtkWidget *)NULL, (gpointer)NULL);

     gtkeyboard_message(1,"Changed filename and saved.\n");
     return;
} /* End gtkeyboard_grab_filename_save */

void save_output_text(GtkWidget *w, gpointer data)
{
     char *buffer = (char *)NULL;
     FILE *fp;
     int nchars;
     GtkWidget *foobar = (GtkWidget *)data;

     if(!options.workingfile || options.workingfile == (char *)NULL)
     {
	  gtkeyboard_error(1,"Please specify a filename.\n");
	  save_file_as(NO_WIDGET, (gpointer)NULL);
	  return;
     } /* End if */

     if(!foobar)
	  foobar = GUI.main_output_text;

     /* Mode append or write can be specified with symbolics at the top
      * of this file.  I highly recommend write, as most people will think
      * it's pretty weird to do appending.  Still, somebody asked for it..
      */

#ifdef MODE_APPEND
     if((fp=fopen(options.workingfile, "a"))!=NULL)
     {
#endif /* MODE_APPEND */
#ifdef MODE_WRITE
     if((fp=fopen(options.workingfile, "w"))!=NULL)
     {
#endif /* MODE_WRITE */

	  /* Freeze so user can't edit while we're saving */
	  gtk_text_freeze(GTK_TEXT(foobar));
	  
	  buffer = gtk_editable_get_chars (GTK_EDITABLE (foobar),
					   (gint) 0,
					   (gint) gtk_text_get_length(
						GTK_TEXT (foobar)));
	  
	  nchars = fwrite(buffer, sizeof(char), strlen(buffer), fp);

	  if(nchars != strlen(buffer))
	       save_output_text_error("Too few bytes written!\n");
	  
	  g_free_(buffer);
	  buffer = NULL;
	  
	  /* Unfreeze so editing can keep going */
	  gtk_text_thaw(GTK_TEXT(foobar));
	  fflush(fp);
	  fclose(fp);
	  save_output_text_successful(options.workingfile);
	  
	  /* No longer modifed, we're up to date. */
	  options.document_modified = OFF;
     } /* End if */
     else
     {
	  /* We didn't save, so consider the document recently modified */
	  options.document_modified = ON;

	  /* Put up a popup with the errno error in it. */
	  save_output_text_error(g_strerror(errno));
     } /* End else */
     
     CONDFREE(buffer);

     return;
} /* End save_output_text */

void save_output_text_error(char *error)
{
     char buffer[2048];

     sprintf(buffer,"Error saving %s:  %s",options.workingfile,error);
     gtkeyboard_error(2, buffer,"\n\n");
     return;
} /* End save_output_text_error */

/* Save_output_text_successful - happens when we saved a file */
void save_output_text_successful(char *filename)
{
     char buffer[2048];   /* Kudos to you if you have a filename to overflow
			   * this.  :)
			   */
     sprintf(buffer,"File save successful:  %s",filename);

     gtkeyboard_message(2,buffer,"\n\n");
     return;
} /* End save_output_text_success */

/* Showmenu */
void Showmenu(GtkWidget *w, gint input)
{
     char message[100];
     
     switch(input)
     {
     case FILEM:
	  strcpy(message,"FILE menu torn off.\n");
	  break;
     case EDITM:
	  strcpy(message,"EDIT menu torn off.\n");
	  break;
     case OPTIONSM:
	  strcpy(message,"OPTIONS menu torn off.\n");
	  break;
     case MISCM:
	  strcpy(message,"MISC menu torn off.\n");
	  break;
     case HELPM:
	  strcpy(message,"HELP menu torn off.\n");
	  break;
     case HTMLM:
	  strcpy(message,"HTML menu torn off.\n");
	  break;
     case SHORTCUTSM:
	  strcpy(message,"SHORTCUTS menu torn off.\n");
	  break;
     default:
	  strcpy(message,"MENU torn off.\n");
	  break;
     } /* End switch */

     chocolate(message);
     return;
} /* End showmenu */

void toggle_double_data(void)
{
     char on[] = 
	  "All keys will be sent to the text\narea and the redirect window.\n";
     char off[] = 
	  "Keys will only be sent to the window\nyou are working with.\n";

     if(ISON(options.SEND_TO_BOTH_WINDOWS))
     {
	  chocolate(off);
	  options.SEND_TO_BOTH_WINDOWS = OFF;
     } /* End if */
     else
     {
	  chocolate(on);
	  options.SEND_TO_BOTH_WINDOWS = ON;
     } /* End else */
} /* End toggle_double_data() */

/* Self explanatory */
void toggle_ask_save_on_exit(void)
{
     char on[]  = "You will be asked if you want to save on exit.\n";
     char off[] = "You will not be asked if you want to save on exit.\n";

     if(ISON(options.ASK_SAVE_ON_EXIT))
     {
	  chocolate(off);
	  options.ASK_SAVE_ON_EXIT=OFF;
     } /* End if */
     else
     {
	  chocolate(on);
	  options.ASK_SAVE_ON_EXIT=ON;
     } /* End else */
     
     return;
} /* End toggle_ask_save_on_exit */

/* Turn status logging on or off - only saves at exit */
void status_logging_toggle(void)
{
     char on[]  = "Status logging is ON\n";
     char off[] = "Status logging is OFF\n";

     if(ISON(options.STATUS_LOGGING))
     {
	  options.STATUS_LOGGING=OFF;
	  chocolate(off);
     } /* End if */
     else 
     {
	  options.STATUS_LOGGING=ON;
	  chocolate(on);
     } /* End else */
     return;
} /* End status_logging_toggle */

static int pester_user_with_popup = 0;

gint CloseApp (GtkWidget *widget, gpointer data)
{
     /* Print a carriage return just to pretty things up so to speak */
     char filename[] = "gtkeyboard.status.txt";
     char *datafoo;
     FILE *fp;
     int flag = 0;
     
     if(data && GTK_IS_WIDGET((GtkWidget *)data))
	  gtk_widget_destroy((GtkWidget *)data);

     if(options.STATUS_LOGGING)
     {
	  fp = fopen(filename,"w");
	  if(fp==NULL)
	  {
	       /* Don't write info to status since we're about to die anyway.
		* wasteful.  :)
		*/
	       fprintf(Q,"Couldn't open %s for writing : %s\n",filename,
		       g_strerror(errno));
	       fprintf(Q,"Status log NOT written.\n");
	  } /* End if */
	  else
	  {
	       datafoo = gtk_editable_get_chars(GTK_EDITABLE(GUI.status), 
						(gint) 0, (gint) -1);

	       /* Print to the file */
               if(datafoo)
                    fprintf(fp,"%s",datafoo);

	       /* Flush the buffer */
	       fflush(fp);
	       /* Close it */
	       fclose(fp);
#ifdef DEBUGGING
	       fprintf(stdout,
		       "Status log successfully written to %s\n",filename);
	       fflush(stdout);
#endif /* DEBUGGING */
	       /* Free that mem, yeehaw! :) */
	       g_free_(datafoo);
	  } /* End else */
     } /* End if options.STATUS_LOGGING */
#ifdef DEBUGGING
     else
     {
	  fprintf(stdout,"Status logging wasn't enabled.  Log not written.\n");
	  fflush(stdout);
     } /* End else */
#endif

     /* Do we need to save anything before we leave? */

     /* Watch out for that short circuit evaluation.  options.workingfile
      * COULD be NULL here, in which case strstr would cause us to 
      * segfault.
      */
     if(options.ASK_SAVE_ON_EXIT && options.workingfile &&
	!strstr(options.workingfile,"/dev/"))
     {
	  /* If we need to save, the confirm saving/give an option to save
	   * to a different file if needed.
	   */

	  /* if(check_if_need_to_save(GUI.main_output_text, 
	     options.workingfile)) */
	  if(options.document_modified)
	  {
	       if(!pester_user_with_popup)
	       {
		    confirm_save_on_exit(options.workingfile);
		    pester_user_with_popup = 1;
	       } /* End if */
	       else
	       {
		    byebye((GtkWidget *)NULL, 0);
		    flag = 1;
	       } /* End else */
	  } /* End if */
	  else  
	  {
	       byebye((GtkWidget *)NULL, 0);
	       flag = 1;
	  } /* End else */
     } /* End if */
     else
     {
	  byebye((GtkWidget *)NULL, 0);
	  flag = 1;
     } /* End else */

     if(flag)
     {
	  gtk_main_quit();
	  
	  MEM_REPORT;
	  gtkeyboard_mem_cleanup();
	  MEM_REPORT;

#ifndef PROD
	  g_mem_profile();
#endif /* !PROD */

	  MEM_REPORT;
	  FLUSH_EVERYTHING;
          
#if 0
          while(1)
          {
               fprintf(Q,"Sleeping...\n"); fflush(Q);
               sleep(1);
          } /* End while */
#endif
	  gtk_exit(0);
     } /* End if */


     /* this might not happen depending on flag */
     return(TRUE);
} /* End closeapp */

void gtkeyboard_signal_handler(int signalno)
{
     fprintf(stderr,"Signal %d caught by GTKeyboard...shutting down.\n",
	     signalno);
     fflush(stderr);

     /* Exit the program immediately. */
     byebye((GtkWidget *)NULL, 1);
          
     exit(1);     /* This should never be reached */
} /* End gtkeyboard_signal_handler */

/* Function exits the program and dies */
int byebye(GtkWidget *emitter, gint foo)
{
     /* Frees all memory associated with all widget below */
     if(window && GTK_IS_WIDGET(window))
     {
	  gtk_widget_unrealize(window);	
	  gtk_widget_destroy(window);
     } /* End if */

     if(foo == 1)
     {
	  gtk_main_quit(); 
     } /* End if */
     
     FLUSH_EVERYTHING;
     
     if(foo==1)         /* Were we called without closeapp? */
     {
	  gtkeyboard_mem_cleanup();

#ifndef PROD
	  g_mem_profile();
#endif /* PROD */

	  MEM_REPORT;
	  FLUSH_EVERYTHING;
          
#if 0
          while(1)
          {
               fprintf(Q,"Sleeping...\n"); fflush(Q);
               sleep(1);
          } /* End while */
#endif

	  gtk_exit(0);
     } /* End if */

     /* May or may not happen */
     return(TRUE);
} /* End byebye */

/* Cleans up a bunch of memory that was allocated at different stages.
 * Make sure you don't call this unless you mean to exit really soon since
 * it destroys all relevant data structures
 *
 * If options.VERBOSE is ON, then it will print out what it is cleaning up
 * at the moment.  It also flushes, since earlier I messed up some of the
 * memory earlier, and it would segfault.  Flush before you clear the memory
 * so you'll know what caused the segfault.  Also, set everything to NULL
 * after you g_free() it because almost everything in GTKeyboard checks to 
 * see if the pointer is pointing somewhere valid before it tries to free
 * it and depending on your machine/compiler, it might still point somewhere
 * !=NULL after freed.
 */
void gtkeyboard_mem_cleanup(void)
{
     int x = 0;

#ifndef PROD  /* If we're debugging assure these messages are seen */
     options.VERBOSE = ON;
#else /* defined PROD */
     options.VERBOSE = OFF;
#endif /* PROD */
     
     for(x=0; x<MAX_CUSTOM_COLORS; x++)
     {
          CONDFREE(GUI.custom_colors[x]);
     } /* End for */

     menus_cleanup();

     MEMMSG("GUI.popup_menu");    COND_DESTROY(GUI.popup_menu);

     MEMMSG("Cursor.");
     if(GUI.cursor)
     {
	  gdk_cursor_destroy(GUI.cursor);
	  GUI.cursor = NULL;
     } /* End if */

     /* Message about cleaning up then conditionally free a bunch of 
      * different items, yo
      */
     MEMMSG("home.");                  CONDFREE(options.home);
     MEMMSG("Workingfile.");           CONDFREE(options.workingfile);
     MEMMSG("Tempslot.");              CONDFREE(options.tempslot);
     MEMMSG("extrafiles.");            CONDFREE(options.extrafiles);
     MEMMSG("Redirect_window_name.");  CONDFREE(options.redirect_window_name);
     MEMMSG("keyboard_file.");         CONDFREE(options.keyboard_file);
     MEMMSG("cache file.");            CONDFREE(options.cache_file);

     for(x=0; x<SHORTCUTS; x++)
     {
          CONDFREE(options.shortcuts[x]);
     } /* End for */

     if(options.old_keyboard)
     {
          MEMMSG("Options.old_keyboard");
          options.old_keyboard = gtkeyboard_destroy_keyboard(
               options.old_keyboard);
          options.old_keyboard = NO_KEYBOARD;
     } /* End if */

     if(options.keyboard)
     {
	  MEMMSG("Options.Keyboard.");
	  options.keyboard = gtkeyboard_destroy_keyboard(options.keyboard);
	  options.keyboard = NO_KEYBOARD;
     } /* End if */

     MEMMSG("Fontname.");              CONDFREE(GUI.fontname);
     MEMMSG("KFontname.");             CONDFREE(GUI.kfontname);
     MEMMSG("Kfont.");                 CONDFREE(GUI.kfont);
     MEMMSG("Font.");                  CONDFREE(GUI.font);
     MEMMSG("Colorname");              CONDFREE(GUI.colorname);

#define COND_STYLE_UNREF(st)           if(st){g_free_(st); st=NULL;}

     MEMMSG("Style.");                 /* COND_STYLE_UNREF(GUI.style); */
     MEMMSG("KStyle.");                /* COND_STYLE_UNREF(GUI.kstyle); */
     MEMMSG("Deflt.");                 /* COND_STYLE_UNREF(GUI.deflt); */
     MEMMSG("Textstyle");              /* COND_STYLE_UNREF(GUI.textstyle); */

     MEMMSG("item_factory");
     if(GUI.item_factory && 
        !GTK_OBJECT_DESTROYED(GTK_OBJECT(GUI.item_factory)))
     {
          gtk_object_destroy(GTK_OBJECT(GUI.item_factory));
     } /* End if */

     if(GUI.tooltips && !GTK_OBJECT_DESTROYED(GUI.tooltips))
          gtk_object_destroy(GTK_OBJECT(GUI.tooltips));

#define CONDSMACK(name, widget) MEMMSG(name);\
                                if(widget && GTK_IS_WIDGET(widget))\
                                     gtk_widget_destroy(widget);
  
     CONDSMACK("GUI.KEYBOARD", GUI.KEYBOARD);
     CONDSMACK("GUI.menubar", GUI.menubar);
     CONDSMACK("GUI.keyboard_socket", GUI.keyboard_socket);
     CONDSMACK("GUI.bottom_row_buttons", GUI.bottom_row_buttons);
     CONDSMACK("GUI.mainbox", GUI.mainbox);
     CONDSMACK("GUI.s_table", GUI.s_table);
     CONDSMACK("GUI.status", GUI.status);
     CONDSMACK("GUI.toolbar", GUI.toolbar);
     CONDSMACK("GUI.toolbar_gtktb", GUI.toolbar_gtktb);
     CONDSMACK("GUI.main_output_text", GUI.main_output_text);
     CONDSMACK("GUI.master_text", GUI.master_text);

     CONDSMACK("GUI.keyboard_elements.cursor_keys", 
               GUI.keyboard_elements.cursor_keys);
     CONDSMACK("GUI.keyboard_elements.keyboard", 
               GUI.keyboard_elements.keyboard);
     CONDSMACK("GUI.keyboard_elements.f_keys", GUI.keyboard_elements.f_keys);
     CONDSMACK("GUI.keyboard_elements.number_pad", 
               GUI.keyboard_elements.number_pad);

     MEMMSG("popup_menu");
     if(GUI.popup_menu &&
        GTK_IS_WIDGET(GUI.popup_menu))
     {
          gtk_widget_destroy(GUI.popup_menu);
     } /* End if */

     /* Reset global signal handlers */
#ifdef SIGINT
     signal(SIGINT, SIG_DFL);
#endif /* SIGINT */
#ifdef SIGTERM
     signal(SIGTERM, SIG_DFL);
#endif /* SIGTERM */
#ifdef SIGHUP
     signal(SIGHUP, SIG_DFL);
#endif /* SIGHUP */
#ifdef SIGQUIT
     signal(SIGQUIT, SIG_DFL);
#endif /* SIGQUIT */

     MEMMSG("Done Freeing\n");
     FLUSH_EVERYTHING;

#ifndef PROD
     fprintf(Q,"gnew = %d\ngnew0 = %d\ngstrdup = %d\ngmalloc = %d\ngfree = %d",
	     options.gnew, options.gnew0, options.gstrdup, options.gmalloc,
	     options.gfree);
     fprintf(Q,"\n");
     fflush(Q);
#endif /* !PROD */
} /* End gtkeyboard_mem_cleanup */

void alt_gr_toggle(GtkWidget *w, gpointer data)
{
     if(ISOFF(options.ALT_GR))
     {
	  options.ALT_GR = ON;
	  chocolate(ALT_GR_IS_ON);
     } /* End if */
     else 
     {
	  options.ALT_GR = OFF;
	  chocolate(ALT_GR_IS_OFF);
     } /* End else */
} /* End alt_gr_toggle */

void control_toggle(GtkWidget *w, gpointer data)
{
     if(ISOFF(options.CONTROL))
     {
	  options.CONTROL = ON;
	  chocolate(CONTROL_IS_ON);
     } /* End if */
     else
     {
	  options.CONTROL = OFF;
	  chocolate(CONTROL_IS_OFF);
     } /* End else */
} /* End control_toggle */

void alt_toggle(GtkWidget *w, gpointer data)
{
     if(ISOFF(options.ALT))
     {
	  options.ALT = ON;
	  chocolate(ALT_IS_ON);
     } /* End if */
     else
     {
	  options.ALT = OFF;
	  chocolate(ALT_IS_OFF);
     } /* End else */
} /* End alt_toggle */

void capslock_toggle(GtkWidget *w, gpointer data)
{
     GtkWidget *foo = (GtkWidget *)data;

     if(options.redirect_window_name || options.SEND_TO_BOTH_WINDOWS)
	  send_redirect_a_keysym(XK_Caps_Lock);

     /* Whatever it currently is, swtich it */
     if(ISOFF(options.CAPS_LOCK))
     {	  
	  options.CAPS_LOCK=ON;
	  gtk_text_insert(GTK_TEXT(foo), NULL, NULL, NULL,
			  CAPS_LOCK_IS_ON, strlen(CAPS_LOCK_IS_ON));
     } /* End if */
     else
     {
	  options.CAPS_LOCK=OFF;
	  gtk_text_insert(GTK_TEXT(foo), NULL, NULL, NULL,
			  CAPS_LOCK_IS_OFF, strlen(CAPS_LOCK_IS_OFF));
     } /* End else */
} /* End capslock_toggle */

void shift_on(GtkWidget *w, gpointer data)
{
     /* Turn shift on */
     options.SHIFT=ON;

     gtk_text_insert(GTK_TEXT((GtkWidget *)data), NULL, NULL, NULL,
                     SHIFT_IS_ON, strlen(SHIFT_IS_ON));
} /* End shift_on */


void do_keyboard_sublayout(void)
{
#define SAFESHOW(wid)     if(wid && GTK_IS_WIDGET(wid)){ \
                               gtk_widget_show_all(wid);\
                          } /* End if */
#define SAFEHIDE(wid) if(wid && GTK_IS_WIDGET(wid)){ \
                               gtk_widget_hide_all(wid);\
                          } /* End if */

     if(GUI.keyboard_elements.show_f_keys) 
     {
	  SAFESHOW(GUI.keyboard_elements.f_keys);
     } /* End if */
     else
     { 
	  SAFEHIDE(GUI.keyboard_elements.f_keys);
     } /* End else */
     if(GUI.keyboard_elements.show_cursor_keys) 
     {
	  SAFESHOW(GUI.keyboard_elements.cursor_keys);
     } /* End if */
     else 
     {
	  SAFEHIDE(GUI.keyboard_elements.cursor_keys);
     } /* End else */
     if(GUI.keyboard_elements.show_keyboard) 
     {
	  SAFESHOW(GUI.keyboard_elements.keyboard);
     } /* End if */
     else 
     {
	  SAFEHIDE(GUI.keyboard_elements.keyboard);
     } /* End else */
     if(GUI.keyboard_elements.show_number_pad) 
     {
	  SAFESHOW(GUI.keyboard_elements.number_pad);
     } /* End if */
     else 
     {
	  SAFEHIDE(GUI.keyboard_elements.number_pad);
     } /* End else */
} /* End do_keyboard_sublayout() */

void do_layout(void)
{
     /* Go through the members of ELEMENTS and hide/show certain widgets
      * according to whether or not they should be there
      */
     if(!ELEMENTS.keyboard)
	  hide_widget(NO_WIDGET, HIDE_KEYBOARD);
     else unhide_widget(NO_WIDGET, SHOW_KEYBOARD);
     if(!ELEMENTS.status)
	  hide_widget(NO_WIDGET, HIDE_STATUS);
     else unhide_widget(NO_WIDGET, SHOW_STATUS);
     if(!ELEMENTS.text)
	  hide_widget(NO_WIDGET, HIDE_OUTPUT);
     else unhide_widget(NO_WIDGET, SHOW_OUTPUT);
     if(!ELEMENTS.toolbar)
	  hide_widget(NO_WIDGET, HIDE_TOOLBAR);
     else unhide_widget(NO_WIDGET, SHOW_TOOLBAR);
     if(!ELEMENTS.buttonbar)
	  hide_widget(NO_WIDGET, HIDE_BOTTOM_ROW_BUTTONS);
     else unhide_widget(NO_WIDGET, SHOW_BOTTOM_ROW_BUTTONS);
     if(!ELEMENTS.menubar)
	  hide_widget(NO_WIDGET, HIDE_MENUS);
     else unhide_widget(NO_WIDGET, SHOW_MENUS);
} /* End do_layout() */

/* Functions dealing with Layout settings (loading/saving, etc)
 *
 * NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
 * - the same structure ELEMENTS_rc_tokens[] is defined
 * in two different places - load_layout_settings() and save_layout_settings().
 * IF YOU MAKE A CHANGE TO ONE PLACE, CHANGE IT IN BOTH PLACES.
 *
 * Originally, this was defined once for both, but a static initialization
 * won't do it, since some of the pointers in the structure point to 
 * dynamically allocated memory which has a tendency to move over the life
 * of the program yielding the pointers no good.  So, as ugly as this is,
 * change it in both places or don't change it.
 */
void load_layout_settings(GtkWidget *emitter, gpointer data)
{
     char *filename = (char *)data;
     int ret = 0;

     /* This should be identical to the same named variable in 
      * save_layout_settings().
      */
     RCVARS ELEMENTS_rc_tokens[] = {
          { NULL, "KBD_FONT",  RC_STR,  &GUI.kfontname,         NULL },
          { NULL, "TXT_FONT",  RC_STR,  &GUI.fontname,          NULL },
          { NULL, "COLOR",     RC_STR,  &GUI.colorname,         NULL },
          { NULL, "KBD_FILE",  RC_STR,  &options.keyboard_file, NULL },
          { NULL, "TOOLTIPS",  RC_BOOL, &options.SHOW_TOOLTIPS, NULL },
          { NULL, "TOOLBAR",   RC_BOOL, &ELEMENTS.toolbar,      NULL },
          { NULL, "MENUBAR",   RC_BOOL, &ELEMENTS.menubar,      NULL },
          { NULL, "KEYBOARD",  RC_BOOL, &ELEMENTS.keyboard,     NULL },
          { NULL, "STATUS",    RC_BOOL, &ELEMENTS.status,       NULL },
          { NULL, "TEXT",      RC_BOOL, &ELEMENTS.text,         NULL },
          { NULL, "BUTTONBAR", RC_BOOL, &ELEMENTS.buttonbar,    NULL },
          { NULL, "KBD_KBD",   RC_BOOL, &GUI.keyboard_elements.show_keyboard, 
            NULL },
          { NULL, "KBD_NUMPAD",RC_BOOL, 
            &GUI.keyboard_elements.show_number_pad,  NULL },
          { NULL, "KBD_CURSOR",RC_BOOL, 
            &GUI.keyboard_elements.show_cursor_keys, NULL },
          { NULL, "KBD_FKEYS", RC_BOOL, 
            &GUI.keyboard_elements.show_f_keys, NULL },
          { NULL, NULL,        RC_NONE, NULL,                NULL }
     };

     ELEMENTS_rc_tokens[0].Val = &GUI.kfontname;
     ELEMENTS_rc_tokens[1].Val = &GUI.fontname;
     ELEMENTS_rc_tokens[2].Val = &GUI.colorname;

     if(!filename)
     {
	  filename = g_new0_(char, strlen(options.home) + 
			     strlen(LAYOUT_FILENAME) + 5);
	  sprintf(filename,"%s%s%s",options.home, _DIR, LAYOUT_FILENAME);
     } /* End if */
     
     ret = read_ConfigFile(filename, ELEMENTS_rc_tokens, 1);
     g_free_(filename);
     
     if(ret != 0)
     {
	  gtkeyboard_message(1,"Couldn't load layout file from disk.\n");

          if(!file_exists(filename))
          {
               /* We couldn't load layout settings.  If it was just
                * because they don't exist, then create it in a more
                * reasonable way by saving our current state.  (default)
                */
               save_layout_settings(NO_WIDGET, (gpointer) NULL);
          } /* End if */
     } /* End if */
     else gtkeyboard_message(1,"Loaded layout file from disk.\n");
} /* End load_layout_settings() */

/* saves the users layout preferences into a file */
void save_layout_settings(GtkWidget *emitter, gpointer data)
{
     char *filename = NULL;

     /* This definition should be identical to the ELEMENTS_rc_tokens[] 
      * variable in load_layout_settings() 
      */
     RCVARS ELEMENTS_rc_tokens[] = {
          { NULL, "KBD_FONT",  RC_STR,  &GUI.kfontname,      NULL },
          { NULL, "TXT_FONT",  RC_STR,  &GUI.fontname,       NULL },
          { NULL, "COLOR",     RC_STR,  &GUI.colorname,      NULL },
          { NULL, "KBD_FILE",  RC_STR,  &options.keyboard_file, NULL },
          { NULL, "TOOLTIPS",  RC_BOOL, &options.SHOW_TOOLTIPS, NULL },
          { NULL, "TOOLBAR",   RC_BOOL, &ELEMENTS.toolbar,   NULL },
          { NULL, "MENUBAR",   RC_BOOL, &ELEMENTS.menubar,   NULL },
          { NULL, "KEYBOARD",  RC_BOOL, &ELEMENTS.keyboard,  NULL },
          { NULL, "STATUS",    RC_BOOL, &ELEMENTS.status,    NULL },
          { NULL, "TEXT",      RC_BOOL, &ELEMENTS.text,      NULL },
          { NULL, "BUTTONBAR", RC_BOOL, &ELEMENTS.buttonbar, NULL },
          { NULL, "KBD_KBD",   RC_BOOL, &GUI.keyboard_elements.show_keyboard, 
            NULL },
          { NULL, "KBD_NUMPAD",RC_BOOL, 
            &GUI.keyboard_elements.show_number_pad,  NULL },
          { NULL, "KBD_CURSOR",RC_BOOL, 
            &GUI.keyboard_elements.show_cursor_keys, NULL },
          { NULL, "KBD_FKEYS", RC_BOOL, 
            &GUI.keyboard_elements.show_f_keys, NULL },
          { NULL, NULL,        RC_NONE, NULL,                NULL }
     };

     ELEMENTS_rc_tokens[0].Val = &GUI.kfontname;
     ELEMENTS_rc_tokens[1].Val = &GUI.fontname;

     ELEMENTS_rc_tokens[2].Val = &GUI.colorname;

     filename = g_new0(char, strlen(options.home) + 
		       strlen(LAYOUT_FILENAME) + 5);
     sprintf(filename,"%s%s%s", options.home, _DIR, LAYOUT_FILENAME);
 
     if(write_ConfigFile(filename, ELEMENTS_rc_tokens) != 0)
	  chocolate("Error writing layout file to disk!\n");
     else chocolate("Successfully saved GTKeyboard layout to disk.\n");

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

void forget_layout_settings(GtkWidget *emitter, gpointer data)
{
     /* Holds the location of where the layout file should be. */
     char *filename = g_new0(char, strlen(options.home) + 
                             strlen(LAYOUT_FILENAME) + 5);

     /* _DIR is the directory separator, "/" on UNIX */
     sprintf(filename,"%s%s%s", options.home, _DIR, LAYOUT_FILENAME);
     
     if(!file_exists(filename))
     {
          /* The file doesn't exist */
          gtkeyboard_error(3, "Cannot delete ", filename, 
                           ": file doesn't exist!\n");
          g_free_(filename);
          return;
     } /* End if */
     else if(file_exists(filename) && is_link(filename))
     {
          /* The file exists, but it's a link.  It's a bad idea to 
           * delete links, since we didn't WRITE it as a link.  Begging
           * for trouble if we delete this, so don't do it.
           */
          gtkeyboard_error(5, "The file ", filename, 
                           " appears to be a link.  Sorry, but I can't delete",
                           " links for security reasons.\n",
                           "(Layout settings remain)\n");
          g_free_(filename);
          return;
     } /* End else if */
     else if(file_exists(filename) && is_regular_file(filename))
     {
          /* The file exists, AND it's a regular file.  Smack it. */
          if(unlink(filename) < 0)
          {
               gtkeyboard_error(5, "Cannot delete ", filename,
                                ":  ", g_strerror(errno), "\n");
          } /* End if */
          else 
          {
               gtkeyboard_message(4, "File ", filename, 
                                  " successfully deleted.  Layout settings ",
                                  "have been forgotten.\n");
          } /* End else */
          
          g_free_(filename);
          return;
     } /* End else if */
     else
     {
          /* Some unknown error...maybe it was a directory, who knows.  Just
           * don't try to delete it.
           */
          gtkeyboard_error(5, "Sorry, ", filename, 
                           " doesn't exist or has errors.  I can't remove ",
                           "your layout file.\n",
                           "(Current layout settings remain)\n");
          g_free_(filename);
          return;
     } /* End else */
} /* End forget_layout_settings() */
