
/*
 * menu.c --
 *
 * This file implements the menu 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:
        09: accelerator for menuItems
   2001-03: Begin of developement
 */

/*
   TODO?: menu factory? But as tcl function!
*/

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

typedef struct
{
   Gnocl_StringBool title;
   Gnocl_BoolBool  tearoff;
   Gnocl_ListBool  children;
} MenuOptions;

/*
   register key as accelerator for widget
   accelGroup and modifier can only determined when packed
   only a small number of WidgetKeys are pending, so a list
   sould be sufficient
*/
typedef struct
{
   GtkWidget *widget;
   guint     key;
} WidgetKey;

static GSList *widgetKeyList = NULL;

static const GnoclWidgetOptions widgetOptions[] =
{
   { "-title", GNOCL_STRING, GNOCL_OFFSET( MenuOptions, title ) },
   { "-tearoff", GNOCL_BOOL, GNOCL_OFFSET( MenuOptions, tearoff ) },
   { "-children", GNOCL_LIST, GNOCL_OFFSET( MenuOptions, children ) },
   { 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;
   GtkMenu    *widget;
   GtkWidget  *tearoffWidget;

} MenuParams;

static void freeOpts( MenuOptions *p )
{
   GNOCL_FREE_MEM_OPT( p->title );
   GNOCL_FREE_TCL_OPT( p->children );
}

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

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

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

int gnoclMenu_registerKey( GtkWidget *widget, guint key )
{
   WidgetKey *p = g_new( WidgetKey, 1 );
   p->widget = widget;
   p->key = key;

   /* TODO: what to do, if the assert fails? */
   assert( gnoclMenu_getKey( widget ) == GDK_VoidSymbol );

   widgetKeyList = g_slist_append( widgetKeyList, p );

   return 0;
}

guint gnoclMenu_getKey( GtkWidget *widget )
{
   guint key = GDK_VoidSymbol;
   GSList *p;

   for( p = widgetKeyList; p != NULL; p = g_slist_next( p ) )
   {
      WidgetKey *wp = (WidgetKey *)p->data;
      if( wp->widget == widget )
      {
         key = wp->key;
         widgetKeyList = g_slist_remove_link( widgetKeyList, p );
         g_slist_free( p );
         break;
      }
   }

   return key;
}

static int addChildren( MenuParams *pp, Tcl_Interp *interp,
      Tcl_Obj *children, int atEnd )
{
   int n, noChilds;
   if( Tcl_ListObjLength( interp, children, &noChilds ) != TCL_OK 
         || noChilds < 1 )
   {
      Tcl_SetResult( interp, "widget-list must be proper list", 
            TCL_STATIC );
      return TCL_ERROR;
   }
   for( n = 0; n < noChilds; ++n )
   {
      GtkWidget   *childWidget;
      Tcl_Obj     *tp;
      const char  *childName;
      guint       key;

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

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

      if( !GTK_CHECK_TYPE( childWidget, GTK_TYPE_MENU_ITEM ) )
      {
         Tcl_AppendResult( interp, "child window \"", 
               childName, "\" is not a menu item.", (char *)NULL );
         return TCL_ERROR;
      }


      key = gnoclMenu_getKey( childWidget );
      if( key != GDK_VoidSymbol )
      {
         GtkAccelGroup *ac = gtk_menu_ensure_uline_accel_group( pp->widget );
         gtk_widget_add_accelerator( GTK_WIDGET( childWidget ), 
               "activate_item", ac, key, 0, GTK_ACCEL_LOCKED );
      }

      /* FIXME: gtk_menu_insert */
      if( atEnd )
         gtk_menu_append( pp->widget, childWidget );
      else
         gtk_menu_prepend( pp->widget, childWidget );
   }

   return TCL_OK;
}

/*
   test, if params are valid
   set params and show window with new settings
*/
static int menuConfigure( Tcl_Interp *interp, MenuParams *para, 
      MenuOptions *popt )
{
   if( popt->title.val != NULL )
      gtk_menu_set_title( para->widget, popt->title.val );
   if( popt->tearoff.changed )
   {
      if( popt->tearoff.val && para->tearoffWidget == NULL )
      {
         para->tearoffWidget = gtk_tearoff_menu_item_new();
         gtk_menu_append( para->widget, gtk_tearoff_menu_item_new() );
      }
   }
   /* FIXME: delete tearoffWidget if necessary */

   if( popt->children.val != NULL )
      addChildren( para, interp, popt->children.val, 1 );
      
   return TCL_OK;
}

static int menuFuncAdd( MenuParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int atEnd )
{
   if( objc != 3 )
   {
      Tcl_WrongNumArgs( interp, 2, objv, "widget-list" );
      return TCL_ERROR;
   }
   return addChildren( pp, interp, objv[2], atEnd );
}

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

   MenuParams *pp = (MenuParams *)data;
   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, GTK_WIDGET( pp->widget ) );
            break;

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

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

               freeOpts( &opt );
               if( ret != TCL_OK )
                  return TCL_ERROR;
            }
            break;
      case AddIdx:      /* AddIdx and EndIdx is the same */
      case BeginIdx:
      case EndIdx:
            return menuFuncAdd( pp, interp, objc, objv, idx != BeginIdx );
      case PopupIdx:
            gtk_menu_popup( pp->widget, NULL, NULL, NULL, NULL, 0, 0 );
            break;
      case PopdownIdx:
            gtk_menu_popdown( pp->widget );
            break; 
   }
   return TCL_OK;
}

int gnoclMenuCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   MenuOptions opt;
   MenuParams *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( MenuParams, 1 );
   para->widget = GTK_MENU( gtk_menu_new() );

   if( opt.tearoff.changed == 0 || opt.tearoff.val )
   {
      para->tearoffWidget = gtk_tearoff_menu_item_new();
      gtk_menu_append( para->widget, gtk_tearoff_menu_item_new() );
      gtk_widget_show_all( GTK_WIDGET( para->widget ) );
      opt.tearoff.changed = 0;
   }
   else
      para->tearoffWidget = NULL;

   ret = menuConfigure( interp, para, &opt );
   freeOpts( &opt );
   if( ret != TCL_OK )
   {
      gtk_widget_destroy( GTK_WIDGET( para->widget ) );
      return TCL_ERROR;
   }
   para->name = gnoclGetAutoWidgetId( );
   para->interp = interp;

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

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

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

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

   return TCL_OK;
}

