
/*
 * gnoclTable.c --
 *
 * This file implements the table widget which is a combination of the
 * gtk table 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:
        12: removed {x,y}{Expand,Shrink,Fill,Padding}, use list instead
            added alignment
            add{Row,Column}
            {row,column}Spacing
            exchange order of row and column in "add"
            STD_OPTIONS
        12: added frame options
   2001-03: Begin of developement
 */

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

typedef struct
{
   GNOCL_STD_OPTIONS_VARS;

   /* table options */
   Gnocl_BoolBool homogeneous;
   Gnocl_IntBool  rows;
   Gnocl_IntBool  columns;
   Gnocl_IntBool  rowSpacing;
   Gnocl_IntBool  columnSpacing;

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

static const GnoclWidgetOptions tableOptions[] =
{
   GNOCL_STD_OPTIONS_NAMES( TableOptions ),

   { "-homogeneous", GNOCL_BOOL, GNOCL_OFFSET( TableOptions, homogeneous ) },
   { "-rows", GNOCL_INT, GNOCL_OFFSET( TableOptions, rows ) },
   { "-columns", GNOCL_BOOL, GNOCL_OFFSET( TableOptions, columns ) },
   { "-rowSpacing", GNOCL_INT, GNOCL_OFFSET( TableOptions, rowSpacing ) },
   { "-columnSpacing", GNOCL_INT, GNOCL_OFFSET( TableOptions, 
         columnSpacing ) },

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

typedef struct 
{
   Gnocl_IntBool  rowSpan;
   Gnocl_IntBool  columnSpan;
   Gnocl_ListBool expand;
   Gnocl_ListBool shrink;
   Gnocl_ListBool fill;
   Gnocl_ListBool padding;

   /* parameters for alignment widget */
   Gnocl_ListBool align;
   Gnocl_ListBool scale;

   /* only for add{Row,Column} */
   Gnocl_IntBool  start;
} PackOptions;

static const GnoclWidgetOptions packOptions[] = 
{
   { "-rowSpan", GNOCL_INT, GNOCL_OFFSET( PackOptions, rowSpan ) },
   { "-columnSpan", GNOCL_INT, GNOCL_OFFSET( PackOptions, columnSpan ) },
   { "-expand", GNOCL_LIST, GNOCL_OFFSET( PackOptions, expand ) },
   { "-shrink", GNOCL_LIST, GNOCL_OFFSET( PackOptions, shrink ) },
   { "-fill", GNOCL_LIST, GNOCL_OFFSET( PackOptions, fill ) },
   { "-padding", GNOCL_LIST, GNOCL_OFFSET( PackOptions, padding ) },

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

typedef struct
{
   char       *name;
   Tcl_Interp *interp;
   GtkTable   *table;
   GtkFrame   *frame;
} TableParams;

typedef struct
{
   GtkAttachOptions xOptions, yOptions;
   int              xPad, yPad;
   int              rowSpan, columnSpan;
   int              useAlign;
   double           xScale, yScale,
                    xAlign, yAlign;
} AttachOptions;

static AttachOptions DefaultAttachOptions = { 
      GTK_EXPAND|GTK_FILL|GTK_SHRINK, GTK_EXPAND|GTK_FILL|GTK_SHRINK,
      0, 0,             /* pad */
      1, 1,             /* span */
      0,                /* useAlign */
      .0, .0,           /* scale (don't fill) */
      0.5, 0.5,         /* align (center) */
};

static void freePackOptions( PackOptions *p )
{
   GNOCL_FREE_TCL_OPT( p->expand );
   GNOCL_FREE_TCL_OPT( p->shrink );
   GNOCL_FREE_TCL_OPT( p->fill );
   GNOCL_FREE_TCL_OPT( p->padding );
   GNOCL_FREE_TCL_OPT( p->align );
   GNOCL_FREE_TCL_OPT( p->scale );
}

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

   GNOCL_FREE_MEM_OPT( p->label );
   GNOCL_FREE_TCL_OPT( p->shadow );
}

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

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

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

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

   /* FIXME: create or delete frame if necessary 
             also change: gnoclMemNameAndWidget( name, widget );
   */
   /* see box.c */
   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->homogeneous.changed )
      gtk_table_set_homogeneous( para->table, popt->homogeneous.val );

   if( popt->rowSpacing.changed )
      gtk_table_set_row_spacings( para->table, popt->rowSpacing.val );

   if( popt->columnSpacing.changed )
      gtk_table_set_col_spacings( para->table, popt->columnSpacing.val );

   return TCL_OK;
}


static int parsePackOptions( Tcl_Interp *interp, PackOptions *po, 
   AttachOptions *pa )
{
   if( po->expand.changed )   
   {
      int x, y;
      if( gnoclGet2Boolean( interp, po->expand.val, &x, &y ) != TCL_OK )
         return TCL_ERROR;

      if( x == 0 )
         pa->xOptions &= ~GTK_EXPAND;
      if( y == 0 )
         pa->yOptions &= ~GTK_EXPAND;
   }
   if( po->shrink.changed )   
   {
      int x, y;
      if( gnoclGet2Boolean( interp, po->shrink.val, &x, &y ) != TCL_OK )
         return TCL_ERROR;

      if( x == 0 )
         pa->xOptions &= ~GTK_SHRINK;
      if( y == 0 )
         pa->yOptions &= ~GTK_SHRINK;
   }
   if( po->fill.changed )   
   {
      int x, y;
      if( gnoclGet2Boolean( interp, po->fill.val, &x, &y ) != TCL_OK )
         return TCL_ERROR;

      if( x == 0 )
         pa->xOptions &= ~GTK_FILL;
      if( y == 0 )
         pa->yOptions &= ~GTK_FILL;
   }
   if( po->padding.changed )   
   {
      if( gnoclGet2Boolean( interp, po->padding.val, 
            &pa->xPad, &pa->yPad ) != TCL_OK )
         return TCL_ERROR;
   }

   if( po->columnSpan.changed )   
      pa->columnSpan = po->columnSpan.val;

   if( po->rowSpan.changed )
      pa->rowSpan = po->rowSpan.val;

   if( po->align.changed || po->scale.changed )
   {
      pa->useAlign = 1;
      if( po->scale.changed )
      {
         /* TODO? if( po->fill.changed ) return TCL_ERROR; */
         if( gnoclGet2Double( interp, po->scale.val, 
               &pa->xScale, &pa->yScale ) != TCL_OK )
            return TCL_ERROR;
      }
      if( po->align.changed )
      {
         if( gnoclGet2Double( interp, po->align.val, 
               &pa->xAlign, &pa->yAlign ) != TCL_OK )
            return TCL_ERROR;
      }
   }

   return TCL_OK;
}

static int addRow( TableParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int addRowFlag )
{
   PackOptions      params;
   int              ret;
   AttachOptions    attachOptions = DefaultAttachOptions;
   const int        nPack = sizeof(packOptions)/sizeof(*packOptions);
   GnoclWidgetOptions addOptions[nPack+1];
   GnoclWidgetOptions addO[] = { 
         { "-start", GNOCL_INT, GNOCL_OFFSET( PackOptions, start ) },
         { NULL, 0, 0 } };

   memcpy( &addOptions, &packOptions, sizeof( packOptions ) );
   addOptions[nPack-1] = addO[0];
   addOptions[nPack] = addO[1];

   if( objc < 2 )
   {
      /* id add{Row,Column} list ... */
      Tcl_WrongNumArgs( interp, 2, objv, 
            "widget-list ?option val ...?" );
      return TCL_ERROR;
   }

   memset( &params, 0, sizeof( params ) );
   ret = gnoclParseAllWidgetOpts( interp, objc, objv, 2,
         addOptions, (char *)&params );

   if( ret == TCL_OK )
      ret = parsePackOptions( interp, &params, &attachOptions );

   if( ret == TCL_OK )
   {
      int    noChilds;
      int    n;
      int    column = 0, 
             row = 0;
      
      if( params.start.changed )
      {
         if( addRowFlag )
            column = params.start.val;
         else
            row = params.start.val;
      }
      if( gtk_container_children( GTK_CONTAINER( pp->table ) ) != NULL )
      {
         /* empty table has 1 column and 1 row, but no children */
         GtkArg arg;
         arg.name = addRowFlag ? "n_rows" : "n_columns";
         gtk_object_getv( GTK_OBJECT( pp->table ), 1, &arg );
         if( addRowFlag )
            row = GTK_VALUE_INT( arg );
         else
            column = GTK_VALUE_INT( arg );
      }


      if( Tcl_ListObjLength( interp, objv[2], &noChilds ) != TCL_OK 
         || noChilds < 1 )
      {
         Tcl_SetResult( interp, "widget-list must be proper list", 
               TCL_STATIC );
         noChilds = 0;
         ret = TCL_ERROR;
      }
      for( n = 0; n < noChilds; ++n )
      {
         GtkWidget *childWidget;
         Tcl_Obj *tp;
         const char *childName;

         if( Tcl_ListObjIndex( interp, objv[2], n, &tp ) != TCL_OK )
         {
            ret = TCL_ERROR;
            break;
         }
         /* TODO? if list object: options */
         childName = Tcl_GetString( tp );
         /* empty widget-ID: skip this row/column */
         if( *childName )
         {
            childWidget = gnoclChildNotPacked( childName, interp );
            if( childWidget == NULL )
            {
               ret = TCL_ERROR;
               break;
            }
            if( attachOptions.useAlign )
            {
               GtkWidget *alignment;
               alignment = gtk_alignment_new( attachOptions.xAlign, 
                     attachOptions.yAlign, attachOptions.xScale, 
                     attachOptions.yScale );
               gtk_container_add( GTK_CONTAINER( alignment ), childWidget );
               gtk_widget_show( alignment );
               attachOptions.xOptions |= GTK_FILL;
               attachOptions.yOptions |= GTK_FILL;
               childWidget = alignment;
            }
            gtk_table_attach( pp->table, childWidget, 
                  column, column + attachOptions.columnSpan,
                  row, row + attachOptions.rowSpan, 
                  attachOptions.xOptions, attachOptions.yOptions, 
                  attachOptions.xPad, attachOptions.yPad );
         }
         if( addRowFlag )
            column += attachOptions.columnSpan;
         else
            row += attachOptions.rowSpan;
      }
   }


   freePackOptions( &params );

   return ret;
}


static int tableFuncAdd( TableParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   PackOptions params;
   int row, column;
   char *childName;
   GtkWidget *childWidget;
   /* default is expand, fill and shrink */
   int ret;

   if( objc < 5 || objc % 2 == 0 )
   {
      Tcl_WrongNumArgs( interp, 1, objv, 
            "add widget column row ?option val ...?" );
      return TCL_ERROR;
   }
   childName = Tcl_GetString( objv[2] );
   childWidget = gnoclGetWidgetFromName( childName, interp );
   if( childWidget == NULL )
      return TCL_ERROR;

   if( Tcl_GetIntFromObj( interp, objv[3], &column ) != TCL_OK )
      return TCL_ERROR;
   if( Tcl_GetIntFromObj( interp, objv[4], &row ) != TCL_OK )
      return TCL_ERROR;

   memset( &params, 0, sizeof( params ) );
   ret = gnoclParseAllWidgetOpts( interp, objc, objv, 4,
         packOptions, (char *)&params );

   if( ret == TCL_OK )
   {
      AttachOptions attachOptions = DefaultAttachOptions;
      ret = parsePackOptions( interp, &params, &attachOptions );

      if( ret == TCL_OK )
      {
         if( attachOptions.useAlign )
         {
            GtkWidget *alignment;
            alignment = gtk_alignment_new( attachOptions.xAlign, 
                  attachOptions.yAlign, attachOptions.xScale, 
                  attachOptions.yScale );
            gtk_container_add( GTK_CONTAINER( alignment ), childWidget );
            gtk_widget_show( alignment );
            attachOptions.xOptions |= GTK_FILL;
            attachOptions.yOptions |= GTK_FILL;
            childWidget = alignment;
         }

         gtk_table_attach( pp->table, childWidget, 
               column, column + attachOptions.columnSpan,
               row, row + attachOptions.rowSpan, 
               attachOptions.xOptions, attachOptions.yOptions, 
               attachOptions.xPad, attachOptions.yPad );
      }
   }

   freePackOptions( &params );

   return ret;
}

static int tableFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES,
         "configure", "add", "addRow", "addColumn",
         "rowSpacing", "columnSpacing", NULL };
   enum cmdIdx { GNOCL_STD_CMD_ENUMS, 
         ConfigureIdx, AddIdx, AddRowIdx, AddColumnIdx,
         RowSpacingIdx, ColumnSpacingIdx };

   TableParams *para = (TableParams *)data;
   int idx;
   GtkWidget *widget = para->frame ? GTK_WIDGET( para->frame ) 
         : GTK_WIDGET( para->table );



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

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

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case AddIdx:
            return tableFuncAdd( para, interp, objc, objv );
      case AddColumnIdx:
      case AddRowIdx:
            return addRow( para, interp, objc, objv, idx == AddRowIdx );
      case RowSpacingIdx:
      case ColumnSpacingIdx:
            /* FIXME */
            break;
      /*
      "\"rowSpacing row spacing\" or \"rowSpacing list-of-row-spacing-pairs\""
      */
   }
   return TCL_OK;
}

int gnoclTableCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   TableOptions opt;

   TableParams *para;
   int         ret;
   GtkWidget   *widget;
   
   memset( &opt, 0, sizeof( opt ) );
   if( gnoclParseAllWidgetOpts( interp, objc, objv, 0,
         tableOptions, (char *)&opt ) != TCL_OK )
      return TCL_ERROR;

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


   /* set default values */
   /*
   if( opt.rows.changed == 0 )          opt.rows.val = 3;
   if( opt.columns.changed == 0 )       opt.columns.val = 3;
   */
   if( opt.homogeneous.changed == 0 )   opt.homogeneous.val = 1;

   para->table = GTK_TABLE( gtk_table_new( opt.rows.val, opt.columns.val, 
         opt.homogeneous.val) );

   if( para->frame )
   {
      widget = GTK_WIDGET( para->frame );
      gtk_container_add( GTK_CONTAINER( para->frame ), 
            GTK_WIDGET( para->table ) );
   }
   else
      widget = GTK_WIDGET( para->table );
  
   ret = tableConfigure( interp, para, &opt );
   freeOpts( &opt );
   if( ret != TCL_OK )
   {
      gtk_widget_destroy( GTK_WIDGET( para->table ) );
      return TCL_ERROR;
   }

   gnoclMemNameAndWidget( para->name, widget );
   gtk_widget_show_all( widget );

   Tcl_CreateObjCommand( interp, para->name, tableFunc, 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;
}

