/*
 * gnocl.c --
 *
 * This file implements a Tcl interface to gnome and gtk+
 *
 * 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-02-19: version 0.0.10
               - "invoke" works now as in Tk (besides the return value)
               - bugfixes for all widgets with "-command" and 
                 "-variable" option
               - new widget scrolledWindow
   2002-01-21: version 0.0.9
               - added alignment to table and box
               - much work on table widget
               - new command "invoke" for most widgets with "-command" option
               - new widget eventBox
               - drag and drop support
   2001-12-21: version 0.0.8
               - remote activation via goad
               - list widget: columns can be sorted via a tcl callback 
                     function and cell text retrived
               - bind: detail for key and button event,
                     more percent substitution
               - parameter default height and width for window and app
               - toolbar icon loadable from file
               - pixmap widget
               - table is now a combination of gtk table and gtk frame
               - parameter [xy]Align and [xy]Pad for label
               - parameter icon for menuItem and optionMenu
   2001-11-18: version 0.0.7
               - scale, optionMenu, socket and plug widget
               - menu hints in appBar
               - scrollbars for canvas widget
               - accelerators for page change in notebook now work
               - new option -layout for box of boxType
   2001-10-17: version 0.0.6
               - spinButton
               - checkButton in menu
               - underlined accelerators for menu items
               - new command setValue, getValue for checkButton
   2001-09-07: version 0.0.5
               - gnome color and font picker
               - new std option -sensitive
               - %-substitution in command of checkButton
               - command addToolbar for app
               - entry widget now combination of gtk entry, gnome entry,
                 file, number, pixmap, and icon entry
   2001-07-23: version 0.0.4
               - mainLoop 
               - keyboard accelerators
               - list, text and paned widget
               - templated documentation with parsed manpages.txt
   2001-06-07: version 0.0.3
               - notebook, dialog and messageBox widget
               - rectangle, line, text, widget canvas item
               - %-identification for strings
               - tooltips
   2001-05-06: version 0.0.2
               - info command
               - radio button widget
               - gnome about dialog
               - bind on canvas items
               - much work on canvas
               - box is now a combination of gtk box and gtk frame
               - start of documentation
               - connect to signal "destroy" for cleanups
   2001-04-03: first official version (0.0.1) 
               announced in comp.lang.tcl.announce
   2001-03:    Begin of developement
 */

#include "gnocl.h"
#ifdef GNOCL_USE_BONOBO
#include <libgnorba/gnorba.h>
#include <bonobo.h>
#endif
#include "canvas.h"
#include <string.h>
#include <ctype.h>

#include <assert.h>

/*
   Brainstorm:
   - fuer jedes widget: -detachable bool -snap_edge (left|right|top|bottom)
         wird transparent über GtkHandleBox realisiert
   - fuer jedes widget: -aspect (ratio|{})
         wird transparent über GtkAspectFrame realisiert
   - gtk_object_(set|get)_data
   - gtk_object_arg_get
   - i18n!
*/

static GtkTooltips *tooltips;
static GData *widgetDataList;
static GData *canvasItemDataList;
#ifdef GNOCL_USE_BONOBO
CORBA_Environment gnoclEv;
#endif

const char idPrefix[] = "::gnocl::_WId";

static gint tclTimerFunc( gpointer data )
{
   /* Tcl_Interp *interp = (Tcl_Interp *)data; */
   while( Tcl_DoOneEvent(  TCL_DONT_WAIT ) )
      ;
   return 1;
}
int gnoclMainLoop( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   guint32 timeout = 500;

   /* TODO? flag for not looping in tclTimerFunc? */
   if( ( objc != 1 && objc != 3 ) || 
         ( objc == 3 && strcmp( Tcl_GetString( objv[1] ), "-timeout" ) ) )
   {
      Tcl_WrongNumArgs( interp, 1, objv, "?-timeout val?" );
      return TCL_ERROR;
   }

   if( objc == 3 )
   {
      long    val;
      if( Tcl_GetLongFromObj( interp, objv[2], &val ) != TCL_OK )
         return TCL_ERROR;
      /* beware of problems with casting signed -> unsigned! */
      if( val < 0 )
      {
         Tcl_SetResult( interp, 
            "Timeout value must be greater or equal zero.", TCL_STATIC );
         return TCL_ERROR;
      }
      timeout = (guint32)val;
   }

   /* if timeout == 0 don't call tcl's event loop */
   if( timeout > 0 )    
      gtk_timeout_add( timeout, tclTimerFunc, NULL );

   gtk_main();

   return 0;
}

int gnoclUpdateCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   int n = 0;
   while( gtk_events_pending() )
   {
      gtk_main_iteration_do ( 0 );
      ++n;
   }
   Tcl_SetObjResult( interp, Tcl_NewIntObj( n ) );

   return TCL_OK;
}

GtkWidget *gnoclChildNotPacked( const char *name, Tcl_Interp *interp )
{
   GtkWidget *child = gnoclGetWidgetFromName( name, interp );

   if( child == NULL )
      return NULL;

   if( gnoclAssertNotPacked( child, interp, name ) )
      return NULL;

   return child;
}

int gnoclAssertNotPacked( GtkWidget *child, Tcl_Interp *interp, 
      const char *name )
{
   GtkArg arg;
   arg.name = "parent";
   gtk_object_getv( GTK_OBJECT( child ), 1, &arg );
   if( GTK_VALUE_POINTER( arg ) != NULL )
   {
      if( name && interp )
         Tcl_AppendResult( interp, "window \"", name,
               "\" has already a parent.", (char *)NULL );
      return 1;
   }

   return 0;
}

/* 
   "char *" and not "const char *" because of a not very strict
   handling of "const char *" in Tcl e.g. Tcl_CreateObjCommand
*/
char *gnoclGetAutoWidgetId( void )
{
   static int no = 0;
   /*
   static char buffer[30];
   */

   char *buffer = g_new( char, sizeof( idPrefix ) + 10 );
   strcpy( buffer, idPrefix );

   /* with namespace, since the Id is also the widget command */
   sprintf( buffer + sizeof( idPrefix ) - 1, "%d", ++no );

   return buffer;
}

/* -----------------
   handle widget <-> name mapping
-------------------- */
GtkWidget *gnoclGetWidgetFromName( const char *id, Tcl_Interp *interp )
{
   GtkWidget *widget = NULL;

   /* shortcut to test if id *could* be a widget ID */
   if( id[0] == idPrefix[0] && id[1] == idPrefix[1] && id[2] == idPrefix[2]  )
      widget =  g_datalist_get_data( &widgetDataList, id );
   if( widget == NULL && interp != NULL )
      Tcl_AppendResult( interp, "There exists no window with name \"", 
               id, "\".", (char *)NULL );

   return widget;
}

int gnoclMemNameAndWidget( const char *path, GtkWidget *widget )
{
   assert( g_datalist_get_data( &widgetDataList, path ) == NULL );

   g_datalist_set_data( &widgetDataList, path, widget );
   return 0;
}
int gnoclForgetWidgetFromName( const char *path )
{
   g_datalist_remove_data( &widgetDataList, path );
   return 0;
}

/* -----------------
   handle canvas item <-> name mapping
-------------------- */
Gnocl_CanvasItemInfo *gnoclGetCanvasInfoFromName( const char *itemName )
{
   return g_datalist_get_data( &canvasItemDataList, itemName );
}
int gnoclMemNameAndCanvasInfo( int itemId, Gnocl_CanvasItemInfo *item )
{
   char itemName[128];
   sprintf( itemName, "%d", itemId );

   assert( g_datalist_get_data( &canvasItemDataList, itemName ) == NULL );

   g_datalist_set_data( &canvasItemDataList, itemName, item );
   return 0;
}
int gnoclForgetCanvasInfoFromName( const char *itemName )
{
   g_datalist_remove_data( &canvasItemDataList, itemName );
   return 0;
}

/* -----------------
   handle canvas item <-> name mapping
-------------------- */
int gnoclDelete( Tcl_Interp *interp, GtkWidget *widget,
      int objc, Tcl_Obj * const objv[] )
{
   if( objc != 2 )
   {
      /* widget delete */
      Tcl_WrongNumArgs( interp, 1, objv, "" );
      return TCL_ERROR;
   }
   /* TODO: all children*/
   gtk_widget_destroy( widget );
   return TCL_OK;
}
int gnoclShow( Tcl_Interp *interp, GtkWidget *widget,
      int objc, Tcl_Obj * const objv[] )
{
   if( objc != 2 )
   {
      /* widget show */
      Tcl_WrongNumArgs( interp, 1, objv, "" );
      return TCL_ERROR;
   }
   gtk_widget_show_all( widget );
   return TCL_OK;
}
int gnoclHide( Tcl_Interp *interp, GtkWidget *widget,
      int objc, Tcl_Obj * const objv[] )
{
   if( objc != 2 )
   {
      /* widget hide */
      Tcl_WrongNumArgs( interp, 1, objv, "" );
      return TCL_ERROR;
   }
   gtk_widget_hide_all( widget );
   return TCL_OK;
}

static int editableGetLength( GtkEditable *editable )
{
   int len;

   if( GTK_IS_TEXT( editable ) )
      len = gtk_text_get_length( GTK_TEXT( editable ) );
   else
   {
      char *p =  gtk_editable_get_chars( editable, 0, -1 ); 
      len = strlen( p );
      g_free( p );
   }
   return len;
}

int gnoclEditablePosToIndex( Tcl_Interp *interp, Tcl_Obj *obj, 
      GtkEditable *editable, int *pidx )
{
   if( Tcl_GetIntFromObj( NULL, obj, pidx ) != TCL_OK )
   {
      const char *txt = Tcl_GetString( obj );
      int idx;
      const char *last;
      if( strncmp( txt, "end", 3 ) == 0 )
      {
         idx = editableGetLength( editable );
         last = txt + 3;
      }
      else if( strncmp( txt, "cursor", 6 ) == 0 )
      {
         /* idx = gtk_text_get_point( text ); */
         idx = gtk_editable_get_position( editable );
         last = txt + 6;
      }
      else if( strncmp( txt, "selectionStart", 14 ) == 0 )
      {
         idx = editable->selection_start_pos;
         last = txt + 14;
      }
      else if( strncmp( txt, "selectionEnd", 12 ) == 0 )
      {
         idx = editable->selection_end_pos;
         last = txt + 12;
      }
      else
      {
         Tcl_AppendResult( interp, "unknown index \"", txt, 
               "\", must be one of end, cursor, selectionStart, " 
               "or selectionEnd", NULL );
         return TCL_ERROR;
      }
      /* pos[+-]offset */
      if( *last == '+' || *last == '-' )
      {
         int offset;
         ++last;
         if( sscanf( last, "%d", &offset ) != 1 )
         {
            Tcl_AppendResult( interp, "invalid offset \"", last + 1, 
                  "\"", NULL );
            return TCL_ERROR;
         }
         if( *last == '+' )
            idx += offset;
         else
            idx -= offset;
         while( isdigit( *last ) )
            ++last;
      }
      if( *last )
      {
         Tcl_AppendResult( interp, "invalid appendix \"", last, 
               "\"", NULL );
         return TCL_ERROR;
      }
      *pidx = idx;
   }
   return TCL_OK;
}

int gnoclHandleEditableCmds( int idx, Tcl_Interp *interp, int objc, 
      Tcl_Obj * const objv[], GtkEditable *editable )
{
   switch( idx )
   {
      case 0:  /* RemoveIdx */
      case 1:  /* SelectIdx: */
      case 5:  /* GetCharsIdx: */
            {
               int startIndex, endIndex;
               /* text remove/select/getChars startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "?startIndex endIndex?" );
                  return TCL_ERROR; 
               }
               if( objc >= 3 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[2], editable, 
                        &startIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  startIndex = 0;

               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], editable, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = editableGetLength( editable );
               
               switch( idx )
               {
                  case 0:  /* RemoveIdx */
                           gtk_editable_delete_text( editable, 
                                 startIndex, endIndex );
                           break;
                  case 1:  /* SelectIdx: */
                           if( startIndex >= endIndex )
                              gtk_editable_delete_selection( editable );
                           else
                              gtk_editable_select_region( editable, 
                                    startIndex, endIndex );
                           break;
                  case 5:  /* GetCharsIdx: */
                           {
                              char *txt = gtk_editable_get_chars( 
                                    editable, startIndex, endIndex );
                              Tcl_SetObjResult( interp, 
                                    Tcl_NewStringObj( txt, -1 ) );
                           }
                           break;
               }
            }
            break;
      case 2:  /* CutIdx: */
      case 3:  /* CopyIdx: */
      case 4:  /* PasteIdx: */
            {
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "" );
                  return TCL_ERROR; 
               }
               switch( idx )
               {
                  case 2:  /* CutIdx: */
                     gtk_editable_cut_clipboard( editable ); 
                     break;
                  case 3:  /* CopyIdx: */
                     gtk_editable_copy_clipboard( editable ); 
                     break;
                  case 4:  /* PasteIdx: */
                     gtk_editable_paste_clipboard( editable ); 
                     break;
               }
            }
            break;
      case 6:  /* GetLengthIdx: */
            {
               /* editable getLength */
               if( objc != 2 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "" );
                  return TCL_ERROR; 
               }

               Tcl_SetObjResult( interp, Tcl_NewIntObj( 
                     editableGetLength( editable ) ) );
            }
            break;


      default: assert( 0 ); return TCL_ERROR;
   }
   return TCL_OK;
}

#if 0
     case 0:  /* RemoveIdx */
            {
               int startIndex, endIndex;
               /* text remove startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "startPos ?endPos?" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], editable, 
                     &startIndex ) != TCL_OK )
                  return TCL_ERROR;
               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], editable, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = startIndex + 1;
                  
               gtk_editable_delete_text( editable, startIndex, endIndex );
            }
            break;
      
      case 1:  /* SelectIdx: */
            {
               int startIndex, endIndex;
               /* text remove startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "startIndex ?endIndex?" );
                  return TCL_ERROR; 
               }
               if( gnoclEditablePosToIndex( interp, objv[2], editable, 
                     &startIndex ) != TCL_OK )
                  return TCL_ERROR;
               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], editable, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = startIndex + 1;
                  
               if( startIndex >= endIndex )
                  gtk_editable_delete_selection( editable );
               else
                  gtk_editable_select_region( editable, startIndex, 
                        endIndex );
            }
            break;
      case 5:  /* GetCharsIdx: */
            {
               int startIndex, endIndex;
               char *txt;
               /* text getChars startIndex ?endIndex? */
               if( objc < 3 || objc > 4 )
               {
                  Tcl_WrongNumArgs( interp, 2, objv, "?startIndex endIndex?" );
                  return TCL_ERROR; 
               }
               if( objc >= 3 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[2], editable, 
                        &startIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  startIndex = 0;

               if( objc >= 4 )
               {
                  if( gnoclEditablePosToIndex( interp, objv[3], editable, 
                        &endIndex ) != TCL_OK )
                     return TCL_ERROR;
               }
               else
                  endIndex = editableGetLength( editable );

               txt = gtk_editable_get_chars( editable, startIndex, endIndex );
               Tcl_SetObjResult( interp, Tcl_NewStringObj( txt, -1 ) );
            }
            break;
#endif

/*
   Prefix Explanation
   %%     % plus rest of string as is
   %!     rest of string as is
   %#     stock text/icon/accelerator/... first '_' is underline character
   %_     text with underline

   else:  use defaultStringPrefix
   
   FIXME: what about translation?
*/

GnoclStringType gnoclGetStringType( Tcl_Obj *obj )
{
   char *name = Tcl_GetString( obj );
   if( *name == 0 )
      return GNOCL_STR_EMPTY;
   if( *name == '%' )
   {
      /* if something is changed here, also change gnoclGetStringFromObj! */
      switch( name[1] )
      {
         case '!': 
         case '%': return GNOCL_STR_STR;

         case '#': return GNOCL_STR_STOCK | GNOCL_STR_UNDERLINE;
         case '/': return GNOCL_STR_FILE;
         case '_': return GNOCL_STR_UNDERLINE;
      }
   }

   return GNOCL_STR_STR;
}

char *gnoclGetEscStringFromObj( Tcl_Obj *op, int *len, int useEscape )
{
   /* FIXME: how to use UTF-8? */
   char *ret;
   if( op == NULL )
      return NULL;

   ret = Tcl_GetStringFromObj( op, len );
   /* FIXME: ugly, but Tcl_GetByteArrayFromObj seems to be the right
      function for UTF-8 
   ret = (const char *)Tcl_GetByteArrayFromObj( op, len ); 
   ret = (const char *)Tcl_SetByteArrayLength( op, *len + 1 );
   ((char *)ret)[*len] = '\0';
   */

   if( useEscape && *ret == '%' )
   {
   /* printf( "stringEscape: %s", ret ); */
      /* if something is changed here, also change gnoclGetStringType! */
      switch( ret[1] )
      {
         case '/':                        /* GNOCL_STR_FILE */
         case '#':                        /* GNOCL_STR_STOCK */
         case '_':                        /* GNOCL_STR_UNDERLINE */
         case '!':   ret += 2;            /* GNOCL_STR_STR */ 
                     if( len )   *len -= 2; 
                     break; 
         case '%':   ret += 1;            /* escaped % */
                     if( len )   *len -= 1; 
                     break; 
         /* translate
         case ???:   ret = _( ret + 2 );
                     if( len )   *len = strlen( ret );
                     break;
         */
      }
   /* printf( " -> %s\n", ret ); */
   }
   return ret;
}
char *gnoclGetStringFromObj( Tcl_Obj *op, int *len )
{
   /* TODO: global flag "gnoclNoStringEscape"? */
   return gnoclGetEscStringFromObj( op, len, 1 );
}

#if 0
char *gnoclGetStringUline( Tcl_Obj *op, char **pattern )
{
   int   len;
   const char *str, *up, *p; 
   char *ret;
   int  ncp = 0;
   if( op == NULL )
      return NULL;

   str = Tcl_GetStringFromObj( op, &len );
   ret = g_new( char, len + 1 );

   if( pattern )
      *pattern = NULL;

   for( up = str; ( p = strchr( up, '_' ) ) != NULL; )
   {
      /* with '\\' escaped '_' */
      if( p != str && p[-1] == '\\' )
      {
         memcpy( ret + ncp, up, p - up - 1 );
         ncp += p - up;
         ret[ncp - 1] = '_';
         up = p + 1;
      }
      else
      {
         memcpy( ret + ncp, up, p - up );
         ncp += p - up;
         up = p + 1;
         if( pattern )
         {
            *pattern = g_new( char, ncp + 2 );
            memset( *pattern, ' ', ncp );
            (*pattern)[ncp] = '_';
            (*pattern)[ncp+1] = '\0';
         }
         break;
      }
   }

   memcpy( ret + ncp, up, len + 1 - ncp );

   return ret;
}
#endif

char *gnoclGetString( Tcl_Obj *op )
{
   return gnoclGetStringFromObj( op, NULL );
}

char *gnoclStringDup( Tcl_Obj *op )
{
   int txtLen;
   const char *txt;
   if( op == NULL )
      return NULL;

   txt = gnoclGetStringFromObj( op, &txtLen );
   return g_memdup( txt, txtLen + 1 );
}


static char *gnoclGetStockName( const char *type, const char *error,
      Tcl_Interp *interp, Tcl_Obj *obj )
{
   int  len;
   const char *name = gnoclGetEscStringFromObj( obj, &len, 1 );   
   char *ret = g_new( char, strlen( type ) + len + 2 );
   const char *pu;

   strcpy( ret, type );
   strcat( ret, "_" );
   /* skip first '_' in name, used for underline a character */
   pu = strchr( name, '_' );
   if( pu )
   {
      strncat( ret, name, pu - name );
      strcat( ret, pu + 1 );
   }
   else
      strcat( ret, name );

   if( gnome_stock_pixmap_checkfor( ret, NULL ) == NULL )
   {
      g_free( ret );
      if( interp != NULL )
         Tcl_AppendResult( interp, "unknown stock ", error, 
               " \"", name, "\"", (char *)NULL );
      return NULL;
   }
   return ret;
}

char *gnoclGetStockButtonName( Tcl_Interp *interp, Tcl_Obj *obj )
{
   assert( gnoclGetStringType( obj ) | GNOCL_STR_STOCK );
   return gnoclGetStockName( "Button", "button", interp, obj );
}

char *gnoclGetStockMenuName( Tcl_Interp *interp, Tcl_Obj *obj, char **txt )
{
   char *ret;
   assert( gnoclGetStringType( obj ) | GNOCL_STR_STOCK );
   ret = gnoclGetStockName( "Menu", "menu", interp, obj );
   if( ret != NULL && txt != NULL )
   {
      int len;
      const char *t = gnoclGetStringFromObj( obj, &len );
      *txt = g_memdup( t, len + 1 );
   }
   return ret;
}

int gnoclGetPixmap( Tcl_Interp *interp, GtkWidget *window,
      Tcl_Obj *obj, GtkWidget **widget )
{
#if 0
   char *opts[] = { "New", "Open", "Close", "Revert", "Save", "Save As",
         "Cut", "Copy", "Paste", "Clear", "Properties", "Preferences",
         "Help", "Scores", "Print", "Search", "Search/Replace", "Back",
         "Forward", "First", "Last", "Home", "Stop", "Refresh", "Undo",
         "Redo", "Timer", "Timer Stopped", "Mail", "Receive Mail",
         "Send Mail", "Reply to Mail", "Forward Mail", "New Mail", "Trash",
         "Trash Full", "Undelete", "Spellchecker", "Microphone", "Line In",
         "Cdrom", "Volume", "Midi", "Book Red", "Book Green", "Book Blue",
         "Book Yellow", "Book Open", "About", "Quit", "Multiple", "Not",
         "Convert", "Jump To", "Up", "Down", "Top", "Bottom", "Attach",
         "Index", "Font", "Exec", "Left", "Right", "Center", "Justify",
         "Bold", "Italic", "Underline", "Strikeout", "Text Indent",
         "Text Unindent", "Color Select", "Add", "Remove", "Table Borders",
         "Table Fill", "Text Bulleted List", "Text Numbered List", 
         "regular", "disabled", "focused",
         (char *)NULL };
#endif
   GnoclStringType type = gnoclGetStringType( obj );
   if( type == GNOCL_STR_EMPTY )
   {
      *widget = NULL;
   }
   else if( type & GNOCL_STR_STOCK )
   {
   #if 0
      int     len;
      char    *txt = gnoclGetStringFromObj( obj, &len );
      Tcl_Obj *obj2 = Tcl_NewStringObj( txt, len );
      int     idx;
      int     ret = Tcl_GetIndexFromObj( interp, obj2, opts, "option", 
                        TCL_EXACT, &idx );
      Tcl_DecrRefCount( obj2 );
      if( ret != TCL_OK )
         return ret;

      *widget = gnome_stock_pixmap_widget( window, opts[idx] );
   #endif
      char    *txt = gnoclGetString( obj );
      *widget = gnome_stock_pixmap_widget( window, txt );
      if( *widget == NULL )
      {
         Tcl_AppendResult( interp, "Unknown stock pixmap \"", 
               txt, "\".", (char *)NULL );
         return TCL_ERROR;
      }
   }
   else if( type == GNOCL_STR_FILE )
   {
      char   *txt = gnoclGetStringFromObj( obj, NULL );
      *widget = gnome_pixmap_new_from_file( txt );
   }
   else
   {
      Tcl_AppendResult( interp, "invalid pixmap type for \"", 
            Tcl_GetString( obj ), "\"", (char *)NULL );
      return TCL_ERROR;
   }

   return TCL_OK;
}

/* ----------
   <- < 0: Error
      >= 0: number of objv processed
------------- */
int gnoclParseWidgetOpts( Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], 
      const GnoclWidgetOptions *opts, char *params )
{
   int n = 1;
   for( ; n < objc; n += 2 )
   {
      char *offset = params;
      int idx;

      /* FIXME: bug in Tcl_GetIndexFromObjStruct 8.3.2 for cached values */
      {
         Tcl_Obj *objPtr = objv[n];
         if( objPtr->typePtr != NULL ) 
         {
            if( objPtr->typePtr->freeIntRepProc != NULL ) 
               objPtr->typePtr->freeIntRepProc(objPtr);
            objPtr->typePtr = NULL;
         }
      }

      if( Tcl_GetIndexFromObjStruct( interp, objv[n], (char **)&opts[0].name, 
            sizeof( GnoclWidgetOptions ), "option", TCL_EXACT, &idx ) 
            != TCL_OK )
         return -1;
      
      if( n == objc - 1 )
      {
         if( interp != NULL )
            Tcl_AppendResult( interp, "no value given for parameter \"", 
                  Tcl_GetString( objv[n] ) , "\"", (char *)NULL );
         return -1;
      }

      offset += opts[idx].offset;

#if 0
do {
   GnoclWidgetOptions *po = opts;
   for( ; po->name; ++po )
      printf( "%d %s type: %d offset %d\n", 
            po-opts, po->name, po->type, po->offset );

   printf( "%p opts: %d \"%s\" \"%s\" idx: %d type: %d offset: %d %p\n", 
         &opts[0].name, n, Tcl_GetString( objv[n] ), Tcl_GetString( objv[n+1] ), 
         idx, opts[idx].type, opts[idx].offset, offset );
}while( 0 );
#endif
      /* this cast is not 100% correct, but does it matter? */
      if( ((Gnocl_BoolBool *)offset)->changed )
      {
         Tcl_AppendResult( interp, "parameter \"", 
               Tcl_GetString( objv[n] ) , "\" defined more than once", 
               (char *)NULL );
         ((Gnocl_BoolBool *)offset)->changed = -1;
         return TCL_ERROR;
      }

      switch( opts[idx].type )
      {
         case GNOCL_STRING:
               {
                  Gnocl_StringBool *p = (Gnocl_StringBool *)offset;
                  int txtLen;
                  const char *txt = gnoclGetStringFromObj( objv[n+1], 
                        &txtLen );

                  assert( p->val == NULL );
                  assert( p->changed == 0 );

                  p->val = g_memdup( txt, txtLen + 1 );
                  p->changed = 1;
               }
               break;
         case GNOCL_BOOL:
               {
                  Gnocl_BoolBool *p = (Gnocl_BoolBool *)offset;
                  assert( p->changed == 0 );

                  if( Tcl_GetBooleanFromObj( interp, objv[n+1], &p->val ) 
                        != TCL_OK )
                  {
                     p->changed = -1;
                     return -1;
                  }
                  p->changed = 1;
               }
               break;
         case GNOCL_INT:
               {
                  Gnocl_IntBool *p = (Gnocl_IntBool *)offset;
                  assert( p->changed == 0 );

                  p->changed = 1;
                  if( Tcl_GetIntFromObj( interp, objv[n+1], &p->val ) 
                        != TCL_OK )
                  {
                     p->changed = -1;
                     return -1;
                  }
               }
               break;
         case GNOCL_DOUBLE:
               {
                  Gnocl_DoubleBool *p = (Gnocl_DoubleBool *)offset;
                  assert( p->changed == 0 );

                  if( Tcl_GetDoubleFromObj( interp, objv[n+1], 
                        &p->val ) != TCL_OK )
                  {
                     p->changed = -1;
                     return -1;
                  }
                  p->changed = 1;
               }
               break;
         case GNOCL_OBJ:
               {
                  Gnocl_ObjBool *p = (Gnocl_ObjBool *)offset;
                  assert( p->val == NULL );
                  assert( p->changed == 0 );

                  /*
                  if( p->val )
                     Tcl_DecrRefCount( p->val );
                  */
                  p->val = objv[n+1];
                  Tcl_IncrRefCount( p->val );
                  p->changed = 1;
               }
               break;
         case GNOCL_LIST:
               {
                  Gnocl_ListBool *p = (Gnocl_ListBool *)offset;
                  int len = 0;
                  assert( p->changed == 0 );
                  assert( p->val == NULL );

                  if( Tcl_ListObjLength( interp, objv[n+1], &len ) != TCL_OK )
                  {
                     p->changed = -1;
                     return -1;
                  }
                  /*
                  if( p->val )
                     Tcl_DecrRefCount( p->val );
                  */
                  p->val = objv[n+1];
                  Tcl_IncrRefCount( p->val );
                  p->changed = 1;
               }
               break;
         default:
               assert( 0 );
      }
   }

   return n;
}

int gnoclParseAllWidgetOpts( Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[], int objcOffset,
      const GnoclWidgetOptions *opts, char *params )
{
   int ret = gnoclParseWidgetOpts( interp, objc - objcOffset, 
         objv + objcOffset, opts, params );
   if( ret < 0 )
      return TCL_ERROR;
   if( ret + objcOffset != objc )
      return TCL_ERROR; /* FIXME: Tcl_SetResult() ? */
   return TCL_OK;
}


int gnoclInfoCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   /* info command */
   static char *cmd[] = { "version", "gtkVersion",
         NULL };
   enum optIdx { VersionIdx, GtkVersionIdx };
   int idx;

   if( objc != 2 )
   {
      Tcl_WrongNumArgs( interp, 1, objv, "option" );
      return TCL_ERROR;
   }
   if( Tcl_GetIndexFromObj( interp, objv[1], cmd, "option", TCL_EXACT,
         &idx ) != TCL_OK )
      return TCL_ERROR;
   switch( idx )
   {
      case VersionIdx:     
            Tcl_SetObjResult( interp, Tcl_NewStringObj( VERSION, -1 ) );
            break;
      case GtkVersionIdx:
            {
               char buffer[128];
               sprintf( buffer, "%d.%d.%d", gtk_major_version,
                     gtk_minor_version, gtk_micro_version );
               Tcl_SetObjResult( interp, Tcl_NewStringObj( buffer, -1 ) );
            }
            break;
   }
   return TCL_OK;
}


int gnoclFileSelectionCmd( ClientData data, Tcl_Interp *interp,
      int objc, Tcl_Obj * const objv[] )
{
   int  n, idx; 
   static char *opts[] = { "-text", "-command", "-tooltip",
         NULL };
   enum optIdx { TxtIdx, CmdIdx, TooltIdx };
   const char *txt = NULL;
   const char *cmd = NULL;
   const char *ttip = NULL;
   const char *path = NULL;
   int txtLen, cmdLen, ttipLen, pathLen;
   GtkWidget *widget;

   if( objc < 2 || objc % 2 == 1 )
   {
      Tcl_WrongNumArgs( interp, 1, objv, "name ?option val ...?" );
      return TCL_ERROR;
   }

   /* TODO: test, if path is valid */
   path = gnoclGetStringFromObj( objv[1], &pathLen );
   for( n = 2; n < objc; n += 2 )
   {
      if( Tcl_GetIndexFromObj( interp, objv[n], opts, "option", TCL_EXACT,
            &idx ) != TCL_OK )
         return TCL_ERROR;
      switch( idx )
      {
         /* TODO: Unicode */
         case TxtIdx:   txt = gnoclGetStringFromObj( objv[n+1], &txtLen );
                        break;
         case CmdIdx:   cmd = gnoclGetStringFromObj( objv[n+1], &cmdLen );
                        break;
         case TooltIdx: ttip = gnoclGetStringFromObj( objv[n+1], &ttipLen );
                        break;
      }
   }
   widget = gtk_file_selection_new( txt );
   gtk_widget_show( widget );

   return TCL_OK;
}

/* ----------------------
   gnocl.c 
------------------------- */
typedef struct 
{
   char *name;
   Tcl_ObjCmdProc *proc;
} GnoclCmd;

Tcl_ObjCmdProc gnoclInfoCmd;
Tcl_ObjCmdProc gnoclBindCmd;
Tcl_ObjCmdProc gnoclDndCmd;

Tcl_ObjCmdProc gnoclButtonCmd;
Tcl_ObjCmdProc gnoclBoxCmd;
Tcl_ObjCmdProc gnoclCheckButtonCmd;
Tcl_ObjCmdProc gnoclComboCmd;
Tcl_ObjCmdProc gnoclEntryCmd;
Tcl_ObjCmdProc gnoclEventBoxCmd;
Tcl_ObjCmdProc gnoclLabelCmd;
Tcl_ObjCmdProc gnoclClistCmd;
Tcl_ObjCmdProc gnoclMenuCmd;
Tcl_ObjCmdProc gnoclMenuBarCmd;
Tcl_ObjCmdProc gnoclMenuItemCmd;
Tcl_ObjCmdProc gnoclNotebookCmd;
Tcl_ObjCmdProc gnoclOptionMenuCmd;
Tcl_ObjCmdProc gnoclPanedCmd;
Tcl_ObjCmdProc gnoclPlugCmd;
Tcl_ObjCmdProc gnoclRadioButtonCmd;
Tcl_ObjCmdProc gnoclScaleCmd;
Tcl_ObjCmdProc gnoclScrolledWindowCmd;
Tcl_ObjCmdProc gnoclSocketCmd;
Tcl_ObjCmdProc gnoclSpinButtonCmd;
Tcl_ObjCmdProc gnoclStatusbarCmd;
Tcl_ObjCmdProc gnoclTableCmd;
Tcl_ObjCmdProc gnoclTextCmd;
Tcl_ObjCmdProc gnoclToolbarCmd;
Tcl_ObjCmdProc gnoclWindowCmd;
#ifdef GNOCL_USE_GNOME
Tcl_ObjCmdProc gnoclAboutCmd;
Tcl_ObjCmdProc gnoclAppCmd;
Tcl_ObjCmdProc gnoclAppBarCmd;
Tcl_ObjCmdProc gnoclCanvasCmd;
Tcl_ObjCmdProc gnoclColorPickerCmd;
Tcl_ObjCmdProc gnoclDialogCmd;
Tcl_ObjCmdProc gnoclFontPickerCmd;
Tcl_ObjCmdProc gnoclPixmapCmd;
#ifdef GNOCL_USE_BONOBO
Tcl_ObjCmdProc gnoclBonoboCmd;
#endif
#endif

static GnoclCmd commands[] = {
   { "info",            gnoclInfoCmd },
   { "bind",            gnoclBindCmd },
   { "dnd",             gnoclDndCmd },
   { "mainLoop",        gnoclMainLoop },
   { "update",          gnoclUpdateCmd },

   /* Gtk Widgets */
   { "button",          gnoclButtonCmd },
   { "box",             gnoclBoxCmd }, 
   { "checkButton",     gnoclCheckButtonCmd },
   { "combo",           gnoclComboCmd },
   { "entry",           gnoclEntryCmd },
   { "eventBox",        gnoclEventBoxCmd },
   { "fileSelection",   gnoclFileSelectionCmd },
   { "frame",           gnoclBoxCmd }, /* alias for "box" */
   { "label",           gnoclLabelCmd },
   { "clist",           gnoclClistCmd },
   { "menu",            gnoclMenuCmd },
   { "menubar",         gnoclMenuBarCmd },
   { "menuItem",        gnoclMenuItemCmd },
   { "notebook",        gnoclNotebookCmd },
   { "optionMenu",      gnoclOptionMenuCmd },
   { "paned",           gnoclPanedCmd },
   { "plug",            gnoclPlugCmd },
   { "radioButton",     gnoclRadioButtonCmd },
   { "scale",           gnoclScaleCmd },
   { "scrolledWindow",  gnoclScrolledWindowCmd },
   { "socket",          gnoclSocketCmd },
   { "spinButton",      gnoclSpinButtonCmd },
   { "statusbar",       gnoclStatusbarCmd },
   { "table",           gnoclTableCmd },
   { "text",            gnoclTextCmd },
   { "toolbar",         gnoclToolbarCmd },
   { "window",          gnoclWindowCmd },

#ifdef GNOCL_USE_GNOME
   /* Gnome Widgets */
   { "about",           gnoclAboutCmd },
   { "app",             gnoclAppCmd },
   { "appBar",          gnoclAppBarCmd },
   { "canvas",          gnoclCanvasCmd },
   { "colorPicker",     gnoclColorPickerCmd },
   { "dialog",          gnoclDialogCmd },
   { "fontPicker",      gnoclFontPickerCmd },
   { "pixmap",          gnoclPixmapCmd },
#ifdef GNOCL_USE_BONOBO
   { "bonobo",          gnoclBonoboCmd },
#endif
#endif
   { NULL,           NULL },
};

/*
 *----------------------------------------------------------------------
 *
 * eventSetupProc --
 *
 *      This procedure implements the setup part of the gtk 
 *      event source.  It is invoked by Tcl_DoOneEvent before entering
 *      the notifier to check for events on all displays.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      ??? TODO
 *      the maximum
 *      block time will be set to 0 to ensure that the notifier returns
 *      control to Tcl even if there is no more data on the X connection.
 *
 *----------------------------------------------------------------------
 */
static void eventSetupProc( ClientData clientData, int flags)
{
   /* 
      TODO: make blockTime configurable 
            via Tcl_GetVar( "::gnocl::blockTime" )
   */
   Tcl_Time blockTime = { 0, 10000 };

   if( !(flags & TCL_WINDOW_EVENTS) )
   {
      /* under which circumstances do we get here? */
      return;
   }

   /* 
      is this the correct way? 
      polling is bad, but how to do better?
   */
   Tcl_SetMaxBlockTime( &blockTime ); 

   return;
}
/*
 *----------------------------------------------------------------------
 *
 * eventCheckProc --
 *
 *      This procedure checks for events sitting in the gtk event
 *      queue.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Moves queued events onto the Tcl event queue.
 *
 *----------------------------------------------------------------------
 */
 
typedef struct 
{
   Tcl_Event header;      /* Standard information for all events. */
   GdkEvent event;
} GnoclEvent;

/*
 *----------------------------------------------------------------------
 *
 * gnoclEventProc --
 *
 *      This procedure is called by Tcl_DoOneEvent when a window event
 *      reaches the front of the event queue.  This procedure is responsible
 *      for actually handling the event.
 *
 * Results:
 *      Returns 1 if the event was handled, meaning it should be removed
 *      from the queue.  Returns 0 if the event was not handled, meaning
 *      it should stay on the queue.  The event isn't handled if the
 *      TCL_WINDOW_EVENTS bit isn't set in flags, if a restrict proc
 *      prevents the event from being handled.
 *
 * Side effects:
 *      Whatever the event handlers for the event do.
 *
 *----------------------------------------------------------------------
 */


static int gnoclEventProc( Tcl_Event *evPtr, int flags )
{
   /* 
      with while loop more responsive than 
      with Tcl_SetMaxBlockTime( 0 ); 
      is this the right way?
   */
   while( gtk_events_pending() )
      gtk_main_iteration_do ( 0 );
   return 1;
}

static void eventCheckProc( ClientData clientData, int flags)
{
   if( !(flags & TCL_WINDOW_EVENTS) )
   {
      /* under which circumstances do we get here? */
      return;
   }

   /* printf( "pending: %d\n", gtk_events_pending() ); */
   if( gtk_events_pending() )
   {
      /* Tcl_Time blockTime = { 0, 0 }; */
      GnoclEvent *gp = (GnoclEvent *)ckalloc( sizeof( GnoclEvent ) );
      gp->header.proc = gnoclEventProc;
      Tcl_QueueEvent( (Tcl_Event *)gp, TCL_QUEUE_TAIL );

      /* Tcl_SetMaxBlockTime( &blockTime ); */

      /* gp->event = *gtk_get_current_event( ); */
   }

   return;
}

static GnomeAppBar *gnoclAppBar = NULL;
int gnoclRegisterHintAppbar( GtkWidget *widget, GnomeAppBar *appBar )
{
   gnoclAppBar = appBar;
   return 0;
}

GnomeAppBar *gnoclGetHintAppbar( GtkWidget *widget )
{
   if( gnoclAppBar != NULL )
   {
      GtkWidget *top = gtk_widget_get_toplevel( GTK_WIDGET( gnoclAppBar ) );
      GtkWidget *wTop = widget->parent;

      /*
      if( top == wTop )
         return gnoclAppBar;
      else 
      */
      while( wTop != NULL && GTK_CHECK_TYPE( wTop, GTK_TYPE_MENU ) )
      {
         wTop = gtk_menu_get_attach_widget( GTK_MENU( wTop ) );
         /* this happens for popup menus */
         if( wTop == NULL )
            break;
         if( gtk_widget_get_toplevel( wTop ) == top )
            return gnoclAppBar;
         wTop = wTop->parent;
      }
      printf( "DEBUG: gnoclAppBar not found\n" );
   }

   return NULL;
}


static void putHintInAppbar( GtkWidget* menuitem, gpointer data )
{
   /*
   MenuLabelParams *para = (MenuLabelParams *)data;
   const char *txt = gtk_object_get_data( GTK_OBJECT(menuitem),
         "gnocl_tooltip" );
   */
   const char *txt = (const char *)data;
   GnomeAppBar *appBar = gnoclGetHintAppbar( GTK_WIDGET( menuitem ) );
   if( appBar )
      gnome_appbar_set_status( appBar, txt );
}

static void removeHintFromAppbar( GtkWidget *menuitem, gpointer data )
{
   GnomeAppBar *appBar = gnoclGetHintAppbar( GTK_WIDGET( data ) );
   if( appBar )
      gnome_appbar_refresh( appBar );
}


int gnoclAddTooltip( GtkWidget *widget, const char *txt, 
      const char *txtPriv )
{
   /*
   gtk_object_set_data( GTK_OBJECT( widget ), "gnocl_tooltip", txt );
   */

   if( GTK_CHECK_TYPE( widget, GTK_TYPE_ITEM ) )
   {
      gtk_signal_connect ( GTK_OBJECT( widget ),
            "select", GTK_SIGNAL_FUNC(putHintInAppbar), 
            g_memdup( txt, strlen( txt ) + 1 ) );
      
      gtk_signal_connect( GTK_OBJECT( widget ),
            "deselect", GTK_SIGNAL_FUNC( removeHintFromAppbar), widget );
   }
   else
      gtk_tooltips_set_tip( tooltips, widget, txt, txtPriv );

   return 0;
}

GtkAccelGroup *gnoclGetAccelGroup( )
{
   return gtk_accel_group_get_default( );
}

/*
#include <bonobo.h>
#include <libgnorba/gnorba.h>

void doBonobo( void )
{
   int argc = 1;
   char *argv[] = { "gnocl", (char *)NULL };
   char **p = argv;

   orb = oaf_init( argc, p );
   if( orb == NULL )
      g_error( "Failure in initializing oaf" );

   if( bonobo_init( orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL ) == FALSE )
      g_error( "Failure in initializing bonobo" );

}
*/

int Gnocl_Init( Tcl_Interp *interp )
{
   int a;
   int argc = 1;
   char *argv[] = { "gnocl", (char *)NULL };
   const char *appId;
   const char *appVersion;
#ifdef GNOCL_USE_BONOBO
   CORBA_ORB orb;
#else
   char **p = argv;  /* ??? */
#endif

   if( Tcl_InitStubs( interp, "8.3", 0 ) == NULL )
      return TCL_ERROR;

   /* FIXME: - get argc and argv from tcl */
   appId = Tcl_GetVar( interp, "::gnocl::appId", 0 );
   appVersion = Tcl_GetVar( interp, "::gnocl::appVersion", 0 );

#ifdef GNOCL_USE_GNOME
   #ifdef GNOCL_USE_BONOBO
   /* TODO: Tcl_GetVar( "::gnocl::useBonobo" ) */
   CORBA_exception_init (&gnoclEv);
   orb = gnome_CORBA_init( appId ? appId : "gnocl", 
         appVersion ? appVersion : VERSION, 
         &argc, argv, 0, &gnoclEv );
   if( orb == NULL )
   {
      Tcl_SetResult( interp, 
            "Failure in initializing gnome/CORBA", TCL_STATIC );
      /* TODO: try it without CORBA: gnome_init? */
      return TCL_ERROR;
   }
   #else
   gnome_init( appId ? appId : "gnocl", 
         appVersion ? appVersion : VERSION, 
         argc, p );
   #endif
#else
   if( !gtk_init_check( &argc, &p ) )
   {
      Tcl_SetResult( interp, "could not initialize gtk", TCL_STATIC );
      return TCL_ERROR;
   }
#endif

   Tcl_CreateEventSource( eventSetupProc, eventCheckProc, NULL );

   /* Tcl_CreateExitHandler( exitHandler, NULL ); */
   if( Tcl_PkgProvide( interp, "Gnocl", VERSION ) != TCL_OK )
      return TCL_ERROR;
   
   for( a = 0; commands[a].name; ++a )
   {
      char buffer[128] = "gnocl::";
      strcpy( buffer + 7, commands[a].name );
      Tcl_CreateObjCommand( interp, buffer, commands[a]. proc, NULL, NULL );
   }
   
   g_datalist_init( &widgetDataList );
   g_datalist_init( &canvasItemDataList );
   tooltips = gtk_tooltips_new( );

   return TCL_OK;
}

