
/*
 * notebook.c --
 *
 * This file implements the notebook 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:
        11: made accelerators work
        09: underlined character: accelerators still don't work
   2001-05: Begin of developement
 */

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

typedef struct
{
   Gnocl_ListBool   children;
   Gnocl_BoolBool   popup;
   Gnocl_ObjBool    tabPosition;
   Gnocl_StringBool switchPageCmd;
} NotebookOptions;

static const GnoclWidgetOptions widgetOptions[] =
{
   { "-children", GNOCL_LIST, GNOCL_OFFSET( NotebookOptions, children ) },
   { "-popup", GNOCL_BOOL, GNOCL_OFFSET( NotebookOptions, popup ) },
   { "-tabPosition", GNOCL_OBJ, GNOCL_OFFSET( NotebookOptions, 
         tabPosition ) },
   { "-switchPageCmd", GNOCL_STRING, GNOCL_OFFSET( NotebookOptions, 
         switchPageCmd ) },
   { 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;
   GtkNotebook *notebook;
   char        *command;
} NotebookParams;

static void freeOpts( NotebookOptions *p )
{
   GNOCL_FREE_TCL_OPT( p->children );
   GNOCL_FREE_TCL_OPT( p->tabPosition );
   GNOCL_FREE_MEM_OPT( p->switchPageCmd );
}

static void switchPageFunc( GtkNotebook *notebook, 
      GtkNotebookPage *page, gint page_num, gpointer data )
{
   NotebookParams *p = (NotebookParams *)data;
   assert( notebook == p->notebook );
   if( p->command )  /* TODO: %-substitution? */
      Tcl_Eval( p->interp, p->command );
}

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

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

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

static void activatePageFunc( GtkWidget *page, gpointer data )
{
   GtkNotebook *notebook = GTK_NOTEBOOK( data );
   int n = 0;
   GtkWidget *pg = NULL;

   for( ;( pg = gtk_notebook_get_nth_page( notebook, n ) ) != NULL; ++n )
   {
      if( pg == page )
      {
         if( gtk_notebook_get_current_page( notebook ) != n )
            gtk_notebook_set_page( notebook, n );
         return;
      }
   }
}

static int addChildren( GtkNotebook *notebook, Tcl_Interp *interp, 
      Tcl_Obj *children, int begin )
{
   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 *contentWidget; 
      GtkWidget *bookmarkWidget;
      char      *bookmarkName;
      GtkWidget *menuWidget;
      Tcl_Obj   *subList, *tp;
      int       noMem;
      guint     accKey = GDK_VoidSymbol;

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

      if( Tcl_ListObjLength( interp, subList, &noMem ) != TCL_OK 
            || ( noMem != 2 && noMem != 3 ) )
      {
         /* if it's not a list of lists, test, if it is a single list
            with content and bookmark */
         if( noMem == 1 && (noChilds == 2 || noChilds == 3) )
         {
            noMem = noChilds;
            noChilds = 1;
            subList = children;
         }
         else
         {
            Tcl_SetResult( interp, 
                  "list must consists of two or three elements: "
                  "\"widget\" \"bookmark\" \"menu\"", TCL_STATIC );
            return TCL_ERROR;
         }
      }

      if( Tcl_ListObjIndex( interp, subList, 0, &tp ) != TCL_OK )
         return TCL_ERROR;

      /* content widget */
      contentWidget = gnoclChildNotPacked( Tcl_GetString( tp ), interp );
      if( contentWidget == NULL )
         return TCL_ERROR;

      /* bookmark widget */
      if( Tcl_ListObjIndex( interp, subList, 1, &tp ) != TCL_OK )
         return TCL_ERROR;
      bookmarkName = Tcl_GetString( tp );
      bookmarkWidget = gnoclGetWidgetFromName( bookmarkName, NULL );
      if( bookmarkWidget == NULL )
      {
         GnoclStringType type = gnoclGetStringType( tp );
         bookmarkName = gnoclGetString( tp );
         bookmarkWidget = gtk_label_new( bookmarkName );
         if( type & GNOCL_STR_UNDERLINE )
            accKey = gnoclLabelParseULine( bookmarkWidget, bookmarkName );
      }
      else if( gnoclAssertNotPacked( bookmarkWidget, interp, bookmarkName ) )
         return TCL_ERROR;
      
      if( noMem == 3 )
      {
         /* menu widget */
         char *menuName;
         if( Tcl_ListObjIndex( interp, subList, 2, &tp ) != TCL_OK )
            return TCL_ERROR;
         menuName = gnoclGetString( tp );
         menuWidget = gnoclGetWidgetFromName( menuName, NULL );
         if( menuWidget == NULL )
            menuWidget = gtk_label_new( menuName );
         else if( gnoclAssertNotPacked( menuWidget, interp, menuName ) )
            return TCL_ERROR;
      }
      else
      {
         /* TODO: clone bookmarkWidget or error message */
         menuWidget = gtk_label_new( bookmarkName );
      }

      if( begin )
         gtk_notebook_append_page_menu( notebook, contentWidget, 
               bookmarkWidget, menuWidget );
      else
         gtk_notebook_prepend_page_menu( notebook, contentWidget, 
               bookmarkWidget, menuWidget );

      if( accKey != GDK_VoidSymbol )
      {
         /* FIXME: activate only activators of active page? */
         gtk_widget_add_accelerator( GTK_WIDGET( contentWidget ), 
               "grab-focus", gnoclGetAccelGroup(), 
               accKey, GDK_MOD1_MASK, GTK_ACCEL_LOCKED );
         gtk_signal_connect( GTK_OBJECT( contentWidget ), "grab-focus", 
               GTK_SIGNAL_FUNC( activatePageFunc ), notebook );
      }

   }

   return TCL_OK;
}

/*
   test, if params are valid
   set params and show window with new settings
*/
static int notebookConfigure( Tcl_Interp *interp, NotebookParams *para, 
      NotebookOptions *popt )
{
   /* FIXME: create or delete frame if necessary 
             also change: gnoclMemNameAndWidget( name, widget );
   */

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

   if( popt->popup.changed )
   {
      if( popt->popup.val )
         gtk_notebook_popup_enable( para->notebook );
      else
         gtk_notebook_popup_disable( para->notebook );
   }

   if( popt->tabPosition.changed )
   {
      GtkPositionType type;
      if( gnoclGetPositionType( interp, popt->tabPosition.val, &type )
            != TCL_OK )
         return TCL_ERROR;

      gtk_notebook_set_tab_pos( para->notebook, type );
   }

   if( popt->switchPageCmd.changed )
   {
      if( strlen( popt->switchPageCmd.val ) == 0 )
      {
         g_free( para->command );
         para->command = NULL;
      }
      else
      {
         para->command = popt->switchPageCmd.val;
         popt->switchPageCmd.val = NULL;
      }
   }

   return TCL_OK;
}

static int notebookFuncAdd( NotebookParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int begin )
{
   typedef struct 
   {
      Gnocl_IntBool expand;
      Gnocl_IntBool fill;
      Gnocl_IntBool padding;
   } PackOptions;
   PackOptions params = { 
         { 1, 0 }, 
         { 1, 0 }, 
         { 0, 0 },
      };
   GnoclWidgetOptions options[] = 
   {
      /*
      { "-expand", GNOCL_BOOL, GNOCL_OFFSET( PackOptions, expand ) },
      { "-fill", GNOCL_BOOL, GNOCL_OFFSET( PackOptions, fill ) },
      { "-padding", GNOCL_INT, GNOCL_OFFSET( PackOptions, padding ) },
      */
      { NULL, 0, 0 }
   };

   /*
   int noChilds;
   int n;
   if( objc < 3 || objc % 2 == 0 )
   {
      Tcl_WrongNumArgs( interp, 2, objv, "widget-list ?option val ...?" );
      return TCL_ERROR;
   }
   if( Tcl_ListObjLength( interp, objv[2], &noChilds ) != TCL_OK 
         || noChilds < 1 )
   {
      Tcl_SetResult( interp, "widget-list must be proper list", 
            TCL_STATIC );
      return TCL_ERROR;
   }
   */

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

   return addChildren( pp->notebook, interp, objv[2], begin );
}

static int notebookNext( NotebookParams *pp, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int next )
{
   int cur = gtk_notebook_get_current_page( pp->notebook );

   if( next )
   {
      /* widget next [count] */
      if( objc < 2 || objc > 3 )
      {
         Tcl_WrongNumArgs( interp, 2, objv, "?count?" );
         return TCL_ERROR;
      }
      if( objc == 2 )
         next = 1;
      else if( Tcl_GetIntFromObj( interp, objv[2], &next ) != TCL_OK )
         return TCL_ERROR;
   }
   else
   {
      /* widget current */
      if( objc != 2 )
      {
         Tcl_WrongNumArgs( interp, 2, objv, "" );
         return TCL_ERROR;
      }
   }
   if( next != 0 )
   {
      gtk_notebook_set_page( pp->notebook, cur + next );
      cur = gtk_notebook_get_current_page( pp->notebook );
   }

   Tcl_SetObjResult( interp, Tcl_NewIntObj( cur ) );

   return TCL_OK;
}

static int notebookFunc( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   static char *cmds[] = { GNOCL_STD_CMD_NAMES,
         "configure", "add", "addBegin", "addEnd", 
         "currentPage", "nextPage",
         NULL };
         /* TODO: getPage k */
   enum cmdIdx { GNOCL_STD_CMD_ENUMS,
         ConfigureIdx, AddIdx, BeginIdx, EndIdx, 
         CurrentIdx, NextPageIdx };

   NotebookParams *para = (NotebookParams *)data;
   int idx;
   GtkWidget *widget = GTK_WIDGET( para->notebook );

   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:
            {
               NotebookOptions opt;
               int ret = TCL_ERROR;

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

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

   return TCL_OK;
}

int gnoclNotebookCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   /* NotebookParams *pp = g_new( NotebookParams, 1 ); */
   NotebookOptions opt;
   NotebookParams *para;
   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( NotebookParams, 1 );

   widget = gtk_notebook_new();
   para->notebook = GTK_NOTEBOOK( widget );
   para->name = gnoclGetAutoWidgetId( );
   para->interp = interp;
   para->command = NULL;
   
   ret = notebookConfigure( interp, para, &opt );
   freeOpts( &opt );
   if( ret != TCL_OK )
   {
      gtk_widget_destroy( GTK_WIDGET( para->notebook ) );
      return TCL_ERROR;
   }

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

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

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

   gtk_signal_connect( GTK_OBJECT( widget ), "switch-page", 
         GTK_SIGNAL_FUNC( switchPageFunc ), para );

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

