
/*
 * box.c --
 *
 * This file implements the box widget which is a combination of the
 * gtk box widget and the gtk frame 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: STD_OPTIONS
        12: added alignment
        11: new option layout
        09: bug fixes and cleanups
        04: added frame options
   2001-03: Begin of developement
 */

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

typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   /* Box options */
   Gnocl_ObjBool  orientation;
   Gnocl_ObjBool  layout;
   Gnocl_BoolBool homogeneous;
   Gnocl_IntBool  spacing;
   Gnocl_BoolBool buttonType;
   Gnocl_ListBool children;
   Gnocl_BoolBool expand;
   Gnocl_BoolBool fill;
   Gnocl_IntBool  padding;

   /* Frame options */
   Gnocl_StringBool label;
   Gnocl_ObjBool    shadow;
   Gnocl_DoubleBool xAlign;
} BoxOptions;

enum { ExpandDefault = 1, FillDefault = 1, PaddingDefault = 0 };

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

   { "-orientation", GNOCL_OBJ, GNOCL_OFFSET( BoxOptions, orientation ) },
   { "-layout", GNOCL_OBJ, GNOCL_OFFSET( BoxOptions, layout ) },
   { "-homogeneous", GNOCL_BOOL, GNOCL_OFFSET( BoxOptions, homogeneous ) },
   { "-spacing", GNOCL_INT, GNOCL_OFFSET( BoxOptions, spacing ) },
   { "-buttonType", GNOCL_BOOL, GNOCL_OFFSET( BoxOptions, buttonType ) },
   { "-children", GNOCL_LIST, GNOCL_OFFSET( BoxOptions, children ) },
   { "-expand", GNOCL_BOOL, GNOCL_OFFSET( BoxOptions, expand ) },
   { "-fill", GNOCL_BOOL, GNOCL_OFFSET( BoxOptions, fill ) },
   { "-padding", GNOCL_INT, GNOCL_OFFSET( BoxOptions, padding ) },

   { "-label", GNOCL_STRING, GNOCL_OFFSET( BoxOptions, label ) },
   { "-shadow", GNOCL_OBJ, GNOCL_OFFSET( BoxOptions, shadow ) },
   { "-xAlign", GNOCL_DOUBLE, GNOCL_OFFSET( BoxOptions, xAlign ) },
   { NULL, 0, 0 }
};

/*
   This struct contains only the necessary states to remember.
   Option values can be inquired via gtk
*/
typedef struct
{
   char       *name;
   Tcl_Interp *interp;
   GtkBox     *box;
   GtkFrame   *frame;
} BoxParams;

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

   GNOCL_FREE_MEM_OPT( p->label );

   GNOCL_FREE_TCL_OPT( p->orientation );
   GNOCL_FREE_TCL_OPT( p->shadow );
   GNOCL_FREE_TCL_OPT( p->children );
}

static void destroyFunc( GtkWidget *widget, gpointer data )
{
   BoxParams *p = (BoxParams *)data;

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

   g_free( p->name );
   g_free( p );
}

static void alignDestroyFunc( GtkWidget *widget, gpointer data )
{
   gtk_widget_destroy( GTK_WIDGET( data ) );
}

static int addChildren( GtkBox *box, Tcl_Interp *interp, 
      Tcl_Obj *children, int expand, int fill, int padding, 
      Tcl_Obj *align, Tcl_Obj *scale, int begin )
{
   int n, noChilds;
   double xScale = 0;        /* don't fill */
   double yScale = 0;
   double xAlign = 0.5;      /* center */
   double yAlign = 0.5;


   if( Tcl_ListObjLength( interp, children, &noChilds ) != TCL_OK 
         || noChilds < 1 )
   {
      Tcl_SetResult( interp, "widget-list must be proper list", 
            TCL_STATIC );
      return TCL_ERROR;
   }

   if( scale != NULL )
   {
      if( gnoclGet2Double( interp, scale, &xScale, &yScale ) != TCL_OK )
         return TCL_ERROR;
   }
   if( align != NULL )
   {
      if( gnoclGet2Double( interp, align, &xAlign, &yAlign ) != TCL_OK )
         return TCL_ERROR;
   }

   for( n = 0; n < noChilds; ++n )
   {
      GtkWidget *childWidget;
      Tcl_Obj *tp;

      if( Tcl_ListObjIndex( interp, children, n, &tp ) != TCL_OK )
         return TCL_ERROR;

      childWidget = gnoclChildNotPacked( Tcl_GetString( tp ), interp );
      if( childWidget == NULL )
         return TCL_ERROR;

      if( scale != NULL || align != NULL )
      {
         GtkWidget *alignment = gtk_alignment_new( xAlign, yAlign, 
               xScale, yScale );
         /* alignment is deleted on deletion of childWidget 
            only necessary, if not whole box is destroyed */
         gtk_signal_connect( GTK_OBJECT( childWidget ), "destroy", 
               GTK_SIGNAL_FUNC( alignDestroyFunc ), alignment );
         gtk_widget_show( alignment );
         gtk_container_add( GTK_CONTAINER( alignment ), childWidget );
         fill = 1;
         childWidget = alignment;
      }
      if( begin )
         gtk_box_pack_start( box, childWidget, expand, fill, padding );
      else
         gtk_box_pack_end( box, childWidget, expand, fill, padding );
   }

   return TCL_OK;
}

static int getLayout( Tcl_Interp *interp, Tcl_Obj *obj, 
      GtkButtonBoxStyle *style )
{
   char *txt[] = { "default", "spread", "edge", "start", "end", NULL };
   GtkButtonBoxStyle styles[] = { GTK_BUTTONBOX_DEFAULT_STYLE,
         GTK_BUTTONBOX_SPREAD, GTK_BUTTONBOX_EDGE, GTK_BUTTONBOX_START,
         GTK_BUTTONBOX_END };

   int idx;
   if( Tcl_GetIndexFromObj( interp, obj, txt, "button box layout",
         TCL_EXACT, &idx ) != TCL_OK )
      return TCL_ERROR;

   *style = styles[idx];

   return TCL_OK;
}

/*
   test, if params are valid
   set params and show window with new settings
*/
static int boxConfigure( Tcl_Interp *interp, BoxParams *para, 
      BoxOptions *popt )
{
   GNOCL_STD_OPTIONS_HANDLE( popt, 
         para->frame ? GTK_WIDGET( para->frame ) : GTK_WIDGET( para->box ) );


   /* FIXME: create or delete frame if necessary 
             also change: gnoclMemNameAndWidget( name, widget );
   */
   if( para->frame )
   {
      if( popt->label.changed )
         gtk_frame_set_label( para->frame, popt->label.val );

      if( popt->shadow.changed )
      {
         GtkShadowType shadow;
         if( gnoclGetShadowType( interp, popt->shadow.val, 
               &shadow ) != TCL_OK )
            return TCL_ERROR;

         gtk_frame_set_shadow_type( para->frame, shadow );
      }

      if( popt->xAlign.changed )
         gtk_frame_set_label_align( para->frame, popt->xAlign.val, .0 );
   }
   if( popt->children.changed )
   {
      const int fill = popt->fill.changed ? popt->fill.val : FillDefault;
      const int expand = popt->expand.changed ? 
            popt->expand.val : ExpandDefault;
      const int padding = popt->padding.changed ? 
            popt->padding.val : PaddingDefault;

      int ret = addChildren( para->box, interp, popt->children.val, 
            expand, fill, padding, NULL, NULL, 1 );
      if( ret != TCL_OK )
         return ret;
   }

   if( popt->homogeneous.changed )
      gtk_box_set_homogeneous( para->box, popt->homogeneous.val );

   if( popt->homogeneous.changed )
      gtk_box_set_spacing( para->box, popt->spacing.val );

   if( popt->layout.changed )
   {
      GtkButtonBoxStyle layout;
      if( getLayout( interp, popt->layout.val, &layout ) != TCL_OK )
         return TCL_ERROR;

      if( GTK_CHECK_TYPE( para->box, GTK_TYPE_BUTTON_BOX ) )
         gtk_button_box_set_layout( GTK_BUTTON_BOX( para->box ), layout );
      else
      {
         Tcl_SetResult( interp, 
               "Option \"layout\" only valid for box of buttonType", 
               TCL_STATIC );
         return TCL_ERROR;
      }
   }

   return TCL_OK;
}

static int boxFuncAdd( BoxParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int begin )
{
   typedef struct 
   {
      Gnocl_BoolBool expand;
      Gnocl_BoolBool fill;
      Gnocl_IntBool padding;

      /* parameters for alignment widget */
      Gnocl_ListBool align;
      Gnocl_ListBool scale;
   } PackOptions;
   PackOptions params;
   GnoclWidgetOptions options[] = 
   {
      { "-expand", GNOCL_BOOL, GNOCL_OFFSET( PackOptions, expand ) },
      { "-fill", GNOCL_BOOL, GNOCL_OFFSET( PackOptions, fill ) },
      { "-padding", GNOCL_INT, GNOCL_OFFSET( PackOptions, padding ) },

      { "-align", GNOCL_LIST, GNOCL_OFFSET( PackOptions, align ) },
      { "-scale", GNOCL_LIST, GNOCL_OFFSET( PackOptions, scale ) },
      { NULL, 0, 0 }
   };
   int ret;

   if( objc < 3 || objc % 2 == 0 )
   {
      Tcl_WrongNumArgs( interp, 2, objv, 
            "widget-list ?option val ...?" );
      return TCL_ERROR;
   }

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

   ret = addChildren( pp->box, interp, objv[2], 
         params.expand.changed ? params.expand.val : ExpandDefault, 
         params.fill.changed ? params.fill.val : FillDefault, 
         params.padding.changed ? params.padding.val : PaddingDefault, 
         params.align.changed ? params.align.val : NULL, 
         params.scale.changed ? params.scale.val : NULL, 
         begin );

   GNOCL_FREE_TCL_OPT( params.align );
   GNOCL_FREE_TCL_OPT( params.scale );

   return ret;
}

static int boxFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = {  GNOCL_STD_CMD_NAMES,
         "configure", "add", "addBegin", "addEnd", NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS,
         ConfigureIdx, AddIdx, BeginIdx, EndIdx };

   BoxParams *para = (BoxParams *)data;
   int idx;

   GtkWidget *widget = para->frame ? GTK_WIDGET( para->frame ) 
         : GTK_WIDGET( para->box );

   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:
            {
               BoxOptions opt;
               int ret = TCL_ERROR;
               memset( &opt, 0, sizeof( opt ) );

               if( gnoclParseAllWidgetOpts( interp, objc, objv, 1,
                     widgetOptions, (char *)&opt ) == TCL_OK )
                  ret = boxConfigure( interp, para, &opt );

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case AddIdx:
      case BeginIdx:
      case EndIdx:
            return boxFuncAdd( para, interp, objc, objv, idx != EndIdx );
   }
   return TCL_OK;
}

int gnoclBoxCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   /* BoxParams *pp = g_new( BoxParams, 1 ); */
   BoxOptions opt;
   BoxParams *para;
   GtkOrientation orient = GTK_ORIENTATION_HORIZONTAL;
   int ret;
   GtkWidget *widget;
   
   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 0,
         widgetOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;


   para = g_new( BoxParams, 1 );
   /* TODO: test, if label != "" and shadow != "flat" ? */
   if( opt.label.val || opt.shadow.val )
      para->frame = GTK_FRAME( gtk_frame_new( NULL ) );
   else
      para->frame = NULL;

   para->name = gnoclGetAutoWidgetId( );
   para->interp = interp;
   if( opt.orientation.changed )
   {
      if( gnoclGetOrientationType( interp, opt.orientation.val, &orient )
            != TCL_OK )
      {
         freeOpts( &opt );
         return TCL_ERROR;
      }
   }

   if( orient == GTK_ORIENTATION_HORIZONTAL )
   {
      if( opt.buttonType.val )
         para->box = GTK_BOX( gtk_hbutton_box_new( ) );
      else
      {
         para->box = GTK_BOX( gtk_hbox_new( opt.homogeneous.val, 
               opt.spacing.val ) );
      }
   }
   else
   {
      if( opt.buttonType.val )
         para->box = GTK_BOX( gtk_vbutton_box_new( ) );
      else
      {
         para->box = GTK_BOX( gtk_vbox_new( opt.homogeneous.val, 
               opt.spacing.val ) );
      }
   }

   if( para->frame )
   {
      widget = GTK_WIDGET( para->frame );
      gtk_container_add( GTK_CONTAINER( para->frame ), 
            GTK_WIDGET( para->box ) );
   }
   else
      widget = GTK_WIDGET( para->box );
   
   /* show box and frame if it exists */
   gtk_widget_show_all( widget );

   ret = boxConfigure( interp, para, &opt );
   freeOpts( &opt );
   if( ret != TCL_OK )
   {
      if( para->frame )
         gtk_widget_destroy( GTK_WIDGET( para->frame ) );
      gtk_widget_destroy( GTK_WIDGET( para->box ) );
      return TCL_ERROR;
   }

   gnoclMemNameAndWidget( para->name, widget );

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

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

   gtk_signal_connect( GTK_OBJECT( widget ), "destroy", 
         GTK_SIGNAL_FUNC( destroyFunc ), para );
 
   return TCL_OK;
}

