/* misc_cursor.c
 * For use with GTKeyboard
 * Written by David Allen, s2mdalle@titan.vcu.edu
 * http://opop.nols.com/
 *
 * This file just does some basic cursor movement, nothing fancy - haven't
 * finished up and down yet because the way I currently figure it, it's going
 * to depend on how many characters/line in the text widget which could be
 * changing size...so I'll do that a bit down the line
 */
/* 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.
 */

/* include/misc_cursor.h is included through master.h - it contains 
 * the prototypes, and #defines needed for this file
 */
#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>

/* Functions */
void toggle_numlock(GtkWidget *emitter, gpointer data)
{
     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Num_Lock);

     if(options.NUMLOCK)
     {
          options.NUMLOCK = 0;
          chocolate("NUMLOCK is OFF\n");
     } /* End if */
     else
     {
          options.NUMLOCK = 1;
          chocolate("NUMLOCK is ON\n");
     } /* End else */

} /* End toggle_numlock */

void toggle_wordwrap(void)
{
     if(options.WORD_WRAP)
     {
	  options.WORD_WRAP = 0;
	  gtk_text_set_word_wrap(GTK_TEXT(GUI.main_output_text), FALSE);
	  chocolate("Word wrap is now OFF\n");
     } /* End if */
     else
     {
	  options.WORD_WRAP = 1;
	  gtk_text_set_word_wrap(GTK_TEXT(GUI.main_output_text), TRUE);
	  chocolate("Word wrap is now ON\n");
     } /* End else */
} /* End toggle_wordwrap */

void delete_line(void)
{
     gint point1 = 0;
     gint point2 = 0;

     if(options.redirect_window_name)
     {
	  /* If we don't do this, then goto_bol and goto_eol will screw
	   * up and send characters to the foreign window, as well as the
	   * fact that this function won't do squat 
	   */
	  annoying_popup("Sorry, this doesn't work in foreign windows.");
	  return;
     } /* End if */

     goto_bol();            /* Go to the beginning of the line we're on */
     point1 = GET_POINT;
     goto_eol();            /* Go to the end of the line we're on */
     point2 = GET_POINT;
     
     if((point1 == point2) || (point1<0) || (point2<0) || (point2 < point1))
     {
	  gtkeyboard_error(1,"Sorry, can't delete this line.\n");
	  MEM_REPORT;
	  return;
     } /* End if */

     /* Delete between the beginning and the end of the line */
     /* We're already at the end of the line, so delete backwards. */
     gtk_text_backward_delete(GTK_TEXT(GUI.main_output_text), 
			      (guint)(point2-point1));
} /* End delete_line */

void cursor_left(void)
{
     int pos = 0;
     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Left);
     else
     {
	  pos = GET_POINT;
	  pos--;
	  
	  FREEZE(GUI.main_output_text);

	  /* Bounds checking on setting the point */
	  if(pos >= 0)
	       SET_POINT(pos);

	  THAW(GUI.main_output_text);
     } /* End else */
} /* End cursor left */

/* Updates the "view" of the GtkText widget - this is an ugly hack,
 * but the widget doesn't provide a better way of doing it.
 */
void update_screen_position(void)
{
     /* Save the modified state - we're going to insert and then
      * delete a character - the modified bit will change, but since we're
      * having no net change on the document, change it back to what it 
      * originally was
      */
     int modified = options.document_modified;
     
     vanilla(" ");     /* Insert one space */
     /* Delete one character */
     gtk_text_backward_delete(GTK_TEXT(GUI.main_output_text), 1);

     options.document_modified = modified;
     return;
} /* End update_screen_position() */

/* Move the cursor up one */
void cursor_up(void)
{
     int start, finish, bol, bol2, diff;
     char buffer[1024];

     start = finish = bol = bol2 = diff = 0;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Up);
     else	  /* Do it within the text widget */
     {
	  start = GET_POINT;             /* Here and now */
	  goto_bol();
	  bol = GET_POINT;               /* Beginning of this line */
	  diff = start - bol;            /* Here and now is diff letters from
					  * the beginning of here and now's
					  * line
					  */
	  
	  cursor_left();                 /* Up one line */
	  cursor_left();
	  goto_bol();                    
	  bol2 = GET_POINT;              /* Beginning of prev line */
	  
	  finish = bol2 + diff;          /* Diff chars from beg of prev line */
	  
	  /* Bounds checking */
	  if(finish < GET_LENGTH && finish >= 0)
	  {
	       sprintf(buffer,"UP => POINT |%d->%d|\n",start,finish);
	       SET_POINT(finish);
	       gtkeyboard_message(1,buffer);
	  } /* End if */
	  else
	       gtkeyboard_error(1,"Can't go UP one line.\n");
     } /* End else */
} /* End cursor_up */

/* Move the cursor down one */
void cursor_down(void)
{
     /* End of first line, beginning of second line, beginning of first line 
      * are the messed up varnames 
      */
     int now, finish, eofl, bosl, bofl, diff, saved;
     char buffer[1024];

     saved = options.document_modified;

     now = finish = eofl = bosl = bofl = 0;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Down);
     else
     {
	  now = GET_POINT;
	  goto_bol();
	  bofl = GET_POINT;
	  diff = now - bofl;       /* Now is diff chars from bofl */
	  goto_eol();
	  eofl = GET_POINT;
	  cursor_right();
	  cursor_right();
	  goto_bol();
	  bosl = GET_POINT;
	  
	  finish = bosl + diff;

	  if(finish < GET_LENGTH && finish >= 0)
	  {
	       sprintf(buffer,"DOWN => POINT |%d->%d|\n",now, finish);
	       SET_POINT(finish);
	       gtkeyboard_message(1,buffer);
	       return;
	  } /* End if */
	  else
	       gtkeyboard_error(1,"Can't go DOWN one line.\n");
     } /* End else */

     options.document_modified = saved;
} /* End cursor_down */

void cursor_right(void)
{
     int pos = 0, 
	  l  = 0;
     int saved = options.document_modified;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Right);
     else
     {
	  pos = GET_POINT;
	  l   = GET_LENGTH;
	  ++pos;

	  if(pos <= l)	  
	       gtk_text_set_point(GTK_TEXT(GUI.main_output_text), pos);
	  
	  /* If position is already at the end of the file, don't do anything
	   * because if you do GTK spouts a message and we don't want that, now
	   * do we?
	   */
     } /* End else */

     options.document_modified = saved;
} /* End cursor_right */

void cursor_left_x_times(size_t input)
{
     register int y=0;
     for(y=0; y<input; y++)
	  cursor_left();
} /* End cursor_left_x_times() */

void cursor_right_x_times(size_t input)
{
     register int y=0;
     for(y=0; y<input; y++)
	  cursor_right();
} /* End cursor_right_x_times() */

/* Had to write these silly functions because itemfactories won't let you use
 * macros in them.
 */
void goto_eof(void)       /* Goto end of file */
{
     int saved = options.document_modified;
     gtk_text_set_point(GTK_TEXT(GUI.main_output_text), 
			gtk_text_get_length(GTK_TEXT(GUI.main_output_text)));
     options.document_modified = saved;
} /* End goto_eof() */

void goto_bof(void)       /* Go to beginning of file */
{
     int saved = options.document_modified;
     gtk_text_set_point(GTK_TEXT(GUI.main_output_text), 0);
     options.document_modified = saved;
} /* End goto_bof() */

void goto_eol(void)         /* Go to end of line */
{
     int x,y;
     gchar *foobar;
     char  *pc;
     int saved = options.document_modified;

     x = y = 0;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_End);
     else
     {
	  x = GET_POINT;
	  y = GET_LENGTH;
	  if (y<=0)
	  {
	       gtkeyboard_error(2,"Can't move to the end of line.\n",
				"No text to manipulate.\n");
	       return;
	  } /* End if */

	  /* Keep advancing while we dont have a linebreak/CR or we're not at
	   * the
	   * end of the file
	   */
	  if(y>0 && x<y)
	  {
	       foobar = gtk_editable_get_chars(GTK_EDITABLE
					       (GUI.main_output_text),
					       (gint) x, (gint)(y));
	       if (foobar == (gchar *)NULL)
	       {
		    gtkeyboard_error(1,"Text==NULL...no text found?\n");
		    return ;
	       } /* End if */
	     
	       pc = foobar;
	       
	       while(*pc!='\n' && *pc!='\r' && x<y  && *pc != '\0' )
	       {
		    x++;
		    pc++;
	       } /* End while */
	     
	       /* Set the point to the end if it's a valid value */
	       if(x<=y)
	       {
		    SET_POINT(x);
	       } /* End if */
	       else
		    gtkeyboard_error(1,"Cursor set to EOF - no EOL found.\n");

	       g_free_(foobar);
	  } /* End else */
	  else 
	       gtkeyboard_error(1,"Can't go to the end of this line\n");
     } /* End else */

     options.document_modified = saved;
} /* end goto_eol */

void goto_bol(void)          /* Go to beginning of line */
{
     gint x,y;
     gchar *foobar;
     char *pc;
     int saved = options.document_modified;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Home);
     else
     {
	  x = gtk_text_get_point(GTK_TEXT(GUI.main_output_text));
	  if(x<=0)
	       chocolate("Can't go to beginning of line.\n");
	  else
	  {
	       y = gtk_text_get_length(GTK_TEXT(GUI.main_output_text));
	       foobar = gtk_editable_get_chars(GTK_EDITABLE
					       (GUI.main_output_text),
					       (gint) 0 , y );
	       if ( (gchar *) NULL == foobar )
	       {
		    chocolate("ERROR:  Can't get content of line.\n");
		    return ;
	       } /* End if */
	       pc = foobar + x ;
	       while(*pc!='\n' && *pc!='\r' && x>0 && x<=y)
	       {
		    x--;
		    pc-- ;
	       } /* End while */
	       
	       /* If we ended on a linebreak, go after not before */
	       if(*pc=='\n' || *pc=='\r')
		    x++;
	       
	       g_free_(foobar);
	       
               /* Set the point to the end if it's a valid value */
	       if(x>=0 && x<=y)
		    gtk_text_set_point(GTK_TEXT(GUI.main_output_text),x);
	       else
		    chocolate("Cursor not set - no last line end found.\n");
	  } /* End else */
     } /* End else */

     options.document_modified = saved;

} /* End goto_bol */

/* Works just like the DEL key */
void delete_key(GtkWidget *w, gpointer data)
{
     GtkWidget *foo = (GtkWidget *)data;

     gint x = GET_POINT;
     gint y = GET_LENGTH;

     if(options.redirect_window_name)
	  send_redirect_a_keysym(XK_Delete);
     else
     {
	  /* Check bounds */
	  if(x<y)
	       gtk_text_forward_delete(GTK_TEXT(foo), (guint) 1);
          
          options.document_modified = 1;
     } /* End else */
} /* End delete_key */
