[initial record of x3 prototype
raph.levien@gmail.com**20051210083334] {
addfile ./Makefile
hunk ./Makefile 1
+TARGET = gtk
+
+ifeq ($(TARGET),gtk)
+  X3_PLAT = X3_GTK
+  X3_INCL = `pkg-config --cflags gtk+-2.0`
+  X3_LIBS = `pkg-config --libs gtk+-2.0`
+endif
+
+ifeq ($(TARGET),carbon)
+  X3_PLAT = X3_CARBON
+  X3_LIBS = -framework Carbon
+endif
+
+ifeq ($(TARGET),win32)
+  X3_PLAT = X3_WIN32
+endif
+
+CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL)
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+test:	test.o x3$(TARGET).o
+
+x3lisp.o:	x3lisp.c
addfile ./test.c
hunk ./test.c 1
+/* A test app for X3. */
+
+#include "x3.h"
+
+int
+mycallback(x3widget *w, void *data,
+	   char *cmd, char *what, char *arg, void *more)
+{
+    printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n",
+	   cmd, what ? what : "(null)", arg ? arg : "(null)");
+    return 1;
+}
+
+#ifdef X3_USEMAIN
+int
+main(int argc, char **argv)
+{
+    x3widget *mainwin;
+    x3widget *vbox;
+    x3widget *m;
+
+    x3init(&argc, &argv);
+    mainwin = x3window(x3window_main, "untitled", mycallback, NULL);
+#if 1
+    m = x3menu(mainwin, "F\xc2\xa1le");
+    x3menuitem(m, "Save", "save", "<cmd>s");
+    x3setactive(x3menuitem(m, "Save As...", "sava", "<cmd>S"), 0);
+    x3menusep(m);
+    x3menuitem(m, "Preferences...", "pref", "<cmd>,");
+
+    x3menu(mainwin, "Edit");
+#endif
+
+#if 1
+    vbox = x3vbox(x3pad(mainwin, 10, 10, 10, 10), 0, 5);
+
+    x3button(x3align(vbox, x3center), "foo", "Click me!");
+    x3button(vbox, "bar", "Click me too!");
+    //x3setactive(x3button(vbox, "bar", "But not me"), 0);
+    x3edittext(vbox, "etxt");
+#endif
+
+    x3_window_show(mainwin);
+
+    x3main();
+    return 0;
+}
+#endif
+
+#ifdef X3_USEWINMAIN
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
+		   int iCmdShow)
+{
+    //printf("winmain\n");
+    x3widget *mainwin;
+
+    x3init_win32(hInstance);
+
+    mainwin = x3window(x3window_main, "untitled", mycallback, NULL);
+    MessageBox(NULL, szCmdLine, "Hello Demo", MB_OK);
+    return 0;
+}
+#endif
addfile ./x3.h
hunk ./x3.h 1
+/* X3 is a lightweight toolset for building cross-platform applications. */
+
+typedef struct _x3widget x3widget;
+
+typedef enum {
+    x3window_main = 1,
+    x3window_dialog = 2,
+} x3windowflags;
+
+typedef enum {
+    x3center = 0,
+    x3left = 1,
+    x3right = 2,
+    x3hfill = 3,
+    x3top = 4,
+    x3bottom = 8,
+    x3vfill = 12,
+
+    x3topleft = 5,
+    x3topright = 6,
+    x3bottomleft = 9,
+    x3bottomright = 10,
+} x3alignment;
+
+#ifdef X3_GTK
+
+#include <gtk/gtk.h>
+
+#define X3_GMW(g, m, w) g
+
+struct _x3widget {
+    char *name;
+    x3widget *parent;
+    GtkWidget *widget;
+};
+
+#define X3_USEMAIN
+
+#endif
+
+#ifdef X3_CARBON
+
+#define X3_GMW(g, m, w) m
+
+#include <Carbon/Carbon.h>
+
+typedef struct _x3type x3type;
+
+struct _x3type {
+    void (*sizereq)(x3widget *w);
+    void (*sizealloc)(x3widget *w, Rect *r);
+};
+
+typedef enum {
+    x3carbonnone,
+    x3carbonhiview,
+    x3carbonwindow,
+    x3carbonmenu,
+    x3carbonmenuitem,
+} x3carbonvar;
+
+typedef enum {
+    x3flag_needsizereq = 1,
+    x3flag_needsizealloc = 2
+} x3widgetflags;
+
+struct _x3widget {
+    const x3type *type;
+    x3widgetflags flags;
+    Rect sizerequest;
+    char *name;
+    x3widget *parent;
+    x3carbonvar var;
+    union {
+	HIViewRef hiview;
+	WindowRef window;
+	MenuRef menu;
+	int menuitem;
+    } u;
+    int n_children;
+    x3widget **children;
+};
+
+#define X3_USEMAIN
+
+#endif
+
+#ifdef X3_WIN32
+
+#include <windows.h>
+
+#define X3_GMW(g, m, w) w
+
+typedef struct _x3type x3type;
+
+struct _x3type {
+    void (*sizereq)(x3widget *w);
+    void (*sizealloc)(x3widget *w, RECT *r);
+};
+
+typedef enum {
+    x3carbonnone,
+    x3carbonhiview,
+    x3carbonwindow,
+    x3carbonmenu,
+    x3carbonmenuitem,
+} x3carbonvar;
+
+typedef enum {
+    x3flag_needsizereq = 1,
+    x3flag_needsizealloc = 2
+} x3widgetflags;
+
+struct _x3widget {
+    const x3type *type;
+    char *name;
+    x3widget *parent;
+    x3widgetflags flags;
+    x3carbonvar var; // should this be in the type?
+    union {
+	HWND hwnd;
+    } u;
+    int n_children;
+    x3widget **children;
+};
+
+#define X3_USEWINMAIN
+
+void x3init_win32(HINSTANCE hInstance);
+
+#endif
+
+typedef int (*x3window_callback)(x3widget *window, void *data,
+				 char *cmd, char *what, char *arg,
+				 void *more);
+
+/* Main loop */
+void x3init(int *pargc, char ***pargv);
+void x3main(void);
+
+void x3_window_show(x3widget *win);
+
+x3widget *x3window(x3windowflags, char *label,
+		   x3window_callback callback, void *callback_data);
+x3widget *x3menu(x3widget *parent, char *name);
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut);
+x3widget *x3menusep(x3widget *parent);
+x3widget *x3align(x3widget *parent, x3alignment alignment);
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r);
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing);
+x3widget *x3button(x3widget *parent, char *name, char *label);
+x3widget *x3edittext(x3widget *parent, char *cmd);
+
+void x3setactive(x3widget *w, int active);
addfile ./x3carbon.c
hunk ./x3carbon.c 1
+#include "x3.h"
+
+/* Globals for managing sync */
+
+int n_x3needsizereqs;
+int n_x3needsizereqs_max;
+x3widget **x3needsizereqs;
+
+int n_x3needsizeallocs;
+int n_x3needsizeallocs_max;
+x3widget **x3needsizeallocs;
+
+/* Some utility-type functions. */
+
+UInt32 x3mkmultichar(const char *s)
+{
+    int len = strlen(s);
+    int i;
+    UInt32 result = 0;
+
+    for (i = 0; i < (len > 4 ? 4 : len); i++)
+	result = (result << 8) + (unsigned char)s[i];
+    for (; i < 4; i++)
+	result = (result << 8) + ' ';
+    return result;
+}
+
+char *x3multicharstr(UInt32 mc, char buf[5])
+{
+    int i, len;
+
+    for (i = 0; i < 4; i++)
+	if (((mc >> (8 * i)) & 0xff) != ' ') break;
+    len = 4 - i;
+    for (i = 0; i < len; i++)
+	buf[i] = (mc >> (24 - 8 * i)) & 0xff;
+    buf[len] = 0;
+    return buf;
+}
+
+void x3qsizereq(x3widget *w)
+{
+    if (w && !(w->flags & x3flag_needsizereq)) {
+	x3qsizereq(w->parent);
+	if (n_x3needsizereqs == n_x3needsizereqs_max)
+	    x3needsizereqs = (x3widget **)realloc(x3needsizereqs,
+						  sizeof(x3widget *) *
+						  (n_x3needsizereqs_max <<= 1));
+	x3needsizereqs[n_x3needsizereqs++] = w;
+	w->flags |= x3flag_needsizereq;
+    }
+}
+
+void x3widget_init(x3widget *w, const x3type *type)
+{
+    w->type = type;
+    w->name = NULL;
+    w->parent = NULL;
+    w->var = x3carbonnone;
+    w->u.window = NULL;
+    w->n_children = 0;
+    w->children = NULL;
+}
+
+static void x3addtoparent(x3widget *parent, x3widget *child)
+{
+    const int n_children_init = 4;
+
+    child->parent = parent;
+    if (parent->n_children == 0)
+	parent->children = (x3widget **)malloc(sizeof(x3widget *) *
+					       n_children_init);
+    else if (parent->n_children >= n_children_init &&
+	     !(parent->n_children & (parent->n_children - 1)))
+	parent->children = (x3widget **)realloc(parent->children,
+						sizeof(x3widget *) *
+						(parent->n_children << 1));
+    parent->children[parent->n_children++] = child;
+}
+
+static x3widget *x3widget_new_container(x3widget *parent, char *name,
+					const x3type *type)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+    x3widget_init(result, type);
+    result->name = name ? strdup(name) : NULL;
+    x3addtoparent(parent, result);
+    x3qsizereq(result);
+    return result;
+}
+
+static x3widget *x3widget_new_hiview(x3widget *parent, char *name,
+				     const x3type *type,
+				     HIViewRef hiview)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+    x3widget_init(result, type);
+    result->name = name ? strdup(name) : NULL;
+    result->var = x3carbonhiview;
+    result->u.hiview = hiview;
+    x3addtoparent(parent, result);
+    x3qsizereq(result);
+    return result;
+}
+
+static x3widget *x3widget_new_menu(x3widget *parent,
+				     const x3type *type,
+				     MenuRef menu)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+    x3widget_init(result, type);
+    result->var = x3carbonmenu;
+    result->u.menu = menu;
+    x3addtoparent(parent, result);
+    return result;
+}
+
+static x3widget *x3widget_new_menuitem(x3widget *parent,
+					const x3type *type, int index)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+    x3widget_init(result, type);
+    x3addtoparent(parent, result);
+    result->var = x3carbonmenuitem;
+    result->u.menuitem = index;
+    return result;
+}
+
+void x3init(int *pargc, char ***pargv)
+{
+    ProcessSerialNumber psn;
+
+    /* Most apps get this done from loading the menu bar from the NIB.
+       Prior to 10.3, there was an undocumented call. This one is official
+       for 10.3 and later. */
+    GetCurrentProcess(&psn);
+    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+    SetFrontProcess(&psn);
+
+    n_x3needsizereqs = 0;
+    x3needsizereqs = (x3widget **)malloc(sizeof(x3widget *) *
+					 (n_x3needsizereqs_max = 16));
+
+    n_x3needsizeallocs = 0;
+    x3needsizeallocs = (x3widget **)malloc(sizeof(x3widget *) *
+					   (n_x3needsizeallocs_max = 16));
+}
+
+void x3sync(void)
+{
+    int i;
+
+    for (i = n_x3needsizereqs - 1; i >= 0; i--) {
+	x3widget *w = x3needsizereqs[i];
+	w->type->sizereq(w);
+	w->flags &= ~x3flag_needsizereq;
+	w->flags |= x3flag_needsizealloc;
+    }
+    for (i = 0; i < n_x3needsizereqs; i++) {
+	x3widget *w = x3needsizereqs[i];
+	if (w->flags & x3flag_needsizealloc) {
+	    w->type->sizealloc(w, NULL);
+	    w->flags &= ~x3flag_needsizealloc;
+	}
+    }
+    n_x3needsizereqs = 0;
+}
+
+void x3main(void)
+{
+    x3sync();
+    RunApplicationEventLoop();
+}
+
+void x3_window_show(x3widget *mainwin)
+{
+    ControlRef root;
+#if 0
+    TransitionWindow(mainwin->u.window, kWindowZoomTransitionEffect,
+		     kWindowShowTransitionAction, NULL);
+#endif
+
+#if 0
+    CreateRootControl(mainwin->u.window, &root);
+#endif
+    //RepositionWindow(mainwin->u.window, NULL, kWindowCascadeOnMainScreen);
+    ShowWindow(mainwin->u.window);
+    SelectWindow(mainwin->u.window);
+}
+
+static WindowRef x3window_of(x3widget *w)
+{
+    while (w->parent) w = w->parent;
+    return w->var == x3carbonwindow ? w->u.window : NULL;
+}
+
+typedef struct {
+    x3widget base;
+    x3window_callback callback;
+    void *callback_data;
+} x3widget_window;
+
+pascal OSStatus x3carbonWindowEventHandler(EventHandlerCallRef cr,
+					   EventRef inEvent, void *data)
+{
+    x3widget_window *z = (x3widget_window *)data;
+    OSStatus result = noErr;
+    UInt32 eclass = GetEventClass(inEvent);
+    UInt32 ekind = GetEventKind(inEvent);
+    char multicharbuf[5];
+
+    if (eclass == kEventClassCommand && ekind == kEventCommandProcess) {
+	HICommand command;
+	int status;
+
+	GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand,
+			  NULL, sizeof(HICommand), NULL, &command);
+	status = z->callback(&z->base, z->callback_data,
+			     x3multicharstr(command.commandID, multicharbuf),
+			     "command", NULL, NULL);
+	if (status == 1)
+	    result = eventNotHandledErr;
+    } else {
+	printf("My handler is getting called %s %d!\n",
+	       x3multicharstr(eclass, multicharbuf),
+	       GetEventKind(inEvent));
+    }
+    result = eventNotHandledErr;
+    return result;
+}
+
+void x3window_sizereq(x3widget *w)
+{
+}
+
+void x3window_sizealloc(x3widget *w, Rect *r)
+{
+    int i;
+
+    for (i = 0; i < w->n_children; i++) {
+	x3widget *child = w->children[i];
+	if (child->type->sizealloc)
+	    child->type->sizealloc(child, &child->sizerequest);
+	child->flags &= ~x3flag_needsizealloc;
+    }
+}
+
+x3type x3windowtype = { x3window_sizereq,
+			x3window_sizealloc};
+
+x3widget *x3window(x3windowflags flags, char *label,
+		   x3window_callback callback, void *data)
+{
+    WindowRef window;
+    Rect bounds = { 100, 100, 400, 600 };
+    EventHandlerRef handlerRef;
+    WindowAttributes attrs =
+	kWindowCompositingAttribute |
+	kWindowLiveResizeAttribute |
+	kWindowInWindowMenuAttribute |
+	kWindowFrameworkScaledAttribute |
+	kWindowStandardHandlerAttribute;
+    EventTypeSpec windowEvents[] = {
+		{ kEventClassCommand,	kEventCommandProcess }, 
+		//{ kEventClassCommand,	kEventCommandUpdateStatus }, 
+
+		//{ kEventClassMouse,	kEventMouseDown },
+
+		//{ kEventClassWindow,	kEventWindowClose }, 
+		//{ kEventClassWindow,	kEventWindowGetIdealSize },
+		{ kEventClassWindow,	kEventWindowBoundsChanged }, 
+		//{ kEventClassWindow,	kEventWindowGetClickActivation }, 
+		//{ kEventClassWindow,	kEventWindowContextualMenuSelect } 
+	};
+    CFStringRef cflabel = CFStringCreateWithCString(NULL, label,
+						    kCFStringEncodingUTF8);
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget_window));
+
+    if (flags & x3window_main)
+	attrs |= kWindowStandardDocumentAttributes;
+
+    OSStatus err = CreateNewWindow(kDocumentWindowClass, attrs,
+				   &bounds, &window);
+
+    SetWindowTitleWithCFString(window, cflabel);
+    CFRelease(cflabel);
+    x3widget_init(result, &x3windowtype);
+    result->var = x3carbonwindow;
+    result->u.window = window;
+    ((x3widget_window *)result)->callback = callback;
+    ((x3widget_window *)result)->callback_data = data;
+
+    InstallWindowEventHandler(window, NewEventHandlerUPP(x3carbonWindowEventHandler),
+			      sizeof(windowEvents)/sizeof(EventTypeSpec),
+			      windowEvents, result, &handlerRef);
+
+    return result;
+}
+
+x3type x3menutype = { NULL, NULL };
+
+x3widget *x3menu(x3widget *parent, char *name)
+{
+    static int id = 1; /* Note: menu id should probably be kept per-window */
+    MenuRef menu;
+    CFStringRef cflabel = CFStringCreateWithCString(NULL, name,
+						    kCFStringEncodingUTF8);
+
+    CreateNewMenu(id++, 0, &menu);
+    SetMenuTitleWithCFString(menu, cflabel);
+    CFRelease(cflabel);
+    InsertMenu(menu, 0);
+    return x3widget_new_menu(parent, &x3menutype, menu);
+}
+
+int x3parseshortcut(const char *shortcut, UInt16 *pkey, UInt8 *pmods)
+{
+    UInt16 key;
+    UInt8 mods = kMenuNoCommandModifier;
+    int i = 0;
+
+    while (shortcut[i] == '<') {
+	if (!strncmp(shortcut + i, "<cmd>", 5)) {
+	    mods &= ~kMenuNoCommandModifier;
+	    i += 5;
+	} else if (!strncmp(shortcut + i, "<shift>", 7)) {
+	    mods |= kMenuShiftModifier;
+	    i += 7;
+	} else if (!strncmp(shortcut + i, "<option>", 8)) {
+	    mods |= kMenuOptionModifier;
+	    i += 8;
+	} else if (!strncmp(shortcut + i, "<ctrl>", 6)) {
+	    mods |= kMenuControlModifier;
+	    i += 6;
+	} else
+	    return false;
+    }
+    if (shortcut[i] && shortcut[i + 1] == 0) {
+	key = shortcut[i];
+	if (key >= 'a' && key <= 'z') key -= 'a' - 'A';
+	else if (key >= 'A' && key <= 'Z') mods |= kMenuShiftModifier;
+    } else
+	return false;
+
+    *pkey = key;
+    *pmods = mods;
+    return true;
+}
+
+x3type x3menuitemtype = { NULL, NULL };
+
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+{
+    CFStringRef cflabel = CFStringCreateWithCString(NULL, name,
+						    kCFStringEncodingUTF8);
+    MenuItemIndex index;
+
+    AppendMenuItemTextWithCFString(parent->u.menu, cflabel,
+				   0,
+				   x3mkmultichar(cmd), &index);
+    if (shortcut) {
+	UInt16 key;
+	UInt8 mods;
+
+	if (x3parseshortcut(shortcut, &key, &mods)) {
+	    SetMenuItemCommandKey(parent->u.menu, index, false, key);
+	    SetMenuItemModifiers(parent->u.menu, index, mods);
+	}
+    }
+    CFRelease(cflabel);
+    return x3widget_new_menuitem(parent, &x3menuitemtype, index);
+}
+
+x3widget *x3menusep(x3widget *parent)
+{
+    Str255 str = {1, '-'};
+
+    AppendMenu(parent->u.menu, str);
+    return x3widget_new_menuitem(parent, &x3menuitemtype, -1);
+}
+
+void x3vbox_sizereq(x3widget *w)
+{
+    int i;
+    int spacing = 12; /* TODO use setting */
+
+    w->sizerequest.left = 0;
+    w->sizerequest.top = 0;
+    w->sizerequest.right = 0;
+    w->sizerequest.bottom = 0;
+    for (i = 0; i < w->n_children; i++) {
+	x3widget *child = w->children[i];
+	int childw = child->sizerequest.right - child->sizerequest.left;
+	int childh = child->sizerequest.bottom - child->sizerequest.top;
+
+	if (i < w->n_children - 1)
+	    childh += spacing;
+	w->sizerequest.bottom += childh;
+	if (childw > w->sizerequest.right)
+	    w->sizerequest.right = childw;
+    }
+}
+
+void x3vbox_sizealloc(x3widget *w, Rect *r)
+{
+    int i;
+    Rect child_r = *r;
+    int spacing = 12; /* TODO use setting */
+
+    printf("vbox sizealloc = (%d, %d) - (%d, %d)\n",
+	   r->left, r->top, r->right, r->bottom);
+    for (i = 0; i < w->n_children; i++) {
+	x3widget *child = w->children[i];
+	int childh = child->sizerequest.bottom - child->sizerequest.top;
+
+	child_r.bottom = child_r.top + childh;
+
+	child->type->sizealloc(child, &child_r);
+	child->flags &= ~x3flag_needsizealloc;
+
+	child_r.top += childh + spacing;
+    }
+}
+
+x3type x3vboxtype = { x3vbox_sizereq,
+		      x3vbox_sizealloc };
+
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+{
+    return x3widget_new_container(parent, NULL, &x3vboxtype);
+}
+
+typedef struct {
+    x3widget base;
+    x3alignment alignment;
+} x3widget_align;
+
+void x3align_sizereq(x3widget *w)
+{
+    w->sizerequest.left = 0;
+    w->sizerequest.top = 0;
+    w->sizerequest.right = 0;
+    w->sizerequest.bottom = 0;
+    if (w->n_children) {
+	x3widget *child = w->children[0];
+	int childw = child->sizerequest.right - child->sizerequest.left;
+	int childh = child->sizerequest.bottom - child->sizerequest.top;
+	w->sizerequest.bottom = childh;
+	w->sizerequest.right = childw;
+    }
+}
+
+void x3align_sizealloc(x3widget *w, Rect *r)
+{
+    x3widget_align *z = (x3widget_align *)w;
+    x3alignment a = z->alignment;
+    int xa = a & 3;
+    int ya = (a >> 2) & 3;
+    Rect child_r = *r;
+
+    printf("align sizealloc = (%d, %d) - (%d, %d)\n",
+	   r->left, r->top, r->right, r->bottom);
+    if (w->n_children) {
+	x3widget *child = w->children[0];
+	if (xa < 3) {
+	    int childw = child->sizerequest.right - child->sizerequest.left;
+	    int pad = r->right - r->left - childw;
+	    child_r.left += (pad * (1 + (xa >> 1) - (xa & 1)) + 1) >> 1;
+	    child_r.right = child_r.left + childw;
+	}
+	if (ya < 3) {
+	    int childh = child->sizerequest.bottom - child->sizerequest.top;
+	    int pad = r->bottom - r->top - childh;
+	    child_r.top += (pad * (1 + (ya >> 1) - (ya & 1)) + 1) >> 1;
+	    child_r.bottom = child_r.top + childh;
+	}
+
+	child->type->sizealloc(child, &child_r);
+	child->flags &= ~x3flag_needsizealloc;
+    }
+}
+
+x3type x3aligntype = { x3align_sizereq,
+		       x3align_sizealloc };
+
+x3widget *x3align(x3widget *parent, x3alignment alignment)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget_align));
+    x3widget_init(result, &x3aligntype);
+    x3addtoparent(parent, result);
+    x3qsizereq(result);
+    ((x3widget_align *)result)->alignment = alignment;
+    return result;
+}
+
+typedef struct {
+    x3widget base;
+    int t, b, l, r;
+} x3widget_pad;
+
+void x3pad_sizereq(x3widget *w)
+{
+    x3widget_pad *z = (x3widget_pad *)w;
+    w->sizerequest.left = 0;
+    w->sizerequest.top = 0;
+    w->sizerequest.right = 0;
+    w->sizerequest.bottom = 0;
+    if (w->n_children) {
+	x3widget *child = w->children[0];
+	int childw = child->sizerequest.right - child->sizerequest.left;
+	int childh = child->sizerequest.bottom - child->sizerequest.top;
+	w->sizerequest.right = childw + z->l + z->r;
+	w->sizerequest.bottom = childh + z->t + z->b;
+    }
+}
+
+void x3pad_sizealloc(x3widget *w, Rect *r)
+{
+    x3widget_pad *z = (x3widget_pad *)w;
+    Rect child_r = *r;
+
+    printf("pad sizealloc = (%d, %d) - (%d, %d)\n",
+	   r->left, r->top, r->right, r->bottom);
+    if (w->n_children) {
+	x3widget *child = w->children[0];
+	child_r.left += z->l;
+	child_r.right -= z->r;
+	child_r.top += z->t;
+	child_r.bottom -= z->b;
+
+	child->type->sizealloc(child, &child_r);
+	child->flags &= ~x3flag_needsizealloc;
+    }
+}
+
+x3type x3padtype = { x3pad_sizereq,
+		     x3pad_sizealloc };
+
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget_pad));
+    x3widget_init(result, &x3padtype);
+    x3addtoparent(parent, result);
+    x3qsizereq(result);
+    ((x3widget_pad *)result)->t = t;
+    ((x3widget_pad *)result)->b = b;
+    ((x3widget_pad *)result)->l = l;
+    ((x3widget_pad *)result)->r = r;
+    return result;
+}
+
+
+void x3button_sizereq(x3widget *w)
+{
+    SInt16 offset;
+
+    w->sizerequest.left = 0;
+    w->sizerequest.top = 0;
+    w->sizerequest.right = 0;
+    w->sizerequest.bottom = 0;
+    GetBestControlRect(w->u.hiview, &w->sizerequest, &offset);
+    printf("button sizereq = (%d, %d) - (%d, %d)\n",
+	   w->sizerequest.left, w->sizerequest.top,
+	   w->sizerequest.right, w->sizerequest.bottom);
+}
+
+void x3button_sizealloc(x3widget *w, Rect *r)
+{
+    /* TODO probably want to use HIViewSetFrame instead */
+    printf("button sizealloc = (%d, %d) - (%d, %d)\n",
+	   r->left, r->top, r->right, r->bottom);
+    SetControlBounds(w->u.hiview, r);
+}
+
+x3type x3buttontype = { x3button_sizereq,
+			x3button_sizealloc };
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+    WindowRef window = x3window_of(parent);
+    Rect r = {10, 10, 30, 100};
+    ControlRef control;
+    OSStatus err;
+    CFStringRef cflabel = CFStringCreateWithCString(NULL, label,
+						    kCFStringEncodingUTF8);
+
+    err = CreatePushButtonControl(window, &r, cflabel, &control);
+    CFRelease(cflabel);
+    SetControlCommandID(control, x3mkmultichar(cmd));
+    //SetWindowDefaultButton(window, control);
+    return x3widget_new_hiview(parent, cmd, &x3buttontype, control);
+}
+
+x3widget *x3edittext(x3widget *parent, char *cmd)
+{
+    WindowRef window = x3window_of(parent);
+    Rect r = {10, 10, 30, 100};
+    ControlRef control;
+    OSStatus err;
+    Boolean singleline = true;
+
+    err = CreateEditUnicodeTextControl(window, &r, CFSTR(""),
+				  false, NULL, &control);
+    SetControlCommandID(control, x3mkmultichar(cmd));
+#if 0
+    SetControlData(control, kControlEntireControl,
+		   kControlEditTextSingleLineTag, sizeof(Boolean),
+		   &singleline);
+#endif
+    return x3widget_new_hiview(parent, cmd, &x3buttontype, control);
+}
+
+/* Functions for manipulating widget state - some fairly polymorphic. */
+
+void x3setactive(x3widget *w, int active)
+{
+    if (w->var == x3carbonmenuitem) {
+	MenuRef menu = w->parent->u.menu;
+	if (active)
+	    EnableMenuItem(menu, w->u.menuitem);
+	else
+	    DisableMenuItem(menu, w->u.menuitem);
+	/* According to Carbon docs, we need to redraw menu. */
+    } else if (w->var == x3carbonhiview) {
+	if (active)
+	    ActivateControl(w->u.hiview);
+	else
+	    DeactivateControl(w->u.hiview);
+    }
+}
addfile ./x3gtk.c
hunk ./x3gtk.c 1
+#include "x3.h"
+
+void x3init(int *pargc, char ***pargv)
+{
+    gtk_init(pargc, pargv);
+}
+
+static void
+x3_getfirst_callback(GtkWidget *widget, gpointer data)
+{
+    GtkWidget **pwidget = (GtkWidget **)data;
+
+    if (*pwidget == NULL)
+	*pwidget = widget;
+}
+
+static x3widget *x3widget_new(x3widget *parent, char *name, GtkWidget *widget)
+{
+    x3widget *result = (x3widget *)malloc(sizeof(x3widget *));
+
+    result->name = g_strdup(name);
+    result->widget = widget;
+    result->parent = parent;
+    if (parent) {
+	if (GTK_IS_WINDOW(parent->widget)) {
+	    GtkWidget *vbox = NULL;
+
+	    gtk_container_foreach(GTK_CONTAINER(parent->widget),
+				  x3_getfirst_callback,
+				  (gpointer)&vbox);
+
+	    if (GTK_IS_MENU_ITEM(widget)) {
+		GtkWidget *first_child = NULL;
+		GtkWidget *menubar;
+
+		gtk_container_foreach(GTK_CONTAINER(vbox),
+				      x3_getfirst_callback,
+				      (gpointer)&first_child);
+		if (first_child == NULL || !GTK_IS_MENU_BAR(first_child)) {
+		    menubar = gtk_menu_bar_new();
+		    gtk_box_pack_start(GTK_BOX(vbox), menubar,
+				       FALSE, FALSE, 0);
+		    gtk_widget_show(menubar);
+		} else
+		    menubar = first_child;
+		gtk_menu_bar_append(GTK_MENU_BAR(menubar), widget);
+	    } else {
+		gtk_container_add(GTK_CONTAINER(vbox), widget);
+	    }
+	} else if (GTK_IS_MENU_ITEM(parent->widget)) {
+	    GtkWidget *menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(parent->widget));
+
+	    gtk_menu_shell_append(GTK_MENU_SHELL(menu), widget);
+	} else {
+	    gtk_container_add(GTK_CONTAINER(parent->widget), widget);
+	}
+    }
+    return result;
+}
+
+void x3_window_show(x3widget *w)
+{
+    gtk_widget_show(w->widget);
+}
+
+void x3main(void)
+{
+    gtk_main();
+}
+
+/* some constructors */
+
+x3widget *x3window(x3windowflags flags, char *label,
+		   x3window_callback callback, void *callback_data)
+{
+    GtkWidget *window;
+    GtkWidget *vbox;
+
+    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(window), label);
+    vbox = gtk_vbox_new(FALSE, 0);
+    gtk_widget_show(vbox);
+    gtk_container_add(GTK_CONTAINER(window), vbox);
+    return x3widget_new(NULL, "mainwin", window);
+}
+
+x3widget *x3menu(x3widget *parent, char *name)
+{
+    GtkWidget *item;
+    GtkWidget *menu;
+
+    menu = gtk_menu_new();
+
+    item = gtk_menu_item_new_with_label(name);
+    gtk_widget_show(item);
+
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
+    return x3widget_new(parent, NULL, item);
+}
+
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+{
+    GtkWidget *item;
+
+    item = gtk_menu_item_new_with_label(name);
+    gtk_widget_show(item);
+    return x3widget_new(parent, NULL, item);
+}
+
+x3widget *x3menusep(x3widget *parent)
+{
+    GtkWidget *item;
+
+    item = gtk_separator_menu_item_new();
+    gtk_widget_show(item);
+    return x3widget_new(parent, NULL, item);
+}
+
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+{
+    GtkWidget *vbox = gtk_vbox_new(homogeneous, spacing);
+
+    gtk_widget_show(vbox);
+    return x3widget_new(parent, NULL, vbox);
+}
+
+x3widget *x3align(x3widget *parent, x3alignment alignment)
+{
+    int xa = alignment & 3;
+    int ya = (alignment >> 2) & 3;
+    float xalign = .5 * (1 + (xa >> 1) - (xa & 1));
+    float yalign = .5 * (1 + (ya >> 1) - (ya & 1));
+    float xscale = (xa == 3);
+    float yscale = (ya == 3);
+    GtkWidget *align = gtk_alignment_new(xalign, yalign, xscale, yscale);
+
+    gtk_widget_show(align);
+    return x3widget_new(parent, NULL, align);
+}
+
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+{
+    GtkWidget *align = gtk_alignment_new(0, 0, 1, 1);
+
+    gtk_alignment_set_padding(align, t, b, l, r);
+    gtk_widget_show(align);
+    return x3widget_new(parent, NULL, align);
+}
+
+x3widget *x3button(x3widget *parent, char *name, char *label)
+{
+    GtkWidget *button = gtk_button_new_with_label(label);
+
+    gtk_widget_show(button);
+    return x3widget_new(parent, name, button);
+}
+
+x3widget *x3edittext(x3widget *parent, char *cmd)
+{
+    GtkWidget *entry = gtk_entry_new();
+
+    gtk_widget_show(entry);
+    return x3widget_new(parent, cmd, entry);
+}
+
+void x3setactive(x3widget *w, int active)
+{
+    gtk_widget_set_sensitive(w->widget, active != 0);
+}
addfile ./x3win32.c
hunk ./x3win32.c 1
+#include "x3.h"
+
+HINSTANCE theInstance = NULL;
+
+void x3init_win32(HINSTANCE hInstance)
+{
+    theInstance = hInstance;
+}
+
+void x3widget_init(x3widget *w, const x3type *type)
+{
+    w->type = type;
+    w->parent = NULL;
+    w->var = x3carbonnone;
+    w->u.hwnd = NULL;
+    w->n_children = 0;
+    w->children = NULL;
+}
+
+x3widget *x3window(x3windowflags flags, char *label,
+		   x3window_callback callback, void *data)
+{
+    HWND hwnd;
+    DWORD style = WS_OVERLAPPED;
+
+    hwnd = CreateWindowEx(0, NULL, "My window",
+			  style, 100, 100, 300, 300,
+			  NULL, NULL,
+			  theInstance, NULL);
+    ShowWindow(hwnd, SW_SHOW);
+    return NULL;
+			 
+}
}
