
/*
 * gnoclCombo.c --
 *
 * This file implements the combo 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:
        09: new command getValue, setValue
   2001-03: Begin of developement
 */

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

typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   Gnocl_StringBool variable;
   Gnocl_ListBool   strings;
   Gnocl_BoolBool   mustMatch;
   Gnocl_BoolBool   okIfEmtpty;
   Gnocl_BoolBool   caseSensitve;
   Gnocl_IntBool    maxLength;
   Gnocl_BoolBool   editable;
} ComboOptions;

typedef struct 
{
   char       *name;
   char       *variable;
   GtkCombo   *widget;
   Tcl_Interp *interp;
} ComboParams;

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

   { "-variable", GNOCL_STRING, GNOCL_OFFSET( ComboOptions, variable ) },
   { "-strings", GNOCL_LIST, GNOCL_OFFSET( ComboOptions, strings ) },
   { "-mustMatch", GNOCL_BOOL, GNOCL_OFFSET( ComboOptions, mustMatch ) },
   { "-okIfEmtpty", GNOCL_BOOL, GNOCL_OFFSET( ComboOptions, okIfEmtpty ) },
   { "-caseSensitve", GNOCL_BOOL, GNOCL_OFFSET( ComboOptions, 
         caseSensitve ) },
   { "-maxLength", GNOCL_INT, GNOCL_OFFSET( ComboOptions, maxLength ) },
   { "-editable", GNOCL_BOOL, GNOCL_OFFSET( ComboOptions, editable ) },
   { NULL, 0, 0 }
};

static void freeOpts( ComboOptions *p )
{
   GNOCL_STD_OPTIONS_FREE( p );
   GNOCL_FREE_MEM_OPT( p->variable );
   GNOCL_FREE_TCL_OPT( p->strings );  
}


static char *comboTraceFunc( ClientData data,
      Tcl_Interp *interp, char *name1, char *name2, int flags)
{
   ComboParams *p = (ComboParams *)data;
   GtkWidget   *entry = GTK_COMBO( p->widget )->entry;
   Tcl_Obj     *obj = name1 ? Tcl_GetVar2Ex( interp, name1, name2, 0 ) : NULL;

   assert( p->interp == interp );
   /* FIXME: ?? if( !editable ) test_if_value_is_member_of_strings */
   if( obj != NULL )
      gtk_entry_set_text( GTK_ENTRY( entry ), Tcl_GetString( obj ) );

   return NULL;
}

static void comboChangedFunc( GtkWidget *widget, gpointer data )
{
   ComboParams *p = (ComboParams *)data;
   GtkWidget   *entry = GTK_COMBO( p->widget )->entry;
   char *val = gtk_entry_get_text( GTK_ENTRY( entry ) ); 
   Tcl_SetVar( p->interp, p->variable, val, 0 );
}

static void comboDestroyFunc( GtkWidget *widget, gpointer data )
{
   ComboParams *p = (ComboParams *)data;

   gnoclForgetWidgetFromName( p->name );
   Tcl_DeleteCommand( p->interp, p->name );
   if( p->variable != NULL )
      Tcl_UntraceVar( p->interp, p->variable, TCL_TRACE_WRITES, 
            comboTraceFunc, p );
  
   g_free( p->variable );
   g_free( p->name );
   g_free( p );
}


static int comboConfigure( Tcl_Interp *interp, ComboParams *para,
      ComboOptions *popt )
{
   GtkEntry *entry = GTK_ENTRY( para->widget->entry );

   /* FIXME: tooltip only works with 
      GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( entry ) );
      but
      GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( para->widget ) );
      is consistend
   */
   GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( entry ) );

   /* TODO: split mustMatch and okIfEmtpty */
   if( popt->mustMatch.changed || popt->okIfEmtpty.changed )
      gtk_combo_set_value_in_list( para->widget, popt->mustMatch.val, 
            popt->okIfEmtpty.val );

   if( popt->strings.changed )
   {
      GList *items = NULL;
      int n, no;
      int ret = Tcl_ListObjLength( interp, popt->strings.val, &no );
      if( ret != TCL_OK )
         return ret;
      for( n = 0; n < no; ++n )
      {
         Tcl_Obj *tp;
         int ret = Tcl_ListObjIndex( interp, popt->strings.val, n, &tp );
         if( ret != TCL_OK )
            return ret;
         items = g_list_append( items, 
               (char *)gnoclGetStringFromObj( tp, NULL ) );
      }
      gtk_combo_set_popdown_strings( para->widget, items );
   }

   if( popt->variable.changed )
   {
      if( para->variable )
      {
         Tcl_UntraceVar( para->interp, para->variable, TCL_TRACE_WRITES, 
               comboTraceFunc, para );
         gtk_signal_disconnect_by_func( GTK_OBJECT( para->widget->entry ), 
               GTK_SIGNAL_FUNC( comboChangedFunc ), para );
         g_free( para->variable );
      }
      if( popt->variable.val[0] == '0' )
      {
         g_free( popt->variable.val );
         popt->variable.val = NULL;
      }

      para->variable = popt->variable.val;
      popt->variable.val = NULL;
      if( para->variable != NULL )
      {
         GtkWidget *entry = GTK_COMBO( para->widget )->entry;
         char      *val;
         /* FIXME: ? create variable, if it does not exist? */
         if( Tcl_TraceVar( interp, para->variable, TCL_TRACE_WRITES, 
               comboTraceFunc, para ) != TCL_OK )
            return TCL_ERROR;
         gtk_signal_connect( GTK_OBJECT( entry ), "changed", 
               GTK_SIGNAL_FUNC( comboChangedFunc ), para );
         val = gtk_entry_get_text( GTK_ENTRY( entry ) ); 
         Tcl_SetVar( para->interp, para->variable, val, 0 );
      }

   }

   if( popt->maxLength.changed )
      gtk_entry_set_max_length( entry, popt->maxLength.val );

   if( popt->editable.changed )
      gtk_entry_set_editable( entry, popt->editable.val );

   comboTraceFunc( para, interp, para->variable, NULL, 0 );

   return TCL_OK;
}

static int comboFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES, "configure", 
         "getValue", "setValue", NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS, ConfigureIdx, 
         GetValueIdx, SetValueIdx };

   ComboParams *para = (ComboParams *)data;
   GtkWidget *widget = GTK_WIDGET( para->widget );
   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, widget );
            break;
      case GetValueIdx:
            {
               Tcl_Obj   *obj;
               GtkEntry *entry = GTK_ENTRY( GTK_COMBO( para->widget )->entry );
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, NULL );
                  return TCL_ERROR;
               }
               obj = Tcl_NewStringObj( gtk_entry_get_text( entry ), -1 );
               Tcl_SetObjResult( interp, obj );
            }
            break;
      case SetValueIdx:
            {
               const char *txt;
               GtkEntry *entry = GTK_ENTRY( GTK_COMBO( para->widget )->entry );
               if( objc != 3 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "value" );
                  return TCL_ERROR;
               }

               txt = Tcl_GetString( objv[2] );
               gtk_entry_set_text( entry, txt );
            }
            break;
      case ConfigureIdx:
            {
               ComboOptions opt;
               int ret = TCL_ERROR;
               memset( &opt, 0, sizeof( opt ) );
               if( gnoclParseAllWidgetOpts( interp, objc, objv, 1,
                     widgetOptions, (char *)&opt ) == TCL_OK )
                  ret = comboConfigure( interp, para, &opt );

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
   }

   return TCL_OK;
}

int gnoclComboCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   ComboOptions opt;
   ComboParams *para;

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

   para = g_new( ComboParams, 1 );
   para->widget = GTK_COMBO( gtk_combo_new( ) );
   para->name = gnoclGetAutoWidgetId();
   para->interp = interp;
   para->variable = NULL;

   if( comboConfigure( interp, para, &opt ) != TCL_OK )
   {
      /* FIXME: g_free pp->name ... */
      gtk_widget_destroy( GTK_WIDGET( para->widget ) );
      return TCL_ERROR;
   }

   gtk_signal_connect( GTK_OBJECT( para->widget ), "destroy", 
         GTK_SIGNAL_FUNC( comboDestroyFunc ), para );

   gnoclMemNameAndWidget( para->name, GTK_WIDGET( para->widget ) );
   gtk_widget_show( GTK_WIDGET( para->widget ) );

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

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

   return TCL_OK;
}

