/* field.c */


/*
 * Field Widget - a single line type-in text field.
 */


#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/keysym.h>
#include "lui.h"
#include "field.h"



#define LMARGIN 2


static void copy_string( char *dst, char *src, int max )
{
   int i;

   for (i=0; i<max && (dst[i]=src[i]); i++)
     ;
}



/*
 * Adjust the field's scroll field according to the field width, length of
 * text and cursor position.
 */
static void compute_scroll( LUI_FIELD *field )
{
   if (strlen(field->text) <= field->columns ||
       strlen(field->text) >= field->len_limit) {
      /* whole string is visible, don't scroll */
      field->scroll = 0;
   }
   else {
      /* scroll so cursor is visible */
      if (field->curpos < field->scroll) {
         /* left */
         field->scroll = field->curpos;
      }
      else if (field->curpos-field->scroll > field->columns) {
         /* right */
         field->scroll = field->curpos - field->columns;
      }
      else {
         /* no change */
      }
   }

   if (field->scroll==field->curpos && field->scroll>0) {
      field->scroll -= 1;
   }
}




void draw_field( LUI_FIELD *field )
{
   int x = LUI_Border;
   int y = LUI_Border;
   int texty;

/*
   XFillRectangle( LUI_Display, field->window, LUI_GC_gray,
             x, y, field->width-2*LUI_Border, field->height-2*LUI_Border );
*/
   XFillRectangle( LUI_Display, field->window, LUI_GC_white,
             x, y, field->width-2*LUI_Border, field->height-2*LUI_Border );

   texty = LUI_Border + (field->height - 2*LUI_Border - LUI_Font_height) / 2
           + LUI_Font_yoff;

   assert( field->scroll <= strlen(field->text) );
   XDrawString( LUI_Display, field->window, LUI_GC_black,
                x+LMARGIN, texty,
                field->text + field->scroll,
                strlen(field->text) - field->scroll );

   LUI_DrawFrame( field->window, 0,0, field->width, field->height,
                  LUI_Border, 0 );

   if (field->editing) {
      /* draw cursor */
      int dir, ascent, descent, width, height;
      XCharStruct overall;

      XTextExtents( LUI_Font,
                    field->text + field->scroll,     /* the text */
                    field->curpos - field->scroll,   /* text length */
                    &dir, &ascent, &descent, &overall);
      width = overall.width;
      height = ascent + descent;
      x = LUI_Border + LMARGIN + width;
      y = texty - LUI_Font_yoff - 1;
      XDrawLine( LUI_Display, field->window, LUI_GC_black,
                 x, y, x, y+height+2 );
      XDrawLine( LUI_Display, field->window, LUI_GC_black,
                 x+1, y, x+1, y+height+2 );
      XDrawLine( LUI_Display, field->window, LUI_GC_black,
                 x-1, y, x+2, y );
      XDrawLine( LUI_Display, field->window, LUI_GC_black,
                 x-1, y+height+2, x+2, y+height+2 );
   }
}



/*
 * Set the current text of a field.
 */
void LUI_FieldSetText( LUI_FIELD *field, char *text )
{
   copy_string( field->text, text, MAX_FIELD );
   field->text[MAX_FIELD-1] = 0;
   field->curpos = strlen( field->text );
   compute_scroll( field );
   draw_field( field );
}


/*
 * Set the current text of a field as a double.
 */
void LUI_FieldSetDouble( LUI_FIELD *field, double x )
{
   sprintf( field->text, "%g", x );
   field->curpos = strlen( field->text );
   compute_scroll( field );
   draw_field( field );
}

void LUI_FieldSetNotDouble( LUI_FIELD *field, double x )
{
   sprintf( field->text, "%.3f", x );
   field->curpos = strlen( field->text );
   compute_scroll( field );
   draw_field( field );
}



/*
 * Set the current text of a field as an integer.
 */
void LUI_FieldSetInt( LUI_FIELD *field, int i )
{
   sprintf( field->text, "%d", i );
   field->curpos = strlen( field->text );
   compute_scroll( field );
   draw_field( field );
}



/*
 * Return the current contests of a field as a string.
 */
void LUI_FieldGetText( LUI_FIELD *field, char *text )
{
   copy_string( text, field->text, MAX_FIELD );
}



/*
 * Return the current contents of a field as a double.
 */
double LUI_FieldGetDouble( LUI_FIELD *field )
{
   return atof( field->text );
}



/*
 * Return the current contents of a field as an integer.
 */
int LUI_FieldGetInt( LUI_FIELD *field )
{
   return atoi( field->text );
}



/*
 * Specify the callback for a field widget.  It will be called whenever
 * the field's text has been modified.
 * Input:  field - which field widget
 *         callback - pointer to callback function declared as:
 *                      int callback( LUI_FIELD *field, char *text )
 */
void LUI_FieldCallback( LUI_FIELD *field, int (* callback)() )
{
   field->callback = callback;
   field->context_index = context_index;
}


static void keypress( LUI_FIELD *field, XEvent *event )
{
   KeySym keysym;
   XComposeStatus compose;
   char buffer[100];
   int len, count;

   if (!field->editing)
      return;

   count = XLookupString( &event->xkey, buffer, 100, &keysym, &compose );

   len = strlen( field->text );


   if (keysym==XK_Left) {
      if (field->curpos>0) {
         field->curpos--;
      }
      compute_scroll( field );
   }
   else if (keysym==XK_Right) {
      if (field->curpos<len) {
         field->curpos++;
      }
      compute_scroll( field );
   }
   else if (keysym==XK_BackSpace) {
      if (field->curpos>0) {
         char *ch = field->text + field->curpos - 1;
         while (*ch) {
            *ch = *(ch+1);
            ch++;
         }
         field->curpos--;
         field->modified = 1;
         if (field->scroll>0) {
            field->scroll--;
         }
/*         compute_scroll( field );*/
      }
   }
   else if (keysym==XK_Delete) {
      if (field->curpos < len) {
         char *ch = field->text + field->curpos;
         while (*ch) {
            *ch = *(ch+1);
            ch++;
         }
         field->modified = 1;
         compute_scroll( field );
      }
   }
   else if (keysym==XK_Return) {
/*      field->editing = 0;*/
      field->modified = 0;
      if (field->callback) {
         (*field->callback)( field, field->text );
      }
   }
   else if (keysym==XK_Tab) {
      if (field->warp_to) {
         field->editing = 0;
         field->modified = 0;
         if (field->callback) {
            (*field->callback)( field, field->text );
         }
         XWarpPointer( LUI_Display, None, field->warp_to->window,
                       0,0, 0,0, event->xkey.x, event->xkey.y );
      }
   }
   else if (count==1 && len < field->len_limit) {
      /* insert */
      int i;
/*
      char *ch = field->text + len;
      for (i=0;i<=len-field->curpos;i++) {
         *(ch+1) = *ch;
         ch--;
      }
      *ch = buffer[0];
*/
      for (i=len; i>=field->curpos; i--) {
         field->text[i+1] = field->text[i];
      }
      field->text[field->curpos] = buffer[0];

      field->curpos++;
      field->modified = 1;
      compute_scroll( field );
   }

   draw_field( field );
}


/*
 * This is called when the mouse button is clicked in the field.  Use
 * the value of x (in pixels) to position the cursor.
 */
static void click( LUI_FIELD *field, int x )
{
   int len, pos;

   len = strlen( field->text );

   field->curpos = len;
   for (pos=0;pos<=len;pos++) {
      int dir, ascent, descent, width;
      XCharStruct overall;

      XTextExtents( LUI_Font,
                    field->text + field->scroll, pos,
                    &dir, &ascent, &descent, &overall);
      width = overall.width;
      if (LUI_Border + LMARGIN + width > x) {
         if (pos>0)
           field->curpos = field->scroll + pos - 1;
         else 
           field->curpos = field->scroll + 0;
         break;
      }
   }

   field->editing = 1;
   field->modified = 0;
   draw_field( field );
}



static int field_process( LUI_FIELD *field, XEvent *event )
{

   switch (event->type) {
      case KeyPress:
         keypress( field, event );
         break;

      case ButtonPress:
         click( field, event->xbutton.x );
         break;

      case EnterNotify:
         field->editing = 1;
         field->modified = 0;
         draw_field( field );
         break;

      case LeaveNotify:
         field->editing = 0;
         draw_field( field );
         if (field->modified && field->callback) {
            (*field->callback)( field, field->text );
         }
         field->modified = 0;
         break;

      case Expose:
         draw_field( field );
         break;

      default:
         ;
   }
   return 1;
}



/*
 * Destroy a type-in field.
 */
void LUI_FieldDestroy( LUI_FIELD *field )
{
   LUI_EventRemove( field->window );
   XDestroyWindow( LUI_Display, field->window );
   free( field );
}



LUI_FIELD *LUI_FieldCreate( Window parent, int x, int y,
                            int width, int height )
{
   LUI_FIELD *field;

   LUI_LayoutCheck( &x, &y, &width, &height );

   field = (LUI_FIELD *) malloc( sizeof(LUI_FIELD) );
   if (!field) {
      return NULL;
   }

   field->window = XCreateSimpleWindow( LUI_Display, parent,
                                        x, y, width-2, height-2,
                                        1, LUI_Color_black, LUI_Color_gray );

   LUI_EventAdd2( field->window, 
                  KeyPressMask | ExposureMask | ButtonPressMask
                  | EnterWindowMask | LeaveWindowMask,
                  (LUI_FNCP) field_process, field );

   XMapWindow( LUI_Display, field->window );

   field->x = x;
   field->y = y;
   field->width = width-2;
   field->height = height-2;

   field->text[0] = 0;
   field->curpos = 0;
   field->columns = (width-2) / LUI_Font_width - 1;
   field->scroll = 0;
   field->editing = 0;
   field->modified = 0;
   field->callback = NULL;
   field->warp_to = NULL;
   field->len_limit = 10000;
   LUI_AddWidgetToWindow( parent, field, (LUI_FNCP) LUI_FieldDestroy );
   return field;
}



/*
 * Link two field widgets together so that pressing TAB in one moves the
 * input focus to the next field.
 */
void LUI_FieldLink( LUI_FIELD *from, LUI_FIELD *to )
{
   from->warp_to = to;
}





syntax highlighted by Code2HTML, v. 0.9.1