/*
 * checkButton.c --
 *
 * This file implements the checkButton widget
 *
 * Copyright (c) 2001 - 2002 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:
   2002-01: new command "invoke"
        10: added getValue, setValue
        09: underlined accelerators 
        07: percent substitution in command
            cleanups
   2001-03: Begin of developement
 */

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

typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   Gnocl_ObjBool    text;
   Gnocl_StringBool command;
   Gnocl_StringBool variable;
   Gnocl_ObjBool    onValue;
   Gnocl_ObjBool    offValue;
   Gnocl_BoolBool   indicatorOn;
} CheckButtonOptions;

static const GnoclWidgetOptions widgetOptions[] = 
{
   GNOCL_STD_OPTIONS_NAMES( CheckButtonOptions ),
   { "-text", GNOCL_OBJ, GNOCL_OFFSET( CheckButtonOptions, text ) },
   { "-command", GNOCL_STRING, GNOCL_OFFSET( CheckButtonOptions, command ) },
   { "-variable", GNOCL_STRING, GNOCL_OFFSET( CheckButtonOptions, variable ) },
   { "-onValue", GNOCL_OBJ, GNOCL_OFFSET( CheckButtonOptions, onValue ) },
   { "-offValue", GNOCL_OBJ, GNOCL_OFFSET( CheckButtonOptions, offValue ) },
   { "-indicatorOn", GNOCL_BOOL, GNOCL_OFFSET( CheckButtonOptions, 
         indicatorOn ) },
   { NULL, 0, 0 }
};

/*
   This struct contains only the necessary states to remember.
   Option values can be inquired via gtk
*/
typedef enum {  EMPTY = 0, 
                DONT_COMMAND = 1 << 0, 
                DONT_SET     = 1 << 1, 
                DONT_GET     = 1 << 2,
                ALL          = 0xFF } InSignalType;

typedef struct
{
   char           *name;
   Tcl_Interp     *interp;
   GtkCheckButton *checkButton;
   char           *command;
   char           *variable;
   Tcl_Obj        *onValue;
   Tcl_Obj        *offValue;
   InSignalType   inSignal;
} CheckButtonParams;

static void freeOpts( CheckButtonOptions *p )
{
   GNOCL_STD_OPTIONS_FREE( p );
   GNOCL_FREE_MEM_OPT( p->command );
   GNOCL_FREE_MEM_OPT( p->variable );
   GNOCL_FREE_TCL_OPT( p->onValue );
   GNOCL_FREE_TCL_OPT( p->offValue );
}

int gnoclCheckIsOn( Tcl_Interp *interp, Tcl_Obj *onValue, 
      Tcl_Obj *offValue, Tcl_Obj *val )
{
   int valInt, onInt;
   if( Tcl_GetIntFromObj( NULL, val, &valInt ) == TCL_OK 
         && Tcl_GetIntFromObj( NULL, onValue, &onInt ) == TCL_OK )
   {
      /* printf( "compare int: %d %d\n", valInt, on ); */
      return valInt == onInt;
   } 
   else if( Tcl_GetBooleanFromObj( NULL, val, &valInt ) == TCL_OK 
         && Tcl_GetBooleanFromObj( NULL, onValue, &onInt ) == TCL_OK )
   {
      /* FIXME? is it sensible to compare boolean? */
      /* printf( "compare boolean: %d %d\n", valInt, on ); */
      return valInt == onInt;
   } 
   else
   {
      char *onChar = Tcl_GetString( offValue );
      char *offChar;
      char *valChar = Tcl_GetString( val );
      /* printf( "compare char: \"%s\" \"%s\"\n", onChar, val ); */
      if( !strcmp( onChar, valChar ) )
         return 1;
      offChar = Tcl_GetString( offValue );
      /* must be either on or off value */
      if( strcmp( offChar, valChar ) )
      {
         if( interp )
         {
            Tcl_AppendResult( interp, "invalid value \"", valChar, 
                  "\", must be either\"", onChar, "\" or \"", 
                  offChar, "\".", (char *)NULL );
         }
         return -1;
      }
   }
   return 0;
}

static int doCommand( CheckButtonParams *para, Tcl_Obj *val )
{
   if( para->command )
   {
      static Gnocl_PercSubst ps[] = {
         { 'w', GNOCL_STRING },  /* widget */
         { 'v', GNOCL_OBJ },     /* value */
         { 0 }
      };

      ps[0].val.str = para->name;
      ps[1].val.obj = val;

      return gnoclPercentSubstAndEval( para->interp, ps, para->command );
   }
   return TCL_OK;
}

static int toggledFuncRet( CheckButtonParams *para )
{
   int val = gtk_toggle_button_get_active( 
         GTK_TOGGLE_BUTTON( para->checkButton ) ); 
   Tcl_Obj *valObj = val ? para->onValue : para->offValue;
   int ret = TCL_OK;

   /*
   printf( "DEBUG checkButton in toggle %d onValue: %s %d offValue: %s %d\n", 
      val,
      Tcl_GetString( para->onValue ), Tcl_IsShared( para->onValue ),
      Tcl_GetString( para->offValue ), Tcl_IsShared( para->offValue ) );
   */

   if( ( para->inSignal & DONT_SET ) == 0 && para->variable )
   {
      para->inSignal |= DONT_GET;
      if( Tcl_SetVar2Ex( para->interp, para->variable, NULL, 
            valObj, TCL_GLOBAL_ONLY ) == NULL )
         ret = TCL_ERROR;
      para->inSignal &= ~DONT_GET;
   }
   if( ( para->inSignal & DONT_COMMAND ) == 0 )
   {
      if( doCommand( para, valObj ) != TCL_OK )
         ret = TCL_ERROR;
   }

   return ret;
}

static char *traceFunc( ClientData data,
      Tcl_Interp *interp, char *name1, char *name2, int flags)
{
   CheckButtonParams *para = (CheckButtonParams *)data;
   if( ( para->inSignal & DONT_GET ) == 0 )
   {
      Tcl_Obj *val = Tcl_GetVar2Ex( interp, name1, name2, flags );
      int on = 0;
      if( val ) 
         on = gnoclCheckIsOn( interp, para->onValue, para->offValue, val );

      /* TODO: what to do if on < 0 i.e. an error occured? */
      if( on < 0 )
         on = 0;

      para->inSignal |= DONT_SET|DONT_COMMAND;
      /* toggledFuncRet is only automatically called if the status of
         the check button is changed, i.e. it was not 'on' */
      gtk_toggle_button_set_active( 
               GTK_TOGGLE_BUTTON( para->checkButton ), on );
      para->inSignal &= ~(DONT_SET|DONT_COMMAND);
      doCommand( para, on ? para->onValue : para->offValue );
   }

   return NULL;
}

static void toggledFunc( GtkWidget *widget, gpointer data )
{
   toggledFuncRet( (CheckButtonParams *)data );
}

static void checkButtonDestroyFunc( GtkWidget *widget, gpointer data )
{
   CheckButtonParams *para = (CheckButtonParams *)data;

   /* untrace and free command and variable */
   gnoclAttacheCmdAndVar( 
      1, &para->command, NULL,
      1, &para->variable, NULL,
      "toggled", GTK_OBJECT( para->checkButton ), toggledFunc,
      para->interp, traceFunc, para );

   gnoclForgetWidgetFromName( para->name );
   Tcl_DeleteCommand( para->interp, para->name );
   g_free( para->name );

   Tcl_DecrRefCount( para->onValue );
   Tcl_DecrRefCount( para->offValue );
   g_free( para );
}

static int checkButtonConfigure( Tcl_Interp *interp, 
      CheckButtonParams *para, CheckButtonOptions *popt )
{
   GNOCL_STD_OPTIONS_HANDLE( popt, GTK_WIDGET( para->checkButton ) );

   if( popt->text.changed )
   {
      GnoclStringType type = gnoclGetStringType( popt->text.val );
      char *txt = gnoclGetString( popt->text.val );
      GtkLabel *label = gnoclFindLabel( GTK_WIDGET( para->checkButton ) );
      if( label )
      {
         gtk_label_set_text( label, txt );
         if( type & GNOCL_STR_UNDERLINE )
         {
            guint key = gnoclLabelParseULine( GTK_WIDGET( label ), txt );
            if( key != GDK_VoidSymbol )
               gtk_widget_add_accelerator( GTK_WIDGET( para->checkButton ), 
                     "clicked", gnoclGetAccelGroup(), 
                     key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED );
         }
      }
   }

   if( popt->indicatorOn.changed )
      gtk_toggle_button_set_mode( GTK_TOGGLE_BUTTON( para->checkButton ), 
            popt->indicatorOn.val );

   if( popt->onValue.changed )
   {
      if( para->onValue ) Tcl_DecrRefCount( para->onValue );
      para->onValue = popt->onValue.val;
      Tcl_IncrRefCount( para->onValue );
   }

   if( popt->offValue.changed )
   {
      if( para->offValue ) Tcl_DecrRefCount( para->offValue );
      para->offValue = popt->offValue.val;
      Tcl_IncrRefCount( para->offValue );
   }

   gnoclAttacheCmdAndVar( 
         popt->command.changed, &para->command, &popt->command.val,
         popt->variable.changed, &para->variable, &popt->variable.val,
         "toggled", GTK_OBJECT( para->checkButton ), toggledFunc,
         interp, traceFunc, para );

   if( popt->variable.changed && para->variable != NULL )
   {
      /* if variable does not exist -> set it, else set widget state */
      Tcl_Obj *var = Tcl_GetVar2Ex( interp, para->variable, NULL, 
            TCL_GLOBAL_ONLY );
      if( var == NULL )
      {
         int on = gtk_toggle_button_get_active( 
               GTK_TOGGLE_BUTTON( para->checkButton ) ); 
         Tcl_Obj *valObj = on ? para->onValue : para->offValue;

         para->inSignal |= DONT_GET;
         Tcl_SetVar2Ex( para->interp, para->variable, NULL, valObj, 
               TCL_GLOBAL_ONLY );
         para->inSignal &= ~DONT_GET;
      }
      else
      {
         if( gnoclCheckIsOn( interp, para->onValue, para->offValue, var ) > 0 )
         {
            para->inSignal |= DONT_SET|DONT_COMMAND;
            gtk_toggle_button_set_active( 
                  GTK_TOGGLE_BUTTON( para->checkButton ), 1 );
            /* since the state of checkButton is changed, 
               toggledFuncRet is automatically called here */
            para->inSignal &= ~(DONT_SET|DONT_COMMAND);
         }
      }
      
   }

   return TCL_OK;
}

static int checkButtonFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES,
         "configure", "getValue", "setValue", "invoke", NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS, 
         ConfigureIdx, GetValueIdx, SetValueIdx, InvokeIdx };
   CheckButtonParams *para = (CheckButtonParams *)data;
   GtkWidget *widget = GTK_WIDGET( para->checkButton );
   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 ConfigureIdx:
            {
               CheckButtonOptions opt;
               int ret = TCL_ERROR;

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

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case GetValueIdx:
            {
               int on = gtk_toggle_button_get_active( 
                     GTK_TOGGLE_BUTTON( para->checkButton ) ); 
               Tcl_Obj *obj;
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, NULL );
                  return TCL_ERROR;
               }
               obj = on ? para->onValue : para->offValue;
               /* this is not necessary!!? Tcl_IncrRefCount( obj ); */
               Tcl_SetObjResult( interp, obj );
            }
            break;
      case SetValueIdx:
            {
               int on;
               if( objc != 3 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "value" );
                  return TCL_ERROR;
               }
               on = gnoclCheckIsOn( interp, para->onValue, para->offValue, 
                     objv[2] );
               if( on >= 0 )
               {
                  para->inSignal |= (DONT_SET | DONT_COMMAND);
                  gtk_toggle_button_set_active( 
                        GTK_TOGGLE_BUTTON( para->checkButton ), on );
                  para->inSignal &= ~(DONT_SET | DONT_COMMAND);
                  /* command is only executed, if status changes, so we
                     call it always explicitely */
                  return toggledFuncRet( para ); 
               }
               else
                  return TCL_ERROR;
            }
            break;
      case InvokeIdx:
            if( GTK_WIDGET_IS_SENSITIVE( widget ) )
               gtk_button_clicked( GTK_BUTTON( para->checkButton ) ); 
            break;
   }

   return TCL_OK;
}

int gnoclCheckButtonCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   CheckButtonOptions opt;
   CheckButtonParams *para;
   int ret;
   
   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 0,
         widgetOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;

   para = g_new( CheckButtonParams, 1 );
   para->interp = interp;
   para->name = gnoclGetAutoWidgetId();
   para->checkButton = GTK_CHECK_BUTTON( 
         gtk_check_button_new_with_label( "" ) );
   para->command = NULL;
   para->variable = NULL;
   para->onValue = Tcl_NewIntObj( 1 );
   Tcl_IncrRefCount( para->onValue );
   para->offValue = Tcl_NewIntObj( 0 );
   Tcl_IncrRefCount( para->offValue );
   para->inSignal = 0;

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

   gtk_signal_connect( GTK_OBJECT( para->checkButton ), "destroy", 
         GTK_SIGNAL_FUNC( checkButtonDestroyFunc ), para );

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

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

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

   return TCL_OK;
}

