/* app.c
 * For use with GTKeyboard
 * written by David Allen, s2mdalle@titan.vcu.edu
 * http://opop.nols.com/
 *
 * #define DEBUGGING at compile time for interesting info most people don't
 * want to see.
 */
/* 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 APP_C
#include "master.h"
#include <stdarg.h>
#include <unistd.h>
#include "include/aboutmessage.h"

#define BUFFER_SIZE                   512  /* For opening files */
#define MEM_PROFILING                 /* For memory warnings/messages */

/* Static function prototypes */
static void file_ok_button           (GtkWidget *w, GtkFileSelection *fs);
static void setup_default_opts       (void);
static void setup_default_opts_other (void);
static void find_function            (GtkWidget *widget, gpointer data);
static guint search_for_text         (char *search_for, guint point);

/********************* MAIN CODE ********************************/

int main(int argc, char *argv[])
{
     int x;
     int index;
     int qflag = 1;

     fargc = &argc;         /* Globals in master.h */
     fargv = argv;
     
#if defined(DEBUGGING) || defined(GTKEYBOARD_MEMORY_DEBUGGING)
     options.MEM = 0;
     options.gnew = 0;
     options.gnew0 = 0;
     options.gfree = 0;
     options.gmalloc = 0;
     options.gstrdup = 0;
#endif

     gtk_set_locale();
     gtk_init(&argc, &argv);

     /* Check args */
     if(argc>1)
     {
	  if(strcmp(argv[1],"-v")==0 ||
	     strcmp(argv[1],"--verbose")==0)
	       options.VERBOSE = ON;
	  else
	       options.VERBOSE = OFF;
	  
	  /* Does the user WANT us to print usage info? */
	  if(g_strcasecmp(argv[1], "--help")==0    ||
	     g_strcasecmp(argv[1], "-h")==0        ||
	     g_strcasecmp(argv[1], "-?")==0        ||
	     g_strcasecmp(argv[1], "--version")==0 ||
	     g_strcasecmp(argv[1], "-version")==0)
	  {
	       /* This exits the program */
	       usage(argv[0]);
	       exit(0);
	  } /* End if */
	  else if(g_strcasecmp(argv[1],"--license")==0 ||
		  g_strcasecmp(argv[1],"-l")==0 ||
		  g_strcasecmp(argv[1],"-L")==0)
	  {
	       license();
	       exit(0);
	  } /* End if */
	  else
	  {
	       /* If verbose flag is set, pass argv[2], if argv[2] isn't
		* around, then pass NULL
		*/
	       if(options.VERBOSE && argc > 2)
	       {
		    /* Scan for non-flag arguments from the front to the
		     * back.  Use the first valid non-flag argument as
		     * the arg for init_keyboard_stuff() 
		     */
		    qflag = 1;

		    for(index = 1; index <= (argc - 1); index++)
		    {
			 if(argv[index] && argv[index][0] != '-')
			 {
			      init_keyboard_stuff(argv[index]);
			      qflag = 0;
			      break;
			 } /* End if */
		    } /* End for */

		    if(qflag)
			 init_keyboard_stuff((char *)NULL);

	       } /* End if */
	       else if(options.VERBOSE && argc <= 2)
		    x = init_keyboard_stuff((char *)NULL);
	       else
		    x = init_keyboard_stuff(argv[1]);
	  } /* End else */
     } /* End if */
     else
	  x = init_keyboard_stuff((char *)NULL);

     /* Initialize the app */

     redirect_to = Select_Window_Args(fargc, fargv);

     /* This is where the real stuff starts happening */

     CreateMainWindow();

     gtk_main();

     gtkeyboard_mem_cleanup();
     exit(0);
} /* End main */

void license(void)
{
     fprintf(stdout,"GTKeyboard version %s\n\n",VERSION);
     fprintf(stdout,"GTKeyboard is released under the terms of the GNU\n");
     fprintf(stdout,"General Public License.  Please see COPYING that\n");
     fprintf(stdout,"came with the source distribution or \n\n");
     fprintf(stdout,"http://www.gnu.org/\n\n");
     fprintf(stdout,"for more details.\n");
     MEM_REPORT;
     exit(0);
} /* End license */

void usage(char *input)
{
     register int x;

     fprintf(stdout,"Usage:  %s filename\n",input);
     fprintf(stdout,"GTKeyboard version %s\n\n",VERSION);

     for(x=0; x<ABOUTLINES; x++)
	  fprintf(stdout,"%s\n",about_message[x]);

     fprintf(stdout,"This copy of GTKeyboard was compiled ");
     fprintf(stdout,"on %s %s for %s %s\n\n",
	     __DATE__, __TIME__, OS_TYPE, ARCH_TYPE);
     /* OS_TYPE and ARCH_TYPE get defined in the Makefile as the result
      * of the output of configure */

     if(options.VERBOSE)
     {
	  fprintf(stdout,"GTKeyboard comes with NO WARRANTY ");
	  fprintf(stdout,"either expressed or implied.  If it breaks, ");
	  fprintf(stdout,"you get both pieces.\n\n");
     } /* End if */
     
     MEM_REPORT;

     exit(0);
} /* End usage */

/* ATTEMPT to clone the window and keep going */
void CLONE(void)
{
#ifdef HAVE_FORK
     int retval = 0;
     /* Ignore child signals */

#if defined(SIGCHLD)
     signal(SIGCHLD, SIG_IGN);
#endif 

#if defined(SIGCLD)
     signal(SIGCLD, SIG_IGN);
#endif
     
     if(fork() != 0)
	  return;
     else
     {
	  retval = execlp("gtkeyboard",(char *)NULL);
	  
	  if(retval == -1)
	  {
	       fprintf(stderr,"An error occurred cloning GTKeyboard.\n");
	       fprintf(stderr,"The error was %s\n",g_strerror(errno));
	  } /* End if */
	  exit(0);     /* Kill child thread */
     } /* End else */
     exit(0);          /* Kill child thread */
#else  /* HAVE_FORK Undefined */

     gtkeyboard_message(3,"When I was compiled I couldn't find fork().\n",
			"That's ok though, since this was an experimental ",
			"function and probably didn't work anyway.  :)\n");
#endif

     MEM_REPORT;
} /* End CLONE */

int init_keyboard_stuff(char *input)
{
     char *filename;
     char testbuffer[1024];
     char *homeptr;

     /* This sets the values in the structure GUI to default values.
      * I'll add things for changing the default in that function later.
      */
     setup_default_opts();                  /* Handles the stuff in options */
     setup_default_opts_other();

     /* Allocate the necessary memory to copy location of support files
      * into */
     options.extrafiles = g_new0_(char, (strlen(INSTALL_DOC_DIR) + 3));
     sprintf(options.extrafiles,"%s%s",INSTALL_DOC_DIR, _DIR);

     options.keyboard      = NO_KEYBOARD;
     options.old_keyboard  = NO_KEYBOARD;
     
     sprintf(testbuffer,"%s%s%s", INSTALL_DOC_DIR, _DIR, ABOUT_INFO);

     if(!file_exists(testbuffer))
     {
          fprintf(Q,"Bad mojo: the path \"%s\" doesn't exist, which means\n",
                  testbuffer);
          fprintf(Q,"that you may have an incomplete or incorrect\n");
          fprintf(Q,"installation of GTKeyboard.  If the application is\n");
          fprintf(Q,"just confused about the location of the files, please\n");
          fprintf(Q,"use the \"extra_files\" option in your .gtkeyboardrc.\n");
          fprintf(Q,"For more help, see the documentation.\n");
     } /* End if */
     
     homeptr = getenv("HOME");

     if(homeptr)
     {
	  options.home = g_strdup_(getenv("HOME"));
     } /* End if */
     else options.home = NULL;

     filename = g_new0_(char, strlen(options.home) + 5 + strlen(RC_FILENAME));
     sprintf(filename,"%s%s%s", (options.home ? options.home : "/tmp"),
	     _DIR, RC_FILENAME);
     
#ifdef HAVE_RAND
     srand(getpid() + time((time_t *) NULL) + (time((time_t *)NULL) << 15));
#endif /* HAVE_RAND */

     /* This parses ALL user preferences from ~/.gtkeyboardrc into various
      * structures - the important parts of this are in rc_file.h and
      * file_manip.c
      */

     parse_user_resource_file(filename);

     CONDFREE(filename);

     /* If we were given something on the command line, that takes 
      * precedence.  Use that as workingfilename
      * options.workingfile may have gotten allocated in the user file parsing
      * routine though.
      */
     if(input != NULL)
     {
	  CONDFREE(options.workingfile);
	  options.workingfile = g_strdup_(input);
     } /* End if */
     else if(!options.workingfile) /* Only if it hasn't been initialized */
     {
	  options.workingfile = (char *)NULL;
     } /* End else if */
     
     return(1);
} /* End init_keyboard_stuff */

/* This gets called once at setup to initialize all the GUI but 
 * non-keyboard related opts
 * Shouldn't return anything or allocate any memory, it just sets
 * things to "reasonable" defaults.
 */
static void setup_default_opts_other(void)
{
     register int x;

     /* Here's where you get to set all of the defaults for GUI structure
      * members.
      */
     GUI.SHADOW_TYPE                  = GTK_SHADOW_ETCHED_OUT;
     GUI.PULLOFFS_SIDE                = GTK_POS_LEFT;
     GUI.cursor                       = (GdkCursor *)      NULL;
     GUI.fontname                     = (gchar *)          NULL;
     GUI.kfontname                    = (gchar *)          NULL;
     GUI.colorname                    = (gchar *)          NULL;
     GUI.textstyle                    = (GtkStyle *)       NULL;
     GUI.style                        = (GtkStyle *)       NULL;
     GUI.font                         = (GdkFont *)        NULL;
     GUI.kfont                        = (GdkFont *)        NULL; 
     GUI.deflt                        = (GtkStyle *)       NULL;
     GUI.xwindow                      = (Window)           NULL;
     GUI.popup_menu                   = (GtkWidget *)      NULL;
     GUI.item_factory                 = (GtkItemFactory *) NULL;
     GUI.BUMP_AMOUNT                  = DEFAULT_BUMP_AMOUNT;
     GUI.tooltips                     = (GtkTooltips *)NULL;
     GUI.use_handleboxes              = OFF;   /* Do not use handle boxes */

     GUI.keyboard_elements.show_cursor_keys = ON;
     GUI.keyboard_elements.show_number_pad  = ON;
     GUI.keyboard_elements.show_f_keys      = ON;
     GUI.keyboard_elements.show_keyboard    = ON;

     GUI.keyboard_elements.cursor_keys = GUI.keyboard_elements.keyboard =
	  GUI.keyboard_elements.number_pad = GUI.keyboard_elements.f_keys = 
	  (GtkWidget *)NULL;

     /* Initialize toolbar show values.  All on by default */
     Toolbar.TB_NEW              = ON;  Toolbar.TB_FONT             = ON;
     Toolbar.TB_KEYBOARD_FONT    = ON;  Toolbar.TB_WINDOW_GRAB      = ON;
     Toolbar.TB_WINDOW_IGNORE    = ON;  Toolbar.TB_CUT              = ON;
     Toolbar.TB_COPY             = ON;  Toolbar.TB_PASTE            = ON;
     Toolbar.TB_OPEN             = ON;  Toolbar.TB_SAVE             = ON;
     Toolbar.TB_COLOR            = ON;  Toolbar.TB_STATUS_REPORT    = ON;
     Toolbar.TB_RESET_STYLE      = ON;  Toolbar.TB_SHORTCUTS        = ON;     
     Toolbar.TB_POSITIONING      = ON;  Toolbar.TB_RAISE            = ON;
     Toolbar.TB_LOWER            = ON;

     for(x=0; x<MAX_CUSTOM_COLORS; x++)
	  GUI.custom_colors[x] = (char *)NULL;

     MEM_REPORT;
     return;
} /* End setup_default_opts_other */

/* This gets called once at startup, sets all the initial values for
 * options.whatever stuff
 */
static void setup_default_opts(void)
{
     register int x = 0;

     /* All screen ELEMENTS on by default */
     ELEMENTS.toolbar = ELEMENTS.menubar = ELEMENTS.text = ELEMENTS.status = 
	  ELEMENTS.keyboard = ELEMENTS.buttonbar = 1;

     /* Set up some default values for program parameters
      * Can be changed by parse_rcfile later.  Hopefully these are reasonable.
      */
     options.handlerid                = NO_HANDLER;  /* No registered handler*/
     options.xpos = options.ypos      = -1;          /* Nonexistant */
     options.other                    = (Window)     NULL;
     options.redirect_window          = (Window)     NULL;
     options.home                     = (char *)     NULL;
     options.redirect_window_name     = (char *)     NULL;
     options.workingfile              = (char *)     NULL;
     options.tempslot                 = (char *)     NULL;
     options.keyboard_file            = (char *)     NULL;

     options.document_modified        = OFF; /* Document doesn't need save */

     /* Signal handler for all signals. */
     options.signal_handling          = gtkeyboard_signal_handler;

     /* Attach some signals depending on what the system has 
      * We do this so if the user sends us an interrupt signal,
      * we can clean up all of our allocated memory and not cause 
      * any leaks.  It exits immediately though, no questions asked.
      * Do not add signals for STOP/CONT because Ctrl-Z in the terminal
      * sends those I think and I don't want to do that, because I test
      * it by running it in an xterm and sometimes putting it in the
      * background so I can type.
      */

#ifdef SIGINT
     signal(SIGINT, options.signal_handling);
#endif /* SIGINT */
#ifdef SIGTERM
     signal(SIGTERM, options.signal_handling);
#endif /* SIGTERM */
#ifdef SIGHUP
     signal(SIGHUP, options.signal_handling);
#endif /* SIGHUP */
#ifdef SIGQUIT
     signal(SIGQUIT, options.signal_handling);
#endif /* SIGQUIT */

     for(x=0; x<SHORTCUTS; x++)
	  options.shortcuts[x] = (char *)NULL; /* This is before load */

     /* options.ASK_REMAP_ON_EXIT     = OFF; *//* Currently deprecated      */
     options.BORDERLESS               = OFF;   /* Show main window as popup?*/
     options.STATUS_LOGGING           = OFF;   /* Save the status window    */
     options.CAPS_LOCK                = OFF;   /* Caps lock starts at OFF   */
     options.SHIFT                    = OFF;   /* Shift starts at OFF       */
     options.ALT_GR                   = OFF;   /* Alt-GR key status         */
     options.CONTROL                  = OFF;   /* Control button at OFF     */
     options.NUMLOCK                  = OFF;   /* Numlock starts at OFF     */
     options.ALT                      = OFF;   /* Alt key starts at OFF     */
     options.WORD_WRAP                = OFF;   /* Word wrapping in text     */
     options.VERBOSE                  = OFF;   /* Spew messages             */
     options.ASK_SAVE_ON_EXIT         = ON;    /* Ask to save unsaved file  */
     options.SHOW_MENU                = ON;    /* Show itemfactory menu     */
     options.RANDOMIZE_XPMS           = OFF;   /* Silly eyecandy            */
     options.REDIRECT_POLICY_IMPLICIT = ON;    /* Default to implicit redir */
     options.SEND_TO_BOTH_WINDOWS     = OFF;   /* Keypresses sent to 2 wins?*/
     options.RANDOM_STRING_CHARACTERS = 50;    /* Length of random string   */
     options.SHOW_TOOLTIPS            = ON;    /* Do show the tooltips      */
     options.IGNORE_LAYOUT_FILE       = OFF;   /* Dont ignore  layout file  */

#ifdef DEBUGGING
     fprintf(Q,"Setup_default_opts:  ");
     MEM_REPORT;
#endif /* DEBUGGING */
} /* End setup_default_opts */

/* Find a string in the main text area
 */
void find_dialog(GtkWidget *emitter, gpointer data)
{
     GtkWidget *label;
     GtkWidget *cancel;
     GtkWidget *hbox;
     GtkWidget *dialog_window;
     GtkWidget *find;
     GtkWidget *entry;

     dialog_window = gtk_dialog_new ();
     gtk_signal_connect(GTK_OBJECT (dialog_window), "destroy",
			GTK_SIGNAL_FUNC (smack),
			dialog_window);

     gtk_window_set_title(GTK_WINDOW (dialog_window), "Find");

     gtk_container_border_width(GTK_CONTAINER (dialog_window), 5);

     hbox = gtk_hbox_new(TRUE, TRUE);
     label = gtk_label_new("Find Text:");
     gtk_widget_show(label);
     entry = gtk_entry_new();
     gtk_widget_show(entry);

     gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_window)->vbox),
			hbox, TRUE, TRUE, 0);
     find = gtk_button_new_with_label("Find");
     gtk_signal_connect(GTK_OBJECT(find), "clicked",
			GTK_SIGNAL_FUNC(find_function),
			entry);
     
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_window)->action_area),
			find, TRUE, TRUE, 0);
     cancel = create_cancel_button(dialog_window);

     GTK_WIDGET_SET_FLAGS(cancel, GTK_CAN_DEFAULT);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_window)->action_area),
			cancel, TRUE, TRUE, 0);
     gtk_widget_grab_default(cancel);

     gtk_window_set_position(GTK_WINDOW(dialog_window), GTK_WIN_POS_CENTER);

     gtkeyboard_window_common_setup(dialog_window);
     gtk_widget_show_all(dialog_window);
} /* End find_dialog() */

static guint last_point = 0;

static void find_function(GtkWidget *widget, gpointer data)
{
     GtkWidget *entry = (GtkWidget *)data;
     char *search_for = gtk_entry_get_text(GTK_ENTRY(entry));
     
     last_point = search_for_text(search_for, last_point);
     
     if(last_point == 0)
     {
          annoying_popup("Text not found!");
          return;
     } /* End if */

     /* Since there is a match at last_point, we don't want to search at
      * last_point again since we will immediately find a match.  So increment
      * it.  But if it's 0, that means there was an error with the last search.
      * If that's the case, don't increment it.
      */
     if(last_point >= 0)
          last_point++;
} /* End find_function */

/* 
 * Popup to give some type of message or warn of an error.
 */
void annoying_popup(const char *message)
{
     GtkWidget *popup    = gtk_dialog_new();
     GtkWidget *okbutton = create_ok_button(GTK_SIGNAL_FUNC(smack), popup);
     GtkWidget *label    = gtk_label_new(message);
     
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
			label, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			okbutton, TRUE, TRUE, 0);

     gtk_window_set_position(GTK_WINDOW(popup), GTK_WIN_POS_CENTER);
     
     /* Bevel the OK button all purty like.  :) */
     GTK_WIDGET_SET_FLAGS(okbutton, GTK_CAN_DEFAULT);
     gtk_widget_grab_default(okbutton);

     gtkeyboard_window_common_setup(popup);
     gtk_widget_show_all(popup);

#ifdef GTKEYBOARD_MEMORY_DEBUGGING
     MEM_REPORT;
#endif
} /* End annoying_popup */

static void wipe_the_slate(GtkWidget *emitter, gpointer data)
{
     clear_all_text_really(emitter, GUI.main_output_text);
     
     if(options.workingfile)
     {
          g_free_(options.workingfile);
     }

     options.workingfile = (char *)NULL;
} /* End wipe_the_slate() */

/* New file dialog box */
void NewFile(gpointer callback_data, guint callback_action, GtkWidget *widget)
{  
     GtkWidget *dialog, *label, *ok, *cancel, *auxlabel, *aux2label;
     dialog    = gtk_dialog_new();
     label     = gtk_label_new("Are you sure you want to\ncreate a new file?");
     auxlabel  = gtk_label_new("Any unsaved changes to the");
     aux2label = gtk_label_new("current document will be lost");
     ok        = create_ok_button(GTK_SIGNAL_FUNC(wipe_the_slate), dialog);
     cancel    = create_cancel_button(dialog);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label,
			FALSE, FALSE, 10);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), auxlabel,
                        FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), aux2label,
                        FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
			ok, FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
			cancel, FALSE, FALSE, 0);

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

     MEM_REPORT;
} /* End NewFile */

/* Error routine for gtkeyboard
 * 
 * Call it with an arbitrary number of strings.
 * Partially ripped off from g_strconcat from glib
 */
void gtkeyboard_error(const int count, ...)
{
     int x;
     va_list args;
     char *str;

     va_start(args, count);

     /* Ring warning bell */
#ifdef DEBUGGING
     if(GDK_DISPLAY())
	  gdk_beep();
#endif /* DEBUGGING */

     for(x=0; x<count; x++)
     {
	  str = va_arg(args, char *);
	  if(str)
	  {
	       fprintf(stderr,"%s",str);
	       fflush(stderr);

	       if(GDK_DISPLAY())
		    chocolate(str);
	  } /* End if */
     } /* End for */

     va_end(args);

     FLUSH_EVERYTHING;

#ifdef DEBUGGING
     MEM_REPORT;
#endif /* DEBUGGING */
} /* End gtkeyboard_error */

/* Message routine - call with an arbitrary number of strings.
 * partially ripped off from glib.
 */
void gtkeyboard_message(const int count, ...)
{
     int x;
     va_list args;
     char *str;

     va_start(args, count);

     for(x=0; x<count; x++)
     {
	  str = va_arg(args, char *);
	  if(str && GDK_DISPLAY())
	       chocolate(str);

#ifdef DEBUGGING
	  fprintf(stdout,"%s",str); 
	  fflush(stdout);
#endif
     } /* End for */
     va_end(args);

#ifdef DEBUGGING
     MEM_REPORT;
#endif /* DEBUGGING */
} /* End gtkeyboard_message */

/* This function changes the name of the file that the program saves to */
void change_filename(GtkWidget *foo, gpointer data)
{
     GtkWidget *grabfrom = (GtkWidget *)data;
     char *changed = "Working filename changed : ";
     char *stuff = gtk_entry_get_text(GTK_ENTRY(grabfrom));

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

     options.workingfile = g_strdup_( stuff );

     /* I don't feel like commenting this.  Figure it out yourself.  :) */
     gtk_widget_destroy(foo->parent->parent->parent);
     
     gtkeyboard_message(3, changed, options.workingfile, "\n");
     clear_all_text_really((GtkWidget *)NULL, (gpointer)GUI.main_output_text);
     
     MEM_REPORT;
} /* End change_filename */

/* PROGRAM_INFO
 * displays program info through info_code that is passed to it.
 * it can display things like CHANGELOG, COPYING, about information, and so
 * on.  Defines for info_code are in master.h
 */
void program_info(GtkWidget *emitter, gint info_code)
{
     GtkWidget *popup, *label, *ok, *aboutlabel;
     GtkWidget *table, *vscroll;
     GtkStyle *text_style;
     char labelname[512];
     char filename[512];
     char *pinfo = "Displaying Program Information:  ";
     char error_message[512];
     FILE *fp;                  /* Pointer to information file */
     char buffer[BUFFER_SIZE];
     int max_width = -1, temp_width = 0;

     sprintf(labelname,"Program information:  ");

     switch(info_code)
     {
     case INFO_CHANGELOG:
	  sprintf(filename,"%s%s", options.extrafiles, CHANGELOG);
	  strcat(labelname,"What's new - CHANGELOG");
	  sprintf(error_message,"Error opening CHANGELOG in %s\n",filename);
	  gtkeyboard_message(2, pinfo, "CHANGELOG\n");
	  break;
     case INFO_ABOUT:
	  sprintf(filename,"%s%s", options.extrafiles, ABOUT_INFO);
	  strcat(labelname,"About GTKeyboard");
	  sprintf(error_message,"Error opening about information in %s\n",
		  filename);
	  gtkeyboard_message(2, pinfo, "ABOUT\n");
	  break;
     case INFO_LICENSE:
	  sprintf(filename,"%sCOPYING", options.extrafiles);
	  strcat(labelname,"GTKeyboard's License");
	  sprintf(error_message,"Error opening license in %s",filename);
	  gtkeyboard_message(2, pinfo, "LICENSE\n");
	  break;
     default:         /* Default is to display license */
	  sprintf(filename,"%sCOPYING",options.extrafiles);
	  strcat(labelname,"GTKeyboard's License");
	  sprintf(error_message,"Error opening license in %s",filename);
	  gtkeyboard_message(3,"Displaying DEFAULT program information.\n",
			     pinfo, "LICENSE.\n");
	  break;
     } /* End switch */

     popup      = gtk_dialog_new();
     label      = gtk_label_new(labelname);
     aboutlabel = gtk_text_new(NULL, NULL);
     ok         = create_ok_button(GTK_SIGNAL_FUNC(smack), popup);

     table = gtk_table_new(2, 2, FALSE);
     gtk_table_attach(GTK_TABLE(table), aboutlabel, 0, 1, 0, 1,
                      GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                      GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
     vscroll = gtk_vscrollbar_new(GTK_TEXT(aboutlabel)->vadj);
     gtk_table_attach(GTK_TABLE(table), vscroll, 1, 2, 0, 1, 
                      GTK_FILL, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

     /* Pack the label, then the table (with the text) into the dialog */
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
			label, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
			table, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			ok, TRUE, TRUE, 0);

     /* Make it uneditable */
     gtk_text_set_editable(GTK_TEXT(aboutlabel), FALSE);
     gtk_text_freeze(GTK_TEXT(aboutlabel));

     /* Tell the user where we're getting the info from. */
     gtkeyboard_message(3,"Loading file from ",filename,"\n");
     
     /* Add in the necessary text */
     fp = fopen(filename,"r");      /* Open the file for reading */

     if(!fp)
     {
	  gtkeyboard_error(5,"Error: open of ",filename," failed:  ",
			   g_strerror(errno),"\n");

	  gtk_text_insert(GTK_TEXT(aboutlabel), NULL, NULL, NULL,
			  error_message, strlen(error_message));
     } /* End if */
     else
     {
          text_style = gtk_widget_get_style(aboutlabel);
          
	  while(!feof(fp))
	  {
	       fgets(buffer,BUFFER_SIZE,fp);
               
	       if(buffer) 
               {
                    temp_width = 
                         gdk_string_width(text_style->font, buffer);
                    
                    if(temp_width > max_width) 
                         max_width = temp_width;
                    
		    gtk_text_insert(GTK_TEXT(aboutlabel), NULL, NULL, NULL,
				    buffer, strlen(buffer));
               } /* End if */
	  } /* End while */
	  
	  /* Close filestream */
	  fclose(fp);
     } /* End else */

     /* Set the width of the window to max_width which is the width of the
      * widest line string in the file.  This should make it display like
      * reasonable text regardless of what the person's systems default font
      * is.  
      */
     gtk_widget_set_usize(aboutlabel, (max_width + 10), 300);

     gtk_text_thaw(GTK_TEXT(aboutlabel));

     gtk_window_set_position(GTK_WINDOW(popup), GTK_WIN_POS_CENTER);

     gtkeyboard_window_common_setup(popup);
     /* Show all widgets */
     gtk_widget_show_all(popup);
} /* End program_info */

/*
 * Set up the file dialog and do all that funny stuff relating to returning
 * the name of the file they want, etc.
 */
void OpenFile(gpointer callback_data, guint callback_action, GtkWidget *widget)
{
     /* Set up file selection dialog */
     GtkWidget *fileselection;
     fileselection = gtk_file_selection_new(FILE_SELECTION_TITLE);
     
     /* Hook up the action of clicking the OK button */
     gtk_signal_connect(GTK_OBJECT(
	  GTK_FILE_SELECTION(fileselection)->ok_button),
			"clicked", GTK_SIGNAL_FUNC(file_ok_button),
			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_widget_show_all(fileselection);

     MEM_REPORT;
} /* End newfile */

/* Easy callback for menus wishing to clear all text from a widget
 * specified by ID - see the headers for CLEAR_STATUS, CLEAR_TEXT, et al
 */
void clear_wrapper(GtkWidget *w, gint ID)
{
     switch(ID)
     {
     case CLEAR_STATUS:
	  clear_all_text(NOWIDGET, GUI.status);
	  break;
     case CLEAR_TEXT:
	  options.document_modified = ON;
	  clear_all_text(NOWIDGET, GUI.main_output_text);
	  break;
     default:
	  annoying_popup("Error - no widget specified {clear_wrapper}");
	  break;
     } /* End switch */

     MEM_REPORT;
} /* End clear_wrapper */

/* This function nukes EVERYTHING that is in the text window passed to it
 * via data
 */
void clear_all_text(GtkWidget *w, gpointer data)
{
     GtkWidget *popup = gtk_dialog_new();
     GtkWidget *yes   = create_ok_button(GTK_SIGNAL_FUNC(
	  clear_all_text_really), data);
     GtkWidget *no    = create_cancel_button(popup);
     GtkWidget *label = gtk_label_new("Really Clear All Text?");

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

     gtk_window_set_position(GTK_WINDOW(popup), GTK_WIN_POS_CENTER);

     gtkeyboard_window_common_setup(popup);
     gtk_widget_show_all(popup);

     MEM_REPORT;
} /* End clear_all_text */

/* For those that are sure that they want to delete everything. */
void clear_all_text_really(GtkWidget *widget, gpointer data)
{
     GtkWidget *foobar = (GtkWidget *)data;
     int length = gtk_text_get_length(GTK_TEXT((GtkWidget *)data));
     
     gtk_text_set_point(GTK_TEXT(foobar), length);
     
     /* Now delete backward ALL characters. */
     gtk_text_backward_delete(GTK_TEXT(foobar), (guint)length);
     
     /* Reset point to be 0 */
     gtk_text_set_point(GTK_TEXT(foobar), 0);
     /* The yes button's parent is a hbox, then vbox, then window so 3x
      * parent
      */
     if(widget)  /* We were called through a dialog */
          gtk_widget_destroy(widget->parent->parent->parent);

     if(foobar == GUI.main_output_text && widget)
	  gtkeyboard_message(1,"All text cleared:  Work area.\n");
     else if(foobar == GUI.status)
	  gtkeyboard_message(1,"All text cleared:  Status.\n");

     MEM_REPORT;
} /* End clear_all_text_really */

int load_file(char *filename, GtkWidget *textbox)
{
     char buffer[BUFFER_SIZE];
     FILE *fp;
     char *fle = NULL;

     if(!options.workingfile || options.workingfile == (char *)NULL)
     {
	  return(0);
     } /* End if */

     fp = fopen(filename,"r");
     fle = g_strerror(errno);

     if(!fp)
     {
	  gtkeyboard_error(5,"Open of ", filename, " failed: ", 
			   fle, "\n");
	  MEM_REPORT;
	  return(0);
     } /* End if */
     else
     {
	  gtk_text_freeze(GTK_TEXT(textbox));
	  while(!feof(fp))
	  {
	       /* Kill the first character - this is how we will know if
		* we should insert it or not 
		*/
	       buffer[0] = 0;
	       fgets(buffer,BUFFER_SIZE,fp);
	       if(buffer[0])
		    gtk_text_insert(GTK_TEXT(textbox), NULL, NULL, NULL,
				    buffer,strlen(buffer));
	  } /* End while */
	  gtk_text_thaw(GTK_TEXT(textbox));
	  fclose(fp);
     } /* End else */

     /* Reset the point to the 0th character so the user will see the
      * top of the file, not the bottom.
      */
     gtk_text_set_point(GTK_TEXT(textbox), (gint) 0);

     gtkeyboard_message(3,"File ",filename," loaded.\n\0");
     MEM_REPORT;
     return(1);
} /* End load_file */

/* What happens when you click the ok button within the open file selection
 * dialog
 */
static void file_ok_button(GtkWidget *w, GtkFileSelection *fs)
{
     char *tempfile;
     char buffer[BUFFER_SIZE]; 
     int length;
     FILE *fp;
     
     /* --- Get the name --- */
     tempfile = gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs));

     /* Free the old file information memory */
     CONDFREE(options.workingfile);
     
     /* --- Allocate space and save it. --- */
     if(tempfile && strlen(tempfile)>=1)
     {
	  options.workingfile = g_strdup_(tempfile);
     } /* End if */
#ifdef DEBUGGING
     else
     {
	  MEM_REPORT;
	  return;       /* They lied to us about filename */
     } /* End else */
#endif

     /* Destroy the file selection - we've taken it for what it's worth  */
     gtk_widget_destroy((GtkWidget *)fs);

     /* Fresh widget, nothing in it.  */
     if((fp=fopen(options.workingfile,"r"))!=NULL)
     {
	  /* Eliminate the old buffer completely */
	  length = gtk_text_get_length(GTK_TEXT(GUI.main_output_text));
	  
	  /* Set the point to the last character */
	  gtk_text_set_point(GTK_TEXT(GUI.main_output_text), length);
	  
	  /* Now delete backward ALL characters. */
	  gtk_text_backward_delete(GTK_TEXT(GUI.main_output_text), 
				   (guint)length);
	  
	  /* Reset point to be 0 */
	  gtk_text_set_point(GTK_TEXT(GUI.main_output_text), 0);

	  /* Do not repaint until we're done */
	  gtk_text_freeze(GTK_TEXT(GUI.main_output_text));
	  /* While there is still data left to read, write it into the text
	   * widget window 
	   */
	  
	  while(!feof(fp))
	  {
	       /* More data, Luke! */
	       fgets(buffer,BUFFER_SIZE,fp);
	     
	       /* Insert what we've got */
	       gtk_text_insert(GTK_TEXT(GUI.main_output_text), NULL, 
			       NULL, NULL, buffer, strlen(buffer));
	  } /* End while */
	  
	  /* Close the file */
	  fclose(fp);

	  /* And now reset the point to 0 */
	  gtk_text_set_point(GTK_TEXT(GUI.main_output_text), 0);
	  gtk_text_thaw(GTK_TEXT(GUI.main_output_text));

	  gtkeyboard_message(3,"Loaded file ", options.workingfile, 
			     "\n");
	  return;
     } /* End if fp!=NULL */
     else
     {
	  annoying_popup("Couldn't open file!");

	  gtkeyboard_error(5,"Open of ", options.workingfile, "failed: ",
			   g_strerror(errno),"\n");
     } /* End else */

     MEM_REPORT;
     return;
} /* End file_ok_button */

/* This is called in the menus to change the output */
void change_output(GtkWidget *w, int data)
{
     int foo = data;
     char *def = "/dev/null";       /* Bitbucket */
     char *changed = "Output file changed to : ";
     
     CONDFREE(options.workingfile);

     switch(foo)
     {
     case OUTPUT_CONSOLE:
	  options.workingfile = g_strdup_(OUTPUT_DEVICE_CONSOLE);
	  gtkeyboard_message(3, changed, options.workingfile, CR);
	  break;
     case OUTPUT_PRINTER:
	  options.workingfile = g_strdup_(OUTPUT_DEVICE_PRINTER);
	  gtkeyboard_message(3, changed, options.workingfile, CR);
	  break;
     case OUTPUT_TEMPFILE:
	  options.workingfile = g_strdup_(OUTPUT_DEVICE_TEMPFILE);
	  gtkeyboard_message(3, changed, options.workingfile, CR);
	  break;
     default:
	  options.workingfile = g_strdup_(def);
	  gtkeyboard_message(3, changed, options.workingfile, CR);
	  break;
     } /* End switch */

     MEM_REPORT;
} /* End void change_output */

/* Confirms the save on exit if needed */
void confirm_save_on_exit(char *filename)
{
     GtkWidget *popup, *label, *yes, *no, *other;
     char labelname[1024];

     sprintf(labelname,"Do you want to save %s?",filename);

     popup = gtk_dialog_new();
     label = gtk_label_new(labelname);
     yes   = create_ok_button(GTK_SIGNAL_FUNC(save_handler), NULL);
     other = gtk_button_new_with_label("Other Filename");
     no    = create_cancel_button_with_signal(GTK_SIGNAL_FUNC(CloseApp), 
					      popup);

     gtk_signal_connect(GTK_OBJECT(other), "clicked",
			GTK_SIGNAL_FUNC(save_file_as_on_exit), 
			"Save File As:");
     
     /* Pack it up, pack it in */
     gtk_box_pack_start(GTK_BOX (GTK_DIALOG(popup)->action_area),
			yes, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			no, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->action_area),
			other, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox),
			label, TRUE, TRUE, 0);
     
     /* Show everything */
     gtk_window_set_position(GTK_WINDOW(popup), GTK_WIN_POS_CENTER);

     gtkeyboard_window_common_setup(popup);
     gtk_widget_show_all(popup);

     MEM_REPORT;
} /* End confirm_save_on_exit */

void save_handler(GtkWidget *w, gpointer data)
{
     /* This gets called by confirm_save_on_exit */
     save_output_text((GtkWidget *)NULL, GUI.main_output_text);
     byebye((GtkWidget *)NULL, 1);
} /* End save_handler */

/* Save as dialog on exit */
void save_file_as_on_exit(GtkWidget *w, gpointer data)
{  
     GtkWidget *foo, *cancel, *ok, *entry, *label;
     GtkWidget *parent = w;

     entry = gtk_entry_new_with_max_length(70);
     foo   = gtk_dialog_new();
     ok    = create_ok_button(GTK_SIGNAL_FUNC(change_filename_save_and_exit),
			      entry);
     cancel = create_cancel_button(entry);
     label = gtk_label_new("Specify a filename:");

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

     if(parent)
          gtk_widget_destroy(w->parent->parent->parent);

     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(foo)->vbox), label,
			TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(foo)->vbox), 
			entry, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(foo)->action_area),
			ok, TRUE, TRUE, 0);
     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(foo)->action_area),
			cancel, TRUE, TRUE, 0);
			
     /* Set the title */
     gtk_window_set_title(GTK_WINDOW(foo),
			  "Save as:");
     
     /* Center the window */
     gtk_window_set_position(GTK_WINDOW(foo), GTK_WIN_POS_CENTER);
     
     gtkeyboard_window_common_setup(foo);
     gtk_widget_show_all(foo);

     MEM_REPORT;
} /* End save_file_as_on_exit */

static void just_get_out(GtkWidget *emitter, gpointer data)
{
     if(data)
          save_output_text((GtkWidget *)NULL, (gpointer)NULL);
     
     MEM_REPORT;
     byebye((GtkWidget *)NULL, 1);
} /* End just_get_out() */

/* Change workingfile, save the file and get the hell out of the 
 * program via byebye
 */
void change_filename_save_and_exit(GtkWidget *w, gpointer data)
{
     GtkWidget *grabfrom = (GtkWidget *)data;
     char *stuff = (char *)NULL;
     GtkWidget *toplevel = w;

     if(grabfrom && GTK_IS_WIDGET(grabfrom))
	  stuff = gtk_entry_get_text(GTK_ENTRY(grabfrom));
     else stuff = NULL;

     CONDFREE(options.workingfile);

     if(!stuff || stuff == (char *)NULL)
     {
	  gtkeyboard_error(1,"Can't save file at exit\n");
          byebye((GtkWidget *)NULL, 1);
     } /* End if */

     options.workingfile = g_strdup_(stuff);

     /* Find the widget's toplevel window parent so we can destroy it */
     while(toplevel && !GTK_IS_WINDOW(toplevel))
	  toplevel = toplevel->parent;

     if(toplevel && GTK_IS_WINDOW(toplevel))
	  gtk_widget_destroy(toplevel);

     if(file_exists(options.workingfile))
     {
          /* Now we have to ask the user if they want to overwrite the
           * file
           */
          GtkWidget *popup  = gtk_dialog_new();
          GtkWidget *ok     = create_ok_button(GTK_SIGNAL_FUNC(just_get_out),
                                              options.workingfile);
          GtkWidget *cancel = 
               create_cancel_button_with_signal(GTK_SIGNAL_FUNC(just_get_out),
                                                NULL);
          char buffer[4096];
          GtkWidget *label;

          /* Free up stale mem */
          CONDFREE(stuff);

          sprintf(buffer,"The file\n\"%s\"\nalready exists.  Save anyway?",
                  options.workingfile);
          label = gtk_label_new(buffer);

          gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label,
                             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 if */
     else 
     {
          save_output_text((GtkWidget *)NULL, (gpointer)NULL);
          
          CONDFREE(stuff);
          
          MEM_REPORT;
          byebye((GtkWidget *)NULL, 1);
     } /* End else */
} /* End change_filename_save_and_exit */

static guint search_for_text(char *search_for, guint point)
{
     guint len        = 0;
     guint target_len = strlen(search_for);
     guint total_len  = gtk_text_get_length(GTK_TEXT(GUI.main_output_text));
     guint offset     = 0;  /* The number of characters from point. */
     guint result     = 0;
     char mesg_buffer[1024];
     char  *text;
     char  *ptr;

     if(point < 0)
     {
          fprintf(Q,"GTKeyboard Error:  Point < 0 in search_for_text!\n");
          fflush(Q);
          return(0);
     } /* End if */
     
     text = gtk_editable_get_chars(GTK_EDITABLE(GUI.main_output_text),
                                   point, total_len);
     len = strlen(text);

     if(!search_for)
          return(0);

     /* We want to grab the text between point and len and see if search_for
      * is in there anywhere.
      */
     for(offset=0; offset<=(len - target_len); offset++)
     {
          ptr = &text[offset];

          /* I'd rather use something different than g_strncasecmp, but
           * I checked out the code in glib, and it's reasonably good.  At
           * least, it will work fine for any amount of data that's manageable
           * as a document you'd be editing.
           */
          if(ptr && g_strncasecmp(ptr, search_for, target_len) == 0)
          {
               /* Point is how many bytes into the whole widget buffer we
                * are, and offset is how many bytes into our selection we
                * are.  So if you add them, you get where we are overall
                */
               result = offset + point;

               if(result <= total_len)
               {
                    sprintf(mesg_buffer,
                            "Text found at point %d:  Scrolling there...\n",
                            result);

                    chocolate(mesg_buffer);

                    SET_POINT(result);
                    update_screen_position();
                    gtk_editable_select_region(
                         GTK_EDITABLE(GUI.main_output_text),
                         result, (result + target_len));
                    
                    g_free_(text);

                    return(result);
               } /* End if */ 
               else 
               {
                    fprintf(Q,"GTKeyboard Internal Error: (search_for_text) ");
                    fprintf(Q,"Illegal length found: %ud\n", result);
                    fflush(Q);

                    g_free_(text);

                    return(0);
               } /* End else */
          } /* End if */
     } /* End for */
     
     update_screen_position();
     return(0);    /* Not found */
} /* End search_for_text */

/* EOF */

