
/*
 * gnoclText.c --
 *
 * This file implements the text widget
 *
 * Copyright (c) 2001 P.G. Baum
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

/*
   History:
   2001-06: Begin of developement
 */

#include "gnocl.h"
#include <string.h>
#include <assert.h>


typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   Gnocl_BoolBool    editable;
   Gnocl_BoolBool    wordWrap;
   Gnocl_BoolBool    lineWrap;
   Gnocl_ObjBool     scrollbar;
   /* Gnocl_ObjBool  defaulFont */
} TextOptions;

static const GnoclWidgetOptions widgetOptions[] = 
{
   GNOCL_STD_OPTIONS_NAMES( TextOptions ),

   { "-editable", GNOCL_BOOL, GNOCL_OFFSET( TextOptions, editable ) },
   { "-wordWrap", GNOCL_BOOL, GNOCL_OFFSET( TextOptions, wordWrap ) },
   { "-lineWrap", GNOCL_BOOL, GNOCL_OFFSET( TextOptions, lineWrap ) },
   { "-scrollbar", GNOCL_OBJ, GNOCL_OFFSET( TextOptions, scrollbar ) },
   { NULL, 0, 0 }
};

typedef struct
{
   char        *name;
   GtkText     *text;
   GtkScrolledWindow *scrollWin;
   Tcl_Interp  *interp;
} TextParams;

static void freeOpts( TextOptions *p )
{
   GNOCL_STD_OPTIONS_FREE( p );

   GNOCL_FREE_TCL_OPT( p->scrollbar );
}


#if 0
static char *textTraceFunc( ClientData data,
      Tcl_Interp *interp, char *name1, char *name2, int flags)
{
   TextParams *p = (TextParams *)data;
   /* 
   Tcl_Obj *obj = Tcl_GetVar2Ex( interp, name1, name2, 0 ); 
   if( obj != NULL )
      gtk_text_set_text( GTK_ENTRY( p->text ), Tcl_GetString( obj ) );
   */

   char *txt = name1 ? Tcl_GetVar2( interp, name1, name2, 0 ): NULL;
   assert( p->interp == interp );

   if( txt != NULL )
      gtk_text_set_text( GTK_ENTRY( p->text ), txt );

   return NULL;
}

static void textChangedFunc ( GtkWidget *text, gpointer data )
{
   TextParams *p = (TextParams *)data;
   char *val = gtk_text_get_text( GTK_ENTRY( p->text ) ); 
   Tcl_SetVar( p->interp, p->variable, val, 0 );
}
#endif

static void textDestroyFunc ( GtkWidget *text, gpointer data )
{
   TextParams *p = (TextParams *)data;

   gnoclForgetWidgetFromName( p->name );
   Tcl_DeleteCommand( p->interp, p->name );

/*
   if( p->variable != NULL )
      Tcl_UntraceVar( p->interp, p->variable, TCL_TRACE_WRITES, 
            textTraceFunc, p );

   g_free( p->variable );
*/
   g_free( p->name );
   g_free( p );
}

static int textConfigure( Tcl_Interp *interp, TextParams *para,
      TextOptions *popt )
{
   GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( para->text ) );

#if 0
   if( popt->variable.changed )
   {
      if( para->variable )
      {
         Tcl_UntraceVar( para->interp, para->variable, TCL_TRACE_WRITES, 
               textTraceFunc, para );
         g_free( para->variable );
      }
      para->variable = popt->variable.val;
      popt->variable.val = NULL;

      /*
      if( para->variable == NULL )
         para->variable = g_memdup( para->name, strlen( para->name ) + 1 );
      */

      if( para->variable != NULL )
      {
         if( Tcl_TraceVar( interp, para->variable, TCL_TRACE_WRITES, 
               textTraceFunc, para ) != TCL_OK )
            return TCL_ERROR;
      }
   }

   if( popt->maxLength.changed )
      gtk_text_set_max_length( para->text, popt->maxLength.val );
#endif   

   if( popt->editable.changed )
      gtk_text_set_editable( para->text, popt->editable.val );

   if( popt->wordWrap.changed )
      gtk_text_set_editable( para->text, popt->wordWrap.val );

   if( popt->lineWrap.changed )
      gtk_text_set_editable( para->text, popt->lineWrap.val );
   
   if( popt->scrollbar.changed )
   {
      GtkPolicyType hor, vert;
      if( gnoclGetScrollbarPolicy( interp, popt->scrollbar.val, 
            &hor, &vert ) != TCL_OK )
         return TCL_ERROR;

      gtk_scrolled_window_set_policy( para->scrollWin, hor, vert );

   #if 0
      if( popt->scrollbar.val )
      {
         if( para->box == NULL )
         {
            GtkWidget *vscrollbar = gtk_vscrollbar_new( para->text->vadj );
            /* use gtk_text_set_adjustments () ? */
            para->box =  GTK_BOX( gtk_hbox_new( 0, 0 ) );
            gtk_box_pack_start( para->box, GTK_WIDGET( para->text ), 
                  1, 1, 0 );
            gtk_box_pack_start( para->box, vscrollbar, 0, 0, 0 );
         }
      }
      /* FIXME: what to do here? ->repack */
   #endif
   }

   /* textTraceFunc( para, interp, para->variable, NULL, 0 ); */

   return TCL_OK;
}

#if 0
static int posToIndex( Tcl_Interp *interp, Tcl_Obj *obj, GtkText *text, 
      int *pidx )
{
   if( Tcl_GetIntFromObj( NULL, obj, pidx ) != TCL_OK )
   {
      const char *txt = Tcl_GetString( obj );
      int idx;
      const char *last;
      if( strncmp( txt, "end", 3 ) == 0 )
      {
         idx = gtk_text_get_length( text );
         last = txt + 3;
      }
      else if( strncmp( txt, "cursor", 6 ) == 0 )
      {
         idx = gtk_text_get_point( text );
         last = txt + 6;
      }
      else if( strncmp( txt, "selectionStart", 14 ) == 0 )
      {
         idx = GTK_EDITABLE( text )->selection_start_pos;
         last = txt + 14;
      }
      else if( strncmp( txt, "selectionEnd", 12 ) == 0 )
      {
         idx = GTK_EDITABLE( text )->selection_end_pos;
         last = txt + 12;
      }
      else
      {
         Tcl_AppendResult( interp, "unknown index \"", txt, 
               "\", must be one of end, cursor, selectionStart, " 
               "or selectionEnd", NULL );
         return TCL_ERROR;
      }
      /* pos[+-]offset */
      if( *last == '+' || *last == '-' )
      {
         int offset;
         ++last;
         if( sscanf( last, "%d", &offset ) != 1 )
         {
            Tcl_AppendResult( interp, "invalid offset \"", last + 1, 
                  "\"", NULL );
            return TCL_ERROR;
         }
         if( *last == '+' )
            idx += offset;
         else
            idx -= offset;
         while( isdigit( *last ) )
            ++last;
      }
      if( *last )
      {
         Tcl_AppendResult( interp, "invalid appendix \"", last, 
               "\"", NULL );
         return TCL_ERROR;
      }
      *pidx = idx;
   }
   return TCL_OK;
}
#endif

static int textInsert( TextParams *para, Tcl_Interp *interp, int objc, 
      Tcl_Obj * const objv[] )
{
   /* widget insert pos text ?-option val ...? */
   typedef struct
   {
      Gnocl_ObjBool font;
      Gnocl_ObjBool foreground;
      Gnocl_ObjBool background;
   } TextInsertOptions;

   static const GnoclWidgetOptions insertOptions[] = {
      { "-font", GNOCL_OBJ, GNOCL_OFFSET( TextInsertOptions, font ) },
      { "-foreground", GNOCL_OBJ, GNOCL_OFFSET( TextInsertOptions, 
            foreground ) },
      { "-background", GNOCL_OBJ, GNOCL_OFFSET( TextInsertOptions, 
            background ) },
      { NULL, 0, 0 }
   };

   TextInsertOptions opt;
   GdkFont *font = NULL;
   GdkColor foreground, background;
   int      index, oldIndex;


   if( objc < 4 )
   {
      Tcl_WrongNumArgs( interp, 2, objv, "position text ?option val ...?" );
      return TCL_ERROR;
   }

   if( gnoclEditablePosToIndex( interp, objv[2], GTK_EDITABLE( para->text ), 
         &index ) != TCL_OK )
      return TCL_ERROR;

   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 3,
         insertOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;
   
   if( opt.font.changed )
   {
      if( gnoclGetGdkFont( interp, opt.font.val, &font ) != TCL_OK )
         return TCL_ERROR;
   }
   else     /* TODO: how to get the default font? */
      font = gdk_font_load( 
            "-adobe-courier-bold-r-normal-*-*-140-*-*-m-*-iso8859-1" );

   if( opt.foreground.changed )
   {
      if( gnoclGetGdkColor( interp, opt.foreground.val, &foreground, NULL ) )
         return TCL_ERROR;
   }
   else
      gdk_color_black( gtk_widget_get_colormap( GTK_WIDGET( para->text ) ), 
            &foreground );

   if( opt.background.changed )
   {
      if( gnoclGetGdkColor( interp, opt.background.val, &background, NULL ) )
         return TCL_ERROR;
   }
   else
      gdk_color_white( gtk_widget_get_colormap( GTK_WIDGET( para->text ) ), 
            &background );

   oldIndex = gtk_text_get_point( para->text );
   gtk_text_set_point( para->text, index );

   gtk_text_insert( para->text, font, &foreground, &background,
      gnoclGetString( objv[3] ), -1 );

   gtk_text_set_point( para->text, oldIndex );

   return TCL_OK;
}

static int textFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES,
         GNOCL_EDITABLE_CMD_NAMES,
         "configure", 
         "setPoint", "getPoint", "getPosition", "insert",
         #if 0
         /* "remove" means "delete", but this is already used by
            the widget std commands */
         , "remove", "select", 
         "cut", "copy", "paste",
         "getChars",
         #endif
         NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS, GNOCL_EDITABLE_CMD_ENUMS,
         ConfigureIdx,
         SetPointIdx, GetPointIdx, GetPositionIdx, InsertIdx
         /*
         RemoveIdx, SelectIdx, CutIdx, CopyIdx, PasteIdx, GetCharsIdx 
         */
         };
   TextParams *para = (TextParams *)data;
   GtkWidget *text = GTK_WIDGET( para->text );
   int idx;

   if( Tcl_GetIndexFromObj( interp, objv[1], cmds, "command", 
         TCL_EXACT, &idx ) != TCL_OK )
      return TCL_ERROR;

   switch( idx )
   {
      GNCOL_STD_CMD_CASE( interp, objc, objv, text );
            break;
      GNCOL_EDITABLE_CMD_CASE( idx, interp, objc, objv, GTK_EDITABLE( text ) );
            break;

      case ConfigureIdx:
            {
               TextOptions opt;
               int ret = TCL_ERROR;

               memset( &opt, 0, sizeof( opt ) );
               if( gnoclParseAllWidgetOpts( interp, objc, objv, 1,
                     widgetOptions, (char *)&opt ) == TCL_OK )
                  ret = textConfigure( interp, para, &opt );

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case SetPointIdx:
            {
               int index;
               /* text setPoint index */
               if( objc != 3 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "index" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], 
                     GTK_EDITABLE( para->text ), &index ) != TCL_OK )
                  return TCL_ERROR;

               gtk_text_set_point( para->text, (guint)index );
               Tcl_SetObjResult( interp, Tcl_NewIntObj( index ) );
            }
            break;
      case GetPointIdx:
            {
               /* text getPoint */
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "" );
                  return TCL_ERROR; 
               }
               Tcl_SetObjResult( interp, Tcl_NewLongObj( 
                     gtk_text_get_point( para->text )) );
            }
            break;
      case GetPositionIdx:
            {
               int index;
               /* text getPosition pos */
               if( objc != 3 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "position" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], 
                     GTK_EDITABLE( para->text ), &index ) != TCL_OK )
                  return TCL_ERROR;
               Tcl_SetObjResult( interp, Tcl_NewIntObj( index ) );
            }
            break;
      case InsertIdx:
            return textInsert( para, interp, objc, objv );
#if 0
      case GetLengthIdx:
            {
               /* text getLength */
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "" );
                  return TCL_ERROR; 
               }
               Tcl_SetObjResult( interp, Tcl_NewIntObj( 
                     gtk_text_get_length( para->text )) );
            }
            break;

      case RemoveIdx:
            {
               int startIndex, endIndex;
               /* text remove startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "startIndex ?endIndex?" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], para->text, 
                     &startIndex ) != TCL_OK )
                  return TCL_ERROR;
               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], para->text, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = startIndex + 1;
                  
               gtk_editable_delete_text( GTK_EDITABLE( para->text ),
                     startIndex, endIndex );
            }
            break;
      case SelectIdx:
            {
               int startIndex, endIndex;
               /* text remove startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "startIndex ?endIndex?" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], para->text, 
                     &startIndex ) != TCL_OK )
                  return TCL_ERROR;
               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], para->text, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = startIndex + 1;
                  
               if( startIndex >= endIndex )
                  gtk_editable_delete_selection( GTK_EDITABLE( para->text ) );
               else
                  gtk_editable_select_region( GTK_EDITABLE( para->text ),
                        startIndex, endIndex );
            }
            break;
      case CutIdx:
      case CopyIdx:
      case PasteIdx:
            {
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "" );
                  return TCL_ERROR; 
               }
               switch( idx )
               case CutIdx:
                     gtk_editable_cut_clipboard( GTK_EDITABLE( para->text ) ); 
                     break;
               case CopyIdx:
                     gtk_editable_copy_clipboard( GTK_EDITABLE( para->text ) ); 
                     break;
               case PasteIdx:
                     gtk_editable_paste_clipboard( GTK_EDITABLE( 
                           para->text ) ); 
                     break;
            }
            break
      case GetCharsIdx:
            {
               int startIndex, endIndex;
               char *txt;
               /* text getChars startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "startIndex ?endIndex?" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], para->text, 
                     &startIndex ) != TCL_OK )
                  return TCL_ERROR;
               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], para->text, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = startIndex + 1;
               txt = gtk_editable_get_chars( GTK_EDITABLE( para->text ),
                  startIndex, endIndex );
               Tcl_SetObjResult( interp, Tcl_NewStringObj( txt, -1 ) );
            }
            break;
#endif
   }

   return TCL_OK;
}

int gnoclTextCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{

   TextOptions opt;
   TextParams *para;
   GtkWidget *widget;

   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 0,
         widgetOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;

   para = g_new( TextParams, 1 );
   para->text = GTK_TEXT( gtk_text_new( NULL, NULL ) );
   para->name = gnoclGetAutoWidgetId();
   para->interp = interp;
   /* para->variable = NULL; */

   para->scrollWin = GTK_SCROLLED_WINDOW( 
         gtk_scrolled_window_new( NULL, NULL ) );
   gtk_scrolled_window_set_policy( para->scrollWin, 
         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
   gtk_container_add( GTK_CONTAINER( para->scrollWin ), 
         GTK_WIDGET( para->text ) );

   if( textConfigure( interp, para, &opt ) != TCL_OK )
   {
      freeOpts( &opt );
      /* FIXME: g_free para->name ... */
      gtk_widget_destroy( GTK_WIDGET( para->scrollWin ) );
      gtk_widget_destroy( GTK_WIDGET( para->text ) );
      return TCL_ERROR;
   }
   freeOpts( &opt );

   widget = GTK_WIDGET( para->scrollWin );
/*
   gtk_signal_connect( GTK_OBJECT( para->text ), "changed", 
         GTK_SIGNAL_FUNC( textChangedFunc ), para );
*/
   gtk_signal_connect( GTK_OBJECT( widget ), "destroy", 
         GTK_SIGNAL_FUNC( textDestroyFunc ), para );

   gnoclMemNameAndWidget( para->name, widget );

   gtk_widget_show_all( GTK_WIDGET( widget) );

   Tcl_CreateObjCommand( interp, para->name, textFunc, para, NULL );

   Tcl_SetObjResult( interp, Tcl_NewStringObj( para->name, -1 ) );

   return TCL_OK;
}

