summaryrefslogtreecommitdiff
path: root/third_party/spiro/x3
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/spiro/x3')
-rw-r--r--third_party/spiro/x3/Makefile25
-rw-r--r--third_party/spiro/x3/pyrex/Makefile39
-rw-r--r--third_party/spiro/x3/pyrex/beztest.py44
-rw-r--r--third_party/spiro/x3/pyrex/main.py40
-rw-r--r--third_party/spiro/x3/pyrex/x3.pyx259
-rw-r--r--third_party/spiro/x3/test.c158
-rw-r--r--third_party/spiro/x3/x3.h293
-rw-r--r--third_party/spiro/x3/x3carbon.c1002
-rw-r--r--third_party/spiro/x3/x3common.c359
-rw-r--r--third_party/spiro/x3/x3common.h16
-rw-r--r--third_party/spiro/x3/x3gtk.c778
-rw-r--r--third_party/spiro/x3/x3win32.c174
12 files changed, 3187 insertions, 0 deletions
diff --git a/third_party/spiro/x3/Makefile b/third_party/spiro/x3/Makefile
new file mode 100644
index 0000000..392417b
--- /dev/null
+++ b/third_party/spiro/x3/Makefile
@@ -0,0 +1,25 @@
+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
+ X3_LIBS = -lgdi32
+endif
+
+CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL)
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+test: test.o x3$(TARGET).o x3common.o
+
+x3lisp.o: x3lisp.c
diff --git a/third_party/spiro/x3/pyrex/Makefile b/third_party/spiro/x3/pyrex/Makefile
new file mode 100644
index 0000000..d0324f3
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/Makefile
@@ -0,0 +1,39 @@
+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`
+ SHARED_FLAG = -shared
+endif
+
+ifeq ($(TARGET),carbon)
+ X3_PLAT = X3_CARBON
+ X3_LIBS = -framework Carbon
+ SHARED_FLAG = -dynamiclib -flat_namespace -undefined suppress
+endif
+
+ifeq ($(TARGET),win32)
+ X3_PLAT = X3_WIN32
+ X3_LIBS = -lgdi32
+endif
+
+PY_INCL := -I$(shell python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()")
+
+CFLAGS = -g -Wall -fPIC -I.. $(PY_INCL) -D$(X3_PLAT) $(X3_INCL)
+LDFLAGS = -g
+LDLIBS = $(X3_LIBS)
+
+x3.so: x3.o x3$(TARGET).o x3common.o
+ gcc $(SHARED_FLAG) $^ $(X3_LIBS) -o $@
+
+x3.c: x3.pyx
+ python2.4-pyrexc $<
+
+x3$(TARGET).o: ../x3$(TARGET).c
+ $(CC) -c $(CFLAGS) -o $@ $<
+
+x3common.o: ../x3common.c
+ $(CC) -c $(CFLAGS) -o $@ $<
+
+
diff --git a/third_party/spiro/x3/pyrex/beztest.py b/third_party/spiro/x3/pyrex/beztest.py
new file mode 100644
index 0000000..effd0d5
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/beztest.py
@@ -0,0 +1,44 @@
+import x3
+from math import *
+
+def my_callback(cmd, what, arg, more):
+ print cmd, what, arg
+
+class bez:
+ def __init__(self):
+ self.coords = [(10, 10), (200, 10), (300, 200), (400, 100)]
+ self.hit = None
+ def draw(self, dc):
+ coords = self.coords
+ dc.setrgba(0, 0, 0.5, 1)
+ dc.moveto(coords[0][0], coords[0][1])
+ dc.curveto(coords[1][0], coords[1][1], coords[2][0], coords[2][1],
+ coords[3][0], coords[3][1])
+ dc.stroke()
+ dc.setrgba(0, 0.5, 0, 0.5)
+ dc.moveto(coords[0][0], coords[0][1])
+ dc.lineto(coords[1][0], coords[1][1])
+ dc.stroke()
+ dc.moveto(coords[2][0], coords[2][1])
+ dc.lineto(coords[3][0], coords[3][1])
+ dc.stroke()
+ dc.setrgba(1, 0, 0, .5)
+ for x, y in coords:
+ dc.rectangle(x - 3, y - 3, 6, 6)
+ dc.fill()
+ def mouse(self, button, mods, x, y):
+ if button == 1:
+ for i in range(4):
+ if hypot(x - self.coords[i][0], y - self.coords[i][1]) < 4:
+ self.hit = i
+ elif button == -1:
+ self.hit = None
+ elif self.hit != None:
+ self.coords[self.hit] = (x, y)
+ self.view.dirty()
+
+win = x3.window(0, "beztest", my_callback)
+
+x3.view(win, 259, bez())
+
+x3.main()
diff --git a/third_party/spiro/x3/pyrex/main.py b/third_party/spiro/x3/pyrex/main.py
new file mode 100644
index 0000000..e784f46
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/main.py
@@ -0,0 +1,40 @@
+import x3
+
+def my_callback(cmd, what, arg, more):
+ print cmd, what, arg
+
+class my_viewclient:
+ def key(self, name, mods, code):
+ print name, mods, code
+ return 1
+ def mouse(self, buttons, mods, x, y):
+ print buttons, mods, x, y
+ def draw(self, dc):
+ print 'rect:', dc.rect
+ dc.moveto(0, 0)
+ dc.lineto(100, 100)
+ print dc.currentpoint()
+ dc.stroke()
+ dc.selectfont("Nimbus Sans L", 0, 0)
+ dc.setfontsize(12)
+ dc.moveto(50, 10)
+ dc.showtext(u"\u00a1hello, world!")
+ print dc.textextents(u"\u00a1hello, world!")
+
+win = x3.window(0, "foo", my_callback)
+
+m = x3.menu(win, "bar")
+
+x3.menuitem(m, "baz", "bazz", "<ctrl>b")
+x3.menusep(m)
+x3.menuitem(m, "Quit", "quit", "<ctrl>q")
+
+v = x3.vbox(win, 0, 12)
+
+v.setpacking(True, False, 0)
+x3.button(v, "butt", u"\u00a1hello!")
+x3.edittext(v, "quux")
+v.setpacking(True, True, 0)
+x3.view(v, 263, my_viewclient())
+
+x3.main()
diff --git a/third_party/spiro/x3/pyrex/x3.pyx b/third_party/spiro/x3/pyrex/x3.pyx
new file mode 100644
index 0000000..917df94
--- /dev/null
+++ b/third_party/spiro/x3/pyrex/x3.pyx
@@ -0,0 +1,259 @@
+cdef extern from "Python.h":
+ void *PyMem_Malloc(int size) except NULL
+ void Py_INCREF(object)
+ int PyUnicode_Check(object str)
+ object PyUnicode_AsUTF8String(object str)
+
+cdef extern from "x3.h":
+ ctypedef struct x3widget
+ ctypedef struct x3dc:
+ int x
+ int y
+ int width
+ int height
+ cdef enum x3windowflags:
+ x3window_main = 1
+ x3window_dialog = 2
+ ctypedef struct x3viewclient:
+ void (*destroy)(x3viewclient *self)
+ void (*mouse)(x3viewclient *self, int buttons, int mods,
+ double x, double y)
+ int (*key)(x3viewclient *self, char *keyname, int mods, int key)
+ void (*draw)(x3viewclient *self, x3dc *dc)
+
+ # client extension
+ void *py_client
+ ctypedef struct x3extents:
+ double x_bearing
+ double y_bearing
+ double width
+ double height
+ double x_advance
+ double y_advance
+ void x3init(int argc, char **argv)
+ void x3main()
+ x3widget *x3window(x3windowflags flags, char *name,
+ int (callback)(x3widget *w, void *data, char *cmd, char *what, char *arg, void *more),
+ void *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, int alignment)
+ x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+ x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+ x3widget *x3hpane(x3widget *parent)
+ x3widget *x3vpane(x3widget *parent)
+ x3widget *x3button(x3widget *parent, char *name, char *label)
+ x3widget *x3label(x3widget *parent, char *text)
+ x3widget *x3edittext(x3widget *parent, char *cmd)
+ x3widget *x3view(x3widget *parent, int flags, x3viewclient *vc)
+ void x3viewclient_init(x3viewclient *vc)
+ void x3view_dirty(x3widget *w)
+ void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+
+ void x3window_setdefaultsize(x3widget *w, int x, int y)
+ void x3setactive(x3widget *w, int active)
+ void x3setpacking(x3widget *w, int fill, int expand, int padding)
+ void x3pane_setsizing(x3widget *w, int c1r, int c1s, int c2r, int c2s)
+ int x3hasfocus(x3widget *w)
+
+ void x3moveto(x3dc *dc, double x, double y)
+ void x3lineto(x3dc *dc, double x, double y)
+ void x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+ void x3closepath(x3dc *dc)
+ void x3rectangle(x3dc *dc, double x, double y, double width, double height)
+ void x3getcurrentpoint(x3dc *dc, double *px, double *py)
+ void x3setrgba(x3dc *dc, unsigned int rgba)
+ void x3setlinewidth(x3dc *dc, double w)
+ void x3fill(x3dc *dc)
+ void x3stroke(x3dc *dc)
+ void x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+ void x3setfontsize(x3dc *dc, double size)
+ void x3showtext(x3dc *dc, char *text)
+ void x3textextents(x3dc *dc, char *text, x3extents *extents)
+ X3_GMW(g, m, w)
+
+cdef object strmaybenull(char *str):
+ if str == NULL:
+ return None
+ else:
+ return str
+
+cdef object utf8(object ustr):
+ if PyUnicode_Check(ustr):
+ return PyUnicode_AsUTF8String(ustr)
+ else:
+ return ustr
+
+ctypedef struct x3viewclient_py:
+ x3viewclient base
+ # client extension
+ void *py_client
+
+cdef class widget:
+ cdef x3widget *w
+ def hasfocus(self):
+ return x3hasfocus(self.w)
+
+cdef int x3py_callback(x3widget *window, void *data, char *cmd, char *what,
+ char *arg, void *more):
+ callback = <object>data
+ callback(cmd, strmaybenull(what), strmaybenull(arg), None)
+
+cdef class window(widget):
+ def __new__(self, int flags, char *name, callback):
+ self.w = x3window(flags, name, x3py_callback, <void *>callback)
+ def setdefaultsize(self, int width, int height):
+ x3window_setdefaultsize(self.w, width, height)
+
+cdef class menu(widget):
+ def __new__(self, widget parent, name):
+ uname = utf8(name)
+ self.w = x3menu(parent.w, uname)
+
+cdef class menuitem(widget):
+ def __new__(self, widget parent, name, char *cmd, char *shortcut = NULL):
+ uname = utf8(name)
+ self.w = x3menuitem(parent.w, uname, cmd, shortcut)
+
+cdef class menusep(widget):
+ def __new__(self, widget parent):
+ self.w = x3menusep(parent.w)
+
+cdef class align(widget):
+ def __new__(self, widget parent, int alignment):
+ self.w = x3align(parent.w, alignment)
+
+cdef class pad(widget):
+ def __new__(self, widget parent, int t, int b, int l, int r):
+ self.w = x3pad(parent.w, t, b, l, r)
+
+cdef class vbox(widget):
+ def __new__(self, widget parent, homogeneous, int spacing):
+ self.w = x3vbox(parent.w, not not homogeneous, spacing)
+ def setpacking(self, fill, expand, int padding):
+ x3setpacking(self.w, not not fill, not not expand, padding)
+
+cdef class hpane(widget):
+ def __new__(self, widget parent):
+ self.w = x3hpane(parent.w)
+ def setsizing(self, c1r, c1s, c2r, c2s):
+ x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s)
+
+cdef class vpane(widget):
+ def __new__(self, widget parent):
+ self.w = x3vpane(parent.w)
+ def setsizing(self, c1r, c1s, c2r, c2s):
+ x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s)
+
+cdef class button(widget):
+ def __new__(self, widget parent, char *name, label):
+ ulabel = utf8(label)
+ self.w = x3button(parent.w, name, ulabel)
+
+cdef class label(widget):
+ def __new__(self, widget parent, text):
+ ulabel = utf8(text)
+ self.w = x3label(parent.w, ulabel)
+
+cdef class edittext(widget):
+ def __new__(self, widget parent, char *cmd):
+ self.w = x3edittext(parent.w, cmd)
+
+cdef int x3py_key(x3viewclient *self, char *keyname, int mods, int key):
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ return py_client.key(keyname, mods, key)
+
+cdef void x3py_mouse(x3viewclient *self, int button, int mods, double x, double y):
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ py_client.mouse(button, mods, x, y)
+
+cdef int dbl_to_byte(double x):
+ if x <= 0: return 0
+ if x >= 1: return 255
+ return x * 255 + 0.5
+
+cdef class x3dc_wrap:
+ cdef x3dc *dc
+
+ property rect:
+ def __get__(self):
+ return (self.dc.x, self.dc.y, self.dc.width, self.dc.height)
+
+ def moveto(self, double x, double y):
+ x3moveto(self.dc, x, y)
+ def lineto(self, double x, double y):
+ x3lineto(self.dc, x, y)
+ def curveto(self, double x1, double y1, double x2, double y2, double x3, double y3):
+ x3curveto(self.dc, x1, y1, x2, y2, x3, y3)
+ def closepath(self):
+ x3closepath(self.dc)
+ def rectangle(self, double x, double y, double width, double height):
+ x3rectangle(self.dc, x, y, width, height)
+ def currentpoint(self):
+ cdef double x
+ cdef double y
+ x3getcurrentpoint(self.dc, &x, &y)
+ return (x, y)
+ def setrgba(self, double r, double g, double b, double a):
+ x3setrgba(self.dc, (dbl_to_byte(r) << 24) |
+ (dbl_to_byte(g) << 16) |
+ (dbl_to_byte(b) << 8) |
+ dbl_to_byte(a))
+ def setlinewidth(self, double w):
+ x3setlinewidth(self.dc, w)
+ def fill(self):
+ x3fill(self.dc)
+ def stroke(self):
+ x3stroke(self.dc)
+ def selectfont(self, char *name, int slant, int weight):
+ x3selectfont(self.dc, name, slant, weight)
+ def setfontsize(self, double size):
+ x3setfontsize(self.dc, size)
+ def showtext(self, text):
+ utext = utf8(text)
+ x3showtext(self.dc, utext)
+ def textextents(self, text):
+ cdef x3extents ext
+ utext = utf8(text)
+ x3textextents(self.dc, utext, &ext)
+ return (ext.x_bearing, ext.y_bearing,
+ ext.width, ext.height,
+ ext.x_advance, ext.y_advance)
+
+
+cdef void x3py_draw(x3viewclient *self, x3dc *dc):
+ cdef x3dc_wrap dc_wrap
+ py_client = <object>(<x3viewclient_py *>self).py_client
+ dc_wrap = x3dc_wrap()
+ dc_wrap.dc = dc
+ py_client.draw(dc_wrap)
+
+cdef class view(widget):
+ def __new__(self, widget parent, int flags, viewclient):
+ cdef x3viewclient_py *vc
+ vc = <x3viewclient_py *>PyMem_Malloc(sizeof(x3viewclient_py))
+ x3viewclient_init(&vc.base)
+ vc.py_client = <void *>viewclient
+ Py_INCREF(viewclient)
+ vc.base.key = x3py_key
+ vc.base.mouse = x3py_mouse
+ vc.base.draw = x3py_draw
+ self.w = x3view(parent.w, flags, &vc.base)
+ viewclient.view = self
+ def dirty(self):
+ x3view_dirty(self.w)
+ def scrollto(self, int x, int y, int width, int height):
+ x3view_scrollto(self.w, x, y, width, height)
+
+def main():
+ x3main()
+
+def gmw(g, m, w):
+ return X3_GMW(g, m, w)
+platform = gmw("gtk", "mac", "win32")
+
+x3init(0, NULL)
diff --git a/third_party/spiro/x3/test.c b/third_party/spiro/x3/test.c
new file mode 100644
index 0000000..53fae33
--- /dev/null
+++ b/third_party/spiro/x3/test.c
@@ -0,0 +1,158 @@
+/* A test app for X3. */
+
+#include "x3.h"
+#include <stdlib.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;
+}
+
+#if defined(X3_GTK) || defined(X3_CARBON)
+static void test_viewclient_draw(x3viewclient *self, x3dc *dc)
+{
+ if (dc->buf) {
+ int i, j;
+
+ for (i = 0; i < dc->height; i++) {
+ for (j = 0; j < dc->width; j++) {
+ dc->buf[i * dc->rowstride + j * 3] = i;
+ dc->buf[i * dc->rowstride + j * 3 + 1] = 0x80;
+ dc->buf[i * dc->rowstride + j * 3 + 2] = j;
+ }
+ }
+ } else {
+ x3extents ext;
+
+ x3moveto(dc, 10, 10);
+ x3curveto(dc, 200, 10, 100, 100, 490, 100);
+ x3stroke(dc);
+ x3selectfont(dc, "Nimbus Roman No9 L", 0, 0);
+ x3moveto(dc, 10, 50);
+ x3setfontsize(dc, 16);
+ x3showtext(dc, "hello, world!");
+ x3textextents(dc, "hello, world!", &ext);
+ printf("text extents: %g %g %g %g %g %g\n",
+ ext.x_bearing, ext.y_bearing,
+ ext.width, ext.height,
+ ext.x_advance, ext.y_advance);
+ }
+}
+
+static int test_viewclient_key(x3viewclient *self,
+ char *keyname, int mods, int key)
+{
+ printf("view key: %s %d %d\n", keyname, mods, key);
+ return 1;
+}
+
+static void test_viewclient_mouse(x3viewclient *self,
+ int button, int mods,
+ double x, double y)
+{
+ printf("view button: %d %d %g %g\n", button, mods, x, y);
+}
+
+x3viewclient *test_viewclient(void)
+{
+ x3viewclient *result = (x3viewclient *)malloc(sizeof(x3viewclient));
+ x3viewclient_init(result);
+ result->draw = test_viewclient_draw;
+ result->key = test_viewclient_key;
+ result->mouse = test_viewclient_mouse;
+
+ return result;
+}
+
+#ifdef X3_USEMAIN
+int
+main(int argc, char **argv)
+{
+ x3widget *mainwin;
+ x3widget *pane;
+ x3widget *vbox;
+ x3widget *m;
+ x3viewflags viewflags = x3view_click | x3view_hover | x3view_key;
+
+ 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>,");
+
+ m = x3menu(mainwin, "Edit");
+
+ x3menuitem(m, "ctrl-delete", "cdel", "<ctl>Delete");
+ x3menuitem(m, "ctrl-f1", "cf1", "<ctl>F1");
+ if (0) {
+ int i, j;
+
+ for (j = 0; j < 3; j++) {
+ char mname[16];
+ mname[0] = '0' + j;
+ mname[1] = 0;
+ m = x3menu(mainwin, mname);
+ for (i = 0x20 + 0x20 * j; i < 0x40 + 0x20 * j; i++) {
+ char name[16];
+
+ name[0] = i;
+ name[1] = 0;
+ x3menuitem(m, name, name, name);
+ }
+ }
+ }
+#endif
+
+#if 1
+ pane = x3vpane(mainwin);
+ vbox = x3vbox(x3pad(pane, 10, 10, 10, 10), 0, 5);
+
+ x3setpacking(vbox, FALSE, FALSE, 0);
+ x3button(x3align(vbox, x3center), "foo", "Click me!");
+ x3button(vbox, "bar", "Click me too!");
+ x3setactive(x3button(vbox, "bar", "But not me"), 0);
+ x3label(vbox, "I am a label.");
+ x3edittext(vbox, "etxt");
+#endif
+
+ x3setpacking(vbox, TRUE, TRUE, 0);
+ viewflags |= x3view_2d;
+ viewflags |= x3view_scroll;
+ x3view(pane, viewflags, test_viewclient());
+
+#if 0
+ x3_window_show(mainwin);
+#endif
+
+ x3main();
+ return 0;
+}
+#endif
+#endif
+
+#ifdef X3_USEWINMAIN
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
+ int iCmdShow)
+{
+ //printf("winmain\n");
+ x3widget *mainwin;
+ x3widget *vbox;
+
+ x3init_win32(hInstance);
+
+ mainwin = x3window(x3window_main, "untitled", mycallback, NULL);
+ x3_window_show(mainwin);
+ vbox = x3vbox(mainwin, 0, 5);
+ x3button(vbox, "foo", "Click me!");
+ x3button(vbox, "bar", "And me too!");
+ x3main();
+ return 0;
+}
+#endif
diff --git a/third_party/spiro/x3/x3.h b/third_party/spiro/x3/x3.h
new file mode 100644
index 0000000..a056583
--- /dev/null
+++ b/third_party/spiro/x3/x3.h
@@ -0,0 +1,293 @@
+/* X3 is a lightweight toolset for building cross-platform applications. */
+
+typedef struct _x3widget x3widget;
+
+typedef struct _x3viewclient x3viewclient;
+
+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;
+
+typedef enum {
+ x3view_click = 1,
+ x3view_hover = 2,
+ x3view_key = 4,
+ x3view_2d = 0x100,
+ x3view_rgb = 0x200,
+ x3view_scroll = 0x10000
+} x3viewflags;
+
+typedef struct _x3dc x3dc;
+
+struct _x3viewclient {
+ void (*destroy)(x3viewclient *self);
+ void (*mouse)(x3viewclient *self, int buttons, int mods,
+ double x, double y);
+ int (*key)(x3viewclient *self, char *keyname, int mods, int key);
+ void (*draw)(x3viewclient *self, x3dc *dc);
+};
+
+/* Windows and Carbon both use left/top/right/bottom nomenclature,
+ at least for int rects, while gtk+ uses x/y/width/height. */
+typedef struct {
+ double x0;
+ double y0;
+ double x1;
+ double y1;
+} x3rect;
+
+#ifdef X3_GTK
+
+#include <gtk/gtk.h>
+
+#define X3_GMW(g, m, w) g
+
+struct _x3dc {
+ /* move to x3rect structure? */
+ int x;
+ int y;
+ int width;
+ int height;
+
+ cairo_t *cr;
+
+ /* for rgb drawing */
+ unsigned char *buf;
+ int rowstride;
+};
+
+struct _x3widget {
+ char *name;
+ x3widget *parent;
+ GtkWidget *widget;
+};
+
+typedef cairo_text_extents_t x3extents;
+
+#define X3_SHIFT_MASK GDK_SHIFT_MASK
+#define X3_CONTROL_MASK GDK_CONTROL_MASK
+#define X3_ALT_MASK GDK_MOD1_MASK
+/* there is no X3_CMD_MASK in gtk */
+
+#define X3_USEMAIN
+
+#endif
+
+#ifdef X3_CARBON
+
+#define X3_GMW(g, m, w) m
+
+#include <Carbon/Carbon.h>
+
+struct _x3dc {
+ /* move to x3rect structure? */
+ int x;
+ int y;
+ int width;
+ int height;
+
+ CGContextRef ctx;
+ CGMutablePathRef path;
+
+ /* for rgb drawing */
+ char *buf;
+ int rowstride;
+};
+
+typedef struct _x3type x3type;
+
+struct _x3type {
+ void (*sizereq)(x3widget *w);
+ void (*sizealloc)(x3widget *w, x3rect *r);
+ void (*add)(x3widget *w, x3widget *child);
+};
+
+typedef enum {
+ x3carbonnone,
+ x3carbonhiview,
+ x3carbonwindow,
+ x3carbonmenu,
+ x3carbonmenuitem,
+} x3carbonvar;
+
+typedef enum {
+ x3flag_needsizereq = 1,
+ x3flag_needsizealloc = 2
+} x3widgetflags;
+
+struct _x3widget {
+ const x3type *type;
+ x3widgetflags flags;
+ x3rect sizerequest;
+ char *name;
+ x3widget *parent;
+ x3carbonvar var;
+ union {
+ HIViewRef hiview;
+ WindowRef window;
+ MenuRef menu;
+ int menuitem;
+ } u;
+ int n_children;
+ x3widget **children;
+};
+
+typedef struct {
+ double x_bearing;
+ double y_bearing;
+ double width;
+ double height;
+ double x_advance;
+ double y_advance;
+} x3extents;
+
+#define X3_SHIFT_MASK shiftKey
+#define X3_CONTROL_MASK controlKey
+#define X3_CMD_MASK cmdKey
+#define X3_ALT_MASK optionKey
+
+/* todo: figure out how to plumb mouse event */
+#define X3_BUTTON1_MASK 0
+#define X3_2BUTTON_PRESS 0
+#define X3_3BUTTON_PRESS 0
+
+#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, x3rect *r);
+ void (*add)(x3widget *w, x3widget *child);
+};
+
+typedef enum {
+ x3winnone,
+ x3winhwnd,
+} x3winvar;
+
+typedef enum {
+ x3flag_needsizereq = 1,
+ x3flag_needsizealloc = 2
+} x3widgetflags;
+
+typedef struct {
+ double x_bearing;
+ double y_bearing;
+ double width;
+ double height;
+ double x_advance;
+ double y_advance;
+} x3extents;
+
+struct _x3widget {
+ const x3type *type;
+ char *name;
+ x3widget *parent;
+ x3widgetflags flags;
+ x3rect sizerequest;
+ x3winvar 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);
+
+x3widget *x3window(x3windowflags flags, 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 *x3hpane(x3widget *parent);
+x3widget *x3vpane(x3widget *parent);
+x3widget *x3button(x3widget *parent, char *name, char *label);
+x3widget *x3label(x3widget *parent, char *label);
+x3widget *x3edittext(x3widget *parent, char *cmd);
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc);
+void x3view_dirty(x3widget *w);
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height);
+
+void x3viewclient_init(x3viewclient *vc);
+
+void x3window_setdefaultsize(x3widget *w, int width, int height);
+
+void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink,
+ int child2_resize, int child2_shrink);
+
+void x3setactive(x3widget *w, int active);
+int x3hasfocus(x3widget *w);
+void x3setpacking(x3widget *w, int fill, int expand, int padding);
+
+extern int x3n_winopen;
+
+/* 2d drawing functions */
+
+void x3moveto(x3dc *dc, double x, double y);
+void x3lineto(x3dc *dc, double x, double y);
+void x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3);
+void x3closepath(x3dc *dc);
+void x3rectangle(x3dc *dc, double x, double y, double width, double height);
+void x3getcurrentpoint(x3dc *dc, double *px, double *py);
+void x3setrgba(x3dc *dc, unsigned int rgba);
+void x3setlinewidth(x3dc *dc, double w);
+void x3fill(x3dc *dc);
+void x3stroke(x3dc *dc);
+
+/* This is an overly simple text api, based on cairo's. It may be phased
+ out in favor of a more capable one. */
+void x3selectfont(x3dc *dc, char *fontname, int slant, int weight);
+void x3setfontsize(x3dc *dc, double size);
+void x3showtext(x3dc *dc, char *text);
+void x3textextents(x3dc *dc, char *text, x3extents *extents);
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+/* Internals for carbon/win32 */
+void x3widget_init(x3widget *w, const x3type *type);
+void x3add_default(x3widget *parent, x3widget *child);
+void x3add(x3widget *parent, x3widget *child);
+#endif
diff --git a/third_party/spiro/x3/x3carbon.c b/third_party/spiro/x3/x3carbon.c
new file mode 100644
index 0000000..7929e47
--- /dev/null
+++ b/third_party/spiro/x3/x3carbon.c
@@ -0,0 +1,1002 @@
+#include "x3.h"
+#include "x3common.h"
+
+/* Globals for managing sync */
+
+#define VERBOSE
+
+#define kX3ViewClassID CFSTR("com.levien.x3.X3View")
+#define kX3ViewPrivate 'X3_v'
+
+/* 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 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 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;
+ x3add(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;
+ x3add(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;
+ x3add(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);
+ x3add(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);
+
+ x3initqs();
+}
+
+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 if (eclass = kEventClassWindow && ekind == kEventWindowBoundsChanged) {
+ /* todo: only queue size request when size changes, not just pos */
+ x3qsizereq(&z->base);
+ x3sync();
+ } 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, x3rect *r)
+{
+ int i;
+ Rect bounds;
+ x3rect child_r;
+
+ GetWindowBounds(w->u.window, kWindowContentRgn, &bounds);
+ child_r.x0 = 0;
+ child_r.x1 = bounds.right - bounds.left;
+ child_r.y0 = 0;
+ child_r.y1 = bounds.bottom - bounds.top;
+ printf("x3window_sizealloc (%d, %d) - (%d, %d)\n",
+ bounds.left, bounds.top, bounds.right, bounds.bottom);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ if (child->type->sizealloc)
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3windowtype = { x3window_sizereq,
+ x3window_sizealloc,
+ x3add_default };
+
+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);
+
+ x3qshow(result);
+ return result;
+}
+
+x3type x3menutype = { NULL, NULL, x3add_default };
+
+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, x3add_default };
+
+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 x3button_sizereq(x3widget *w)
+{
+ SInt16 offset;
+ Rect r = { 0, 0, 0, 0 };
+
+ GetBestControlRect(w->u.hiview, &r, &offset);
+ w->sizerequest.x0 = r.left;
+ w->sizerequest.y0 = r.top;
+ w->sizerequest.x1 = r.right;
+ w->sizerequest.y1 = r.bottom;
+#ifdef VERBOSE
+ printf("button sizereq = (%g, %g) - (%g, %g)\n",
+ w->sizerequest.x0, w->sizerequest.y0,
+ w->sizerequest.x1, w->sizerequest.y1);
+#endif
+}
+
+void x3button_sizealloc(x3widget *w, x3rect *r)
+{
+ Rect bounds;
+
+ bounds.left = r->x0;
+ bounds.top = r->y0;
+ bounds.right = r->x1;
+ bounds.bottom = r->y1;
+ /* TODO probably want to use HIViewSetFrame instead */
+ printf("button sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ SetControlBounds(w->u.hiview, &bounds);
+}
+
+x3type x3buttontype = { x3button_sizereq,
+ x3button_sizealloc,
+ x3add_default };
+
+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 *x3label(x3widget *parent, char *text)
+{
+ WindowRef window = x3window_of(parent);
+ Rect r = {10, 10, 30, 100};
+ ControlRef control;
+ OSStatus err;
+ Boolean singleline = true;
+ CFStringRef cftext = CFStringCreateWithCString(NULL, text,
+ kCFStringEncodingUTF8);
+
+ err = CreateStaticTextControl(window, &r, cftext,
+ NULL, &control);
+ CFRelease(cftext);
+#if 0
+ SetControlData(control, kControlEntireControl,
+ kControlEditTextSingleLineTag, sizeof(Boolean),
+ &singleline);
+#endif
+ return x3widget_new_hiview(parent, NULL, &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);
+}
+
+x3widget *x3hpane(x3widget *parent)
+{
+ return NULL;
+}
+
+x3widget *x3vpane(x3widget *parent)
+{
+ return NULL;
+}
+
+typedef struct
+{
+ HIViewRef view;
+ x3viewflags flags;
+ x3viewclient *vc;
+} x3view_data;
+
+static OSStatus
+x3view_construct(EventRef inEvent)
+{
+ OSStatus err;
+ x3view_data *data;
+
+ data = (x3view_data *)malloc(sizeof(x3view_data));
+ require_action(data != NULL, CantMalloc, err = memFullErr);
+ err = GetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeHIObjectRef, NULL, sizeof(HIObjectRef), NULL,
+ (HIObjectRef *)&data->view);
+ require_noerr(err, ParameterMissing);
+ err = SetEventParameter(inEvent, kEventParamHIObjectInstance,
+ typeVoidPtr, sizeof(x3view_data *), &data);
+
+ data->vc = NULL;
+
+ ParameterMissing:
+ if (err != noErr)
+ free(data);
+
+ CantMalloc:
+ return err;
+}
+
+static OSStatus
+x3view_destruct(EventRef inEvent, x3view_data *inData)
+{
+ free(inData);
+ return noErr;
+}
+
+static OSStatus
+x3view_initialize(EventHandlerCallRef inCallRef, EventRef inEvent,
+ x3view_data *inData)
+{
+ OSStatus err;
+ HIRect bounds;
+
+ err = CallNextEventHandler(inCallRef, inEvent);
+ require_noerr(err, TroubleInSuperClass);
+
+ err = GetEventParameter(inEvent, 'Boun', typeHIRect,
+ NULL, sizeof(HIRect), NULL, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ HIViewSetFrame(inData->view, &bounds);
+
+ ParameterMissing:
+ TroubleInSuperClass:
+ return err;
+}
+
+static OSStatus
+x3view_draw(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ CGContextRef ctx;
+
+ err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef,
+ NULL, sizeof(CGContextRef), NULL, &ctx);
+ require_noerr(err, ParameterMissing);
+
+#ifdef VERBOSE
+ printf("x3view_draw!\n");
+#endif
+
+ if (inData->vc && inData->vc->draw) {
+ x3dc dc;
+
+ /* set up bounds */
+ if (inData->flags & x3view_2d) {
+ dc.ctx = ctx;
+ dc.path = NULL;
+ dc.buf = NULL;
+
+ inData->vc->draw(inData->vc, &dc);
+ if (dc.path) {
+ CGPathRelease(dc.path);
+ }
+ } else if (inData->flags & x3view_rgb) {
+ /* todo */
+ }
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_get_data(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ OSType tag;
+ Ptr ptr;
+ Size outSize;
+
+ /* Probably could use a bit more error checking here, for type
+ and size match. Also, just returning an x3view_data seems a
+ little hacky. */
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+
+ if (tag == kX3ViewPrivate) {
+ *((x3view_data **)ptr) = inData;
+ outSize = sizeof(x3view_data *);
+ } else
+ err = errDataNotSupported;
+
+ if (err == noErr)
+ err = SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeLongInteger,
+ sizeof(Size), &outSize);
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_set_data(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ Ptr ptr;
+ OSType tag;
+
+ err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration,
+ NULL, sizeof(OSType), NULL, &tag);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr,
+ NULL, sizeof(Ptr), NULL, &ptr);
+ require_noerr(err, ParameterMissing);
+
+ if (tag == 'X3vc') {
+ inData->vc = *(x3viewclient **)ptr;
+ } else if (tag == 'X3vf') {
+ inData->flags = *(x3viewflags *)ptr;
+ } else
+ err = errDataNotSupported;
+
+ ParameterMissing:
+ return err;
+}
+
+static OSStatus
+x3view_hittest(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ HIRect bounds;
+ ControlPartCode part;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ if (CGRectContainsPoint(bounds, where))
+ part = 1;
+ else
+ part = kControlNoPart;
+ err = SetEventParameter(inEvent, kEventParamControlPart,
+ typeControlPartCode, sizeof(ControlPartCode),
+ &part);
+ printf("hittest %g, %g!\n", where.x, where.y);
+
+ ParameterMissing:
+ return err;
+}
+
+/*
+ If we need more sophisticated tracking (like mixing key events), there's
+ a good discussion here:
+ http://lists.apple.com/archives/Carbon-development/2001/Apr/msg01688.html
+*/
+static OSStatus
+x3view_track(EventRef inEvent, x3view_data *inData)
+{
+ OSStatus err;
+ HIPoint where;
+ MouseTrackingResult mouseStatus;
+ HIRect bounds;
+ Rect windBounds;
+ Point theQDPoint;
+ UInt32 mods;
+
+ err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint,
+ NULL, sizeof(HIPoint), NULL, &where);
+ require_noerr(err, ParameterMissing);
+
+ err = GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32,
+ NULL, sizeof(UInt32), NULL, &mods);
+ require_noerr(err, ParameterMissing);
+
+ err = HIViewGetBounds(inData->view, &bounds);
+ require_noerr(err, ParameterMissing);
+
+ GetWindowBounds(GetControlOwner(inData->view), kWindowStructureRgn, &windBounds);
+
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 1, mods, where.x, where.y);
+
+#ifdef VERBOSE
+ printf("press: %g, %g!\n", where.x, where.y);
+#endif
+ //pe_view_button_press(inData, where.x, where.y, 0);
+
+ mouseStatus = kMouseTrackingMouseDown;
+ while (mouseStatus != kMouseTrackingMouseUp) {
+ TrackMouseLocation(NULL, &theQDPoint, &mouseStatus);
+ where.x = theQDPoint.h - windBounds.left;
+ where.y = theQDPoint.v - windBounds.top;
+ HIViewConvertPoint(&where, NULL, inData->view);
+#ifdef VERBOSE
+ printf("track %d: %g, %g!\n", mouseStatus, where.x, where.y);
+#endif
+ if (mouseStatus == kMouseTrackingMouseUp) {
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, -1, mods, where.x, where.y);
+ } else if (mouseStatus == kMouseTrackingKeyModifiersChanged) {
+ mods = GetCurrentEventKeyModifiers();
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 0, mods, where.x, where.y);
+ } else {
+ if (inData->vc && inData->vc->mouse)
+ inData->vc->mouse(inData->vc, 0, mods, where.x, where.y);
+ }
+ }
+
+ ParameterMissing:
+ return err;
+}
+
+pascal OSStatus
+x3view_handler(EventHandlerCallRef inCallRef,
+ EventRef inEvent,
+ void* inUserData )
+{
+ OSStatus err = eventNotHandledErr;
+ UInt32 eventClass = GetEventClass(inEvent);
+ UInt32 eventKind = GetEventKind(inEvent);
+ x3view_data *data = (x3view_data *)inUserData;
+
+ printf("view handler %c%c%c%c %d\n",
+ (eventClass >> 24) & 0xff,
+ (eventClass >> 16) & 0xff,
+ (eventClass >> 8) & 0xff,
+ (eventClass >> 0) & 0xff,
+ eventKind);
+ switch (eventClass) {
+ case kEventClassHIObject:
+ switch (eventKind) {
+ case kEventHIObjectConstruct:
+ err = x3view_construct(inEvent);
+ break;
+ case kEventHIObjectInitialize:
+ err = x3view_initialize(inCallRef, inEvent, data);
+ break;
+ case kEventHIObjectDestruct:
+ err = x3view_destruct(inEvent, data);
+ break;
+ }
+ break;
+ case kEventClassControl:
+ switch (eventKind) {
+ case kEventControlInitialize:
+ err = noErr;
+ break;
+ case kEventControlDraw:
+ err = x3view_draw(inEvent, data);
+ break;
+ case kEventControlGetData:
+ err = x3view_get_data(inEvent, data);
+ break;
+ case kEventControlSetData:
+ err = x3view_set_data(inEvent, data);
+ break;
+ case kEventControlTrack:
+ err = x3view_track(inEvent, data);
+ break;
+ case kEventControlHitTest:
+ err = x3view_hittest(inEvent, data);
+ break;
+ case kEventControlClick:
+ printf("click event\n");
+ break;
+ /*...*/
+ }
+ break;
+ }
+ return err;
+}
+
+static OSStatus
+x3view_register(void)
+{
+ OSStatus err = noErr;
+ static HIObjectClassRef x3view_ClassRef = NULL;
+
+ if (x3view_ClassRef == NULL) {
+ EventTypeSpec eventList[] = {
+ { kEventClassHIObject, kEventHIObjectConstruct },
+ { kEventClassHIObject, kEventHIObjectInitialize },
+ { kEventClassHIObject, kEventHIObjectDestruct },
+
+ { kEventClassControl, kEventControlActivate },
+ { kEventClassControl, kEventControlDeactivate },
+ { kEventClassControl, kEventControlDraw },
+ { kEventClassControl, kEventControlHiliteChanged },
+ { kEventClassControl, kEventControlHitTest },
+ { kEventClassControl, kEventControlInitialize },
+ { kEventClassControl, kEventControlGetData },
+ { kEventClassControl, kEventControlSetData },
+ { kEventClassControl, kEventControlTrack },
+ { kEventClassControl, kEventControlClick }
+ };
+ err = HIObjectRegisterSubclass(kX3ViewClassID,
+ kHIViewClassID,
+ 0,
+ x3view_handler,
+ GetEventTypeCount(eventList),
+ eventList,
+ NULL,
+ &x3view_ClassRef);
+ }
+ return err;
+}
+
+OSStatus x3view_create(
+ WindowRef inWindow,
+ const HIRect* inBounds,
+ HIViewRef* outView)
+{
+ OSStatus err;
+ EventRef event;
+
+ err = x3view_register();
+ require_noerr(err, CantRegister);
+
+ err = CreateEvent(NULL, kEventClassHIObject, kEventHIObjectInitialize,
+ GetCurrentEventTime(), 0, &event);
+ require_noerr(err, CantCreateEvent);
+
+ if (inBounds != NULL) {
+ err = SetEventParameter(event, 'Boun', typeHIRect, sizeof(HIRect),
+ inBounds);
+ require_noerr(err, CantSetParameter);
+ }
+
+ err = HIObjectCreate(kX3ViewClassID, event, (HIObjectRef*)outView);
+ require_noerr(err, CantCreate);
+
+ if (inWindow != NULL) {
+ HIViewRef root;
+ err = GetRootControl(inWindow, &root);
+ require_noerr(err, CantGetRootView);
+ err = HIViewAddSubview(root, *outView);
+ }
+ CantCreate:
+ CantGetRootView:
+ CantSetParameter:
+ CantCreateEvent:
+ ReleaseEvent(event);
+ CantRegister:
+ return err;
+}
+
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc)
+{
+ WindowRef window = x3window_of(parent);
+ HIRect r = { {10, 10}, {30, 100} };
+ OSStatus err;
+ HIViewRef view;
+
+ err = x3view_create(window, &r, &view);
+ err = SetControlData(view, 1, 'X3vc', sizeof(x3view_data *), &vc);
+ err = SetControlData(view, 1, 'X3vf', sizeof(x3viewflags), &flags);
+ HIViewSetVisible(view, true);
+ return x3widget_new_hiview(parent, NULL, &x3buttontype, view);
+}
+
+void
+x3view_dirty(x3widget *w)
+{
+ if (w->var == x3carbonhiview)
+ HIViewSetNeedsDisplay(w->u.hiview, true);
+}
+
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+{
+ /* todo */
+}
+
+void x3viewclient_init(x3viewclient *vc)
+{
+ vc->destroy = NULL;
+ vc->mouse = NULL;
+ vc->key = NULL;
+ vc->draw = NULL;
+}
+
+/* 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);
+ }
+}
+
+int x3hasfocus(x3widget *w)
+{
+ if (w->var == x3carbonhiview) {
+ return HIViewSubtreeContainsFocus(w->u.hiview);
+ } else
+ return 0;
+}
+
+/* 2d drawing functions, implemented using Quartz */
+
+void
+x3moveto(x3dc *dc, double x, double y)
+{
+ if (dc->path == NULL) {
+ dc->path = CGPathCreateMutable();
+ }
+ CGPathMoveToPoint(dc->path, NULL, x, y);
+}
+
+void
+x3lineto(x3dc *dc, double x, double y)
+{
+ CGPathAddLineToPoint(dc->path, NULL, x, y);
+}
+
+void
+x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+{
+ CGPathAddCurveToPoint(dc->path, NULL, x1, y1, x2, y2, x3, y3);
+}
+
+void
+x3closepath(x3dc *dc)
+{
+ CGPathCloseSubpath(dc->path);
+}
+
+void
+x3rectangle(x3dc *dc, double x, double y, double width, double height)
+{
+ CGRect rect;
+
+ if (dc->path == NULL) {
+ dc->path = CGPathCreateMutable();
+ }
+ rect.origin.x = x;
+ rect.origin.y = y;
+ rect.size.width = width;
+ rect.size.height = height;
+ CGPathAddRect(dc->path, NULL, rect);
+}
+
+void
+x3getcurrentpoint(x3dc *dc, double *px, double *py)
+{
+ CGPoint point = CGPathGetCurrentPoint(dc->path);
+
+ *px = point.x;
+ *py = point.y;
+}
+
+void
+x3setrgba(x3dc *dc, unsigned int rgba)
+{
+ CGContextSetRGBFillColor(dc->ctx,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+ CGContextSetRGBStrokeColor(dc->ctx,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+}
+
+void
+x3setlinewidth(x3dc *dc, double w)
+{
+ CGContextSetLineWidth(dc->ctx, w);
+}
+
+void
+x3fill(x3dc *dc)
+{
+ CGContextAddPath(dc->ctx, dc->path);
+ CGPathRelease(dc->path);
+ dc->path = NULL;
+ CGContextFillPath(dc->ctx);
+}
+
+void
+x3stroke(x3dc *dc)
+{
+ CGContextAddPath(dc->ctx, dc->path);
+ CGPathRelease(dc->path);
+ dc->path = NULL;
+ CGContextStrokePath(dc->ctx);
+}
+
+void
+x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+{
+ CGContextSelectFont(dc->ctx, fontname, 8.0, kCGEncodingMacRoman);
+}
+
+void
+x3setfontsize(x3dc *dc, double size)
+{
+ CGContextSetFontSize(dc->ctx, size);
+}
+
+void
+x3showtext(x3dc *dc, char *text)
+{
+ CGPoint point = CGPathGetCurrentPoint(dc->path);
+ CGAffineTransform textmat;
+
+ textmat = CGAffineTransformMakeScale(1, -1);
+ CGContextSetTextMatrix(dc->ctx, textmat);
+ CGContextShowTextAtPoint(dc->ctx, point.x, point.y, text, strlen(text));
+}
+
+void x3textextents(x3dc *dc, char *text, x3extents *extents)
+{
+ /* todo */
+}
diff --git a/third_party/spiro/x3/x3common.c b/third_party/spiro/x3/x3common.c
new file mode 100644
index 0000000..e4845ff
--- /dev/null
+++ b/third_party/spiro/x3/x3common.c
@@ -0,0 +1,359 @@
+/* Functions common to more than one platform. */
+
+#include <stdlib.h>
+
+#include "x3.h"
+#include "x3common.h"
+
+int n_x3needshow = 0;
+int n_x3needshow_max = 0;
+x3widget **x3needshow = NULL;
+
+#if defined(X3_GTK) || defined(X3_WIN32)
+int x3n_winopen = 0;
+#endif
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+int n_x3needsizereqs = 0;
+int n_x3needsizereqs_max = 0;
+x3widget **x3needsizereqs = NULL;
+
+int n_x3needsizeallocs = 0;
+int n_x3needsizeallocs_max = 0;
+x3widget **x3needsizeallocs = NULL;
+
+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 x3add_default(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;
+}
+
+void x3add(x3widget *parent, x3widget *child)
+{
+ parent->type->add(parent, child);
+}
+
+/* Widgets common to carbon and win32 platforms */
+
+typedef struct {
+ x3widget base;
+ int homogeneous;
+ int spacing;
+ int cur_child_prop;
+ int *child_props; /* bit 0=expand, bit 1=fill, bits 2:31=padding */
+} x3widget_box;
+
+void x3vbox_sizereq(x3widget *w)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ int i;
+ int spacing = wb->spacing;
+
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int padding = wb->child_props[i] >> 2;
+
+ if (i < w->n_children - 1)
+ childh += spacing;
+ w->sizerequest.y1 += childh + 2 * padding;
+ if (childw > w->sizerequest.x1)
+ w->sizerequest.x1 = childw;
+ }
+}
+
+void x3vbox_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ int i;
+ x3rect child_r = *r;
+ int spacing = wb->spacing;
+ int n_extend = 0;
+ int n_stretch, i_stretch = 0;
+ int extra;
+
+ /* todo: impl padding & homog, factor hbox/vbox common */
+ printf("vbox sizealloc = (%g, %g) - (%g, %g), req was %g x %g\n",
+ r->x0, r->y0, r->x1, r->y1,
+ w->sizerequest.x1, w->sizerequest.y1);
+ extra = r->y1 - r->y0 - w->sizerequest.y1;
+ for (i = 0; i < w->n_children; i++)
+ if (wb->child_props[i] & 1)
+ n_extend++;
+ n_stretch = n_extend ? n_extend : w->n_children;
+ printf("extra = %d, n_stretch = %d\n", extra, n_stretch);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int my_extra;
+ int next_top;
+
+ if (n_extend == 0 || (wb->child_props[i] & 1)) {
+ my_extra = (extra * (i_stretch + 1)) / n_stretch -
+ (extra * i_stretch) / n_stretch;
+ i_stretch++;
+ } else
+ my_extra = 0;
+ next_top = child_r.y0 + childh + spacing + my_extra;
+
+ if (wb->child_props[i] & 2) {
+ childh += my_extra;
+ } else {
+ child_r.y0 += my_extra >> 1;
+ }
+ child_r.y1 = child_r.y0 + childh;
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+
+ child_r.y0 = next_top;
+ }
+}
+
+void x3vbox_add(x3widget *w, x3widget *child)
+{
+ x3widget_box *wb = (x3widget_box *)w;
+ const int n_children_init = 4;
+
+ if (w->n_children == 0)
+ wb->child_props = (int *)malloc(sizeof(int) * n_children_init);
+ else if (w->n_children >= n_children_init &&
+ !(w->n_children & (w->n_children - 1)))
+ wb->child_props = (int *)realloc(wb->child_props,
+ sizeof(int) * (w->n_children << 1));
+ wb->child_props[w->n_children] = wb->cur_child_prop;
+ x3add_default(w, child);
+}
+
+x3type x3vboxtype = { x3vbox_sizereq,
+ x3vbox_sizealloc,
+ x3vbox_add };
+
+x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
+{
+ x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box));
+ x3widget_init(&result->base, &x3vboxtype);
+ x3add(parent, &result->base);
+ result->homogeneous = homogeneous;
+ result->spacing = spacing;
+ result->cur_child_prop = 3;
+ x3qsizereq(&result->base);
+ return &result->base;
+}
+
+void x3setpacking(x3widget *w, int fill, int expand, int padding)
+{
+ if (w->type == &x3vboxtype) {
+ x3widget_box *wb = (x3widget_box *)w;
+ int child_props = padding << 2;
+
+ if (fill) child_props |= 1;
+ if (expand) child_props |= 2;
+ wb->cur_child_prop = child_props;
+ }
+}
+
+typedef struct {
+ x3widget base;
+ x3alignment alignment;
+} x3widget_align;
+
+void x3align_sizereq(x3widget *w)
+{
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ w->sizerequest.x1 = childw;
+ w->sizerequest.y1 = childh;
+ }
+}
+
+void x3align_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_align *z = (x3widget_align *)w;
+ x3alignment a = z->alignment;
+ int xa = a & 3;
+ int ya = (a >> 2) & 3;
+ x3rect child_r = *r;
+
+ printf("align sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ if (xa < 3) {
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int pad = r->x1 - r->x0 - childw;
+ child_r.x0 += (pad * (1 + (xa >> 1) - (xa & 1)) + 1) >> 1;
+ child_r.x1 = child_r.x0 + childw;
+ }
+ if (ya < 3) {
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ int pad = r->y1 - r->y0 - childh;
+ child_r.y0 += (pad * (1 + (ya >> 1) - (ya & 1)) + 1) >> 1;
+ child_r.y1 = child_r.y0 + childh;
+ }
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3aligntype = { x3align_sizereq,
+ x3align_sizealloc,
+ x3add_default };
+
+x3widget *x3align(x3widget *parent, x3alignment alignment)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget_align));
+ x3widget_init(result, &x3aligntype);
+ x3add(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.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 0;
+ w->sizerequest.y1 = 0;
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ int childw = child->sizerequest.x1 - child->sizerequest.x0;
+ int childh = child->sizerequest.y1 - child->sizerequest.y0;
+ w->sizerequest.x1 = childw + z->l + z->r;
+ w->sizerequest.y1 = childh + z->t + z->b;
+ }
+}
+
+void x3pad_sizealloc(x3widget *w, x3rect *r)
+{
+ x3widget_pad *z = (x3widget_pad *)w;
+ x3rect child_r = *r;
+
+ printf("pad sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->n_children) {
+ x3widget *child = w->children[0];
+ child_r.x0 += z->l;
+ child_r.x1 -= z->r;
+ child_r.y0 += z->t;
+ child_r.y1 -= z->b;
+
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3padtype = { x3pad_sizereq,
+ x3pad_sizealloc,
+ x3add_default };
+
+x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget_pad));
+ x3widget_init(result, &x3padtype);
+ x3add(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;
+}
+
+#endif
+
+void x3initqs(void)
+{
+ n_x3needshow = 0;
+ x3needshow = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needshow_max = 16));
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+ n_x3needsizereqs = 0;
+ x3needsizereqs = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needsizereqs_max = 16));
+
+ n_x3needsizeallocs = 0;
+ x3needsizeallocs = (x3widget **)malloc(sizeof(x3widget *) *
+ (n_x3needsizeallocs_max = 16));
+#endif
+}
+
+void x3qshow(x3widget *w)
+{
+ if (n_x3needshow == n_x3needshow_max)
+ x3needshow = (x3widget **)realloc(x3needshow,
+ sizeof(x3widget *) *
+ (n_x3needshow_max <<= 1));
+ x3needshow[n_x3needshow++] = w;
+}
+
+void x3sync(void)
+{
+ int i;
+
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+ 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;
+#endif
+
+ for (i = 0; i < n_x3needshow; i++)
+ x3_window_show(x3needshow[i]);
+ n_x3needshow = 0;
+}
diff --git a/third_party/spiro/x3/x3common.h b/third_party/spiro/x3/x3common.h
new file mode 100644
index 0000000..fc616a2
--- /dev/null
+++ b/third_party/spiro/x3/x3common.h
@@ -0,0 +1,16 @@
+#if defined(X3_CARBON) || defined(X3_WIN32)
+
+void x3qsizereq(x3widget *w);
+
+void x3add_default(x3widget *parent, x3widget *child);
+void x3add(x3widget *parent, x3widget *child);
+
+#endif
+
+void x3initqs(void);
+void x3qshow(x3widget *w);
+void x3sync(void);
+
+
+/* provided by impls to common routines */
+void x3_window_show(x3widget *w);
diff --git a/third_party/spiro/x3/x3gtk.c b/third_party/spiro/x3/x3gtk.c
new file mode 100644
index 0000000..162f496
--- /dev/null
+++ b/third_party/spiro/x3/x3gtk.c
@@ -0,0 +1,778 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "x3.h"
+#include "x3common.h"
+
+void x3init(int *pargc, char ***pargv)
+{
+ gtk_init(pargc, pargv);
+ x3initqs();
+}
+
+static void
+x3_getfirst_callback(GtkWidget *widget, gpointer data)
+{
+ GtkWidget **pwidget = (GtkWidget **)data;
+
+ if (*pwidget == NULL)
+ *pwidget = widget;
+}
+
+static GtkWidget *x3_gtkwidget_getchild(GtkWidget *w)
+{
+ GtkWidget *child = NULL;
+ gtk_container_foreach(GTK_CONTAINER(w),
+ x3_getfirst_callback,
+ (gpointer)&child);
+ return child;
+}
+
+typedef struct {
+ x3widget base;
+ gboolean expand;
+ gboolean fill;
+ guint padding;
+} x3widget_box;
+
+static void x3widget_init(x3widget *w, x3widget *parent, char *name,
+ GtkWidget *widget)
+{
+ w->name = g_strdup(name);
+ w->widget = widget;
+ w->parent = parent;
+ if (parent) {
+ if (GTK_IS_WINDOW(parent->widget)) {
+ GtkWidget *vbox = x3_gtkwidget_getchild(parent->widget);
+
+ if (GTK_IS_MENU_ITEM(widget)) {
+ GtkWidget *first_child = x3_gtkwidget_getchild(vbox);
+ GtkWidget *menubar;
+
+ 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 if (GTK_IS_BOX(parent->widget)) {
+ x3widget_box *pwb = (x3widget_box *)parent;
+ gtk_box_pack_start(GTK_BOX(parent->widget), widget,
+ pwb->expand, pwb->fill, pwb->padding);
+ } else {
+ gtk_container_add(GTK_CONTAINER(parent->widget), widget);
+ }
+ }
+}
+
+static x3widget *x3widget_new(x3widget *parent, char *name, GtkWidget *widget)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+
+ x3widget_init(result, parent, name, widget);
+ return result;
+}
+
+static x3widget *x3box_new(x3widget *parent, GtkWidget *widget)
+{
+ x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box));
+
+ x3widget_init(&result->base, parent, NULL, widget);
+ result->expand = TRUE;
+ result->fill = TRUE;
+ result->padding = 0;
+ return &result->base;
+}
+
+void x3_window_show(x3widget *w)
+{
+ gtk_widget_show(w->widget);
+}
+
+void x3window_setdefaultsize(x3widget *w, int width, int height)
+{
+ gtk_window_set_default_size(GTK_WINDOW(w->widget), width, height);
+}
+
+void x3main(void)
+{
+ x3sync();
+ gtk_main();
+}
+
+/* some constructors */
+
+typedef struct {
+ x3widget base;
+ x3window_callback callback;
+ void *callback_data;
+
+ GtkAccelGroup *accel_group;
+} x3widget_window;
+
+gboolean x3window_delete(GtkWidget *window, GdkEvent *event, gpointer data)
+{
+ /* todo: pass this as a command callback */
+ if (--x3n_winopen <= 0)
+ gtk_main_quit();
+ return FALSE;
+}
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *callback_data)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ x3widget_window *result;
+
+ 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);
+ result = (x3widget_window *)malloc(sizeof(x3widget_window));
+ x3widget_init(&result->base, NULL, "mainwin", window);
+ result->callback = callback;
+ result->callback_data = callback_data;
+ result->accel_group = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(window), result->accel_group);
+ g_signal_connect(G_OBJECT(window), "delete-event",
+ G_CALLBACK(x3window_delete), result);
+ x3qshow(&result->base);
+ x3n_winopen++;
+ return &result->base;
+}
+
+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);
+}
+
+typedef struct {
+ x3widget base;
+ char *cmd;
+} x3widget_cmdable;
+
+static void x3doevent(x3widget_cmdable *wc, char *str)
+{
+ char *cmd = wc->cmd;
+ x3widget *w = &wc->base;
+ x3widget_window *ww;
+
+ while (w->parent) w = w->parent;
+ ww = (x3widget_window *)w;
+ ww->callback(w, ww->callback_data, cmd, str, NULL, NULL);
+ x3sync();
+}
+
+static void x3cmdable_clicked(GtkWidget *widget, gpointer data)
+{
+ x3widget_cmdable *wc = (x3widget_cmdable *)data;
+
+ x3doevent(wc, "command");
+}
+
+static const char *asciinames[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ "space",
+ "exclam",
+ "quotedbl",
+ "numbersign",
+ "dollar",
+ "percent",
+ "ampersand",
+ "apostrophe",
+ "parenleft",
+ "parenright",
+ "asterisk",
+ "plus",
+ "comma",
+ "minus",
+ "period",
+ "slash",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "colon",
+ "semicolon",
+ "less",
+ "equal",
+ "greater",
+ "question",
+ "at",
+ "<shift>a",
+ "<shift>b",
+ "<shift>c",
+ "<shift>d",
+ "<shift>e",
+ "<shift>f",
+ "<shift>g",
+ "<shift>h",
+ "<shift>i",
+ "<shift>j",
+ "<shift>k",
+ "<shift>l",
+ "<shift>m",
+ "<shift>n",
+ "<shift>o",
+ "<shift>p",
+ "<shift>q",
+ "<shift>r",
+ "<shift>s",
+ "<shift>t",
+ "<shift>u",
+ "<shift>v",
+ "<shift>w",
+ "<shift>x",
+ "<shift>y",
+ "<shift>z",
+ "bracketleft",
+ "backslash",
+ "bracketright",
+ "asciicircum",
+ "underscore",
+ "grave",
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "braceleft",
+ "bar",
+ "braceright",
+ "asciitilde"
+};
+
+/* return 1 on success */
+static int
+x3parseshortcut(const char *shortcut,
+ guint *accelerator_key, GdkModifierType *accelerator_mods)
+{
+ int len;
+ char tmp[256];
+ int i;
+
+ if (shortcut == NULL) return 0;
+ len = strlen(shortcut);
+ if (len >= sizeof(tmp) - 1) return 0;
+ strcpy(tmp, shortcut);
+ for (i = 0; i < len - 5; i++)
+ if (!memcmp(tmp + i, "<cmd>", 5))
+ memcpy(tmp + i, "<ctl>", 5);
+ if (len == 1 || tmp[len - 2] == '>') {
+ unsigned char c = (unsigned char)tmp[len-1];
+ if (c < sizeof(asciinames) / sizeof(asciinames[0]) && asciinames[c] &&
+ len + strlen(asciinames[c]) < sizeof(tmp))
+ strcpy(tmp + len - 1, asciinames[c]);
+ }
+ gtk_accelerator_parse(tmp, accelerator_key, accelerator_mods);
+ return *accelerator_key != 0 || *accelerator_mods != 0;
+}
+
+static GtkAccelGroup *
+x3getaccelgroup(x3widget *w)
+{
+ while (w->parent) w = w->parent;
+ if (!GTK_IS_WINDOW(w->widget)) return NULL;
+ return ((x3widget_window *)w)->accel_group;
+}
+
+x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut)
+{
+ GtkWidget *item;
+ x3widget_cmdable *result = (x3widget_cmdable *)malloc(sizeof(x3widget_cmdable));
+ guint accel_key;
+ GdkModifierType accel_mods;
+
+ item = gtk_menu_item_new_with_label(name);
+ x3widget_init(&result->base, parent, cmd, item);
+ result->cmd = g_strdup(cmd);
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(x3cmdable_clicked), result);
+
+ if (x3parseshortcut(shortcut, &accel_key, &accel_mods)) {
+ gtk_widget_add_accelerator(item, "activate", x3getaccelgroup(parent),
+ accel_key, accel_mods, GTK_ACCEL_VISIBLE);
+ }
+
+ gtk_widget_show(item);
+ return &result->base;
+}
+
+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 x3box_new(parent, vbox);
+}
+
+x3widget *x3hpane(x3widget *parent)
+{
+ GtkWidget *hpane = gtk_hpaned_new();
+
+ gtk_widget_show(hpane);
+ return x3widget_new(parent, NULL, hpane);
+}
+
+x3widget *x3vpane(x3widget *parent)
+{
+ GtkWidget *vpane = gtk_vpaned_new();
+
+ gtk_widget_show(vpane);
+ return x3widget_new(parent, NULL, vpane);
+}
+
+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(GTK_ALIGNMENT(align), t, b, l, r);
+ gtk_widget_show(align);
+ return x3widget_new(parent, NULL, align);
+}
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+ GtkWidget *button = gtk_button_new_with_label(label);
+ x3widget_cmdable *result = (x3widget_cmdable *)malloc(sizeof(x3widget_cmdable));
+
+ x3widget_init(&result->base, parent, cmd, button);
+ result->cmd = g_strdup(cmd);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(x3cmdable_clicked), result);
+
+ gtk_widget_show(button);
+ return &result->base;
+}
+
+x3widget *x3label(x3widget *parent, char *text)
+{
+ GtkWidget *label = gtk_label_new(text);
+
+ gtk_widget_show(label);
+ return x3widget_new(parent, NULL, label);
+}
+
+x3widget *x3edittext(x3widget *parent, char *cmd)
+{
+ GtkWidget *entry = gtk_entry_new();
+
+ gtk_widget_show(entry);
+ return x3widget_new(parent, cmd, entry);
+}
+
+typedef struct {
+ x3widget base;
+ x3viewflags flags;
+ x3viewclient *vc;
+} x3widget_view;
+
+static gboolean x3view_expose(GtkWidget *widget, GdkEventExpose *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+ GdkWindow *window = GTK_IS_LAYOUT(widget) ?
+ GTK_LAYOUT(widget)->bin_window :
+ widget->window;
+
+ if (w->vc && w->vc->draw) {
+ x3dc dc;
+
+ dc.x = event->area.x;
+ dc.y = event->area.y;
+ dc.width = event->area.width;
+ dc.height = event->area.height;
+ if (w->flags & x3view_rgb) {
+ dc.rowstride = (event->area.width * 3 + 3) & -4;
+ dc.buf = (guchar *)malloc(event->area.height * dc.rowstride);
+ dc.cr = NULL;
+
+ w->vc->draw(w->vc, &dc);
+ gdk_draw_rgb_image(window, widget->style->black_gc,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height,
+ GDK_RGB_DITHER_NORMAL,
+ dc.buf, dc.rowstride);
+ free(dc.buf);
+ } else if (w->flags & x3view_2d) {
+ dc.cr = gdk_cairo_create(window);
+ dc.buf = NULL;
+
+ w->vc->draw(w->vc, &dc);
+ cairo_destroy(dc.cr);
+ }
+ }
+
+#if 1
+ /* experimental code for managing cairo dynamics */
+ if (event->count == 0)
+ gdk_flush();
+#endif
+
+ return TRUE;
+}
+
+static gboolean x3view_button_press(GtkWidget *widget, GdkEventButton *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+ guint button = event->button;
+
+ if (event->type == GDK_BUTTON_RELEASE) button = -button;
+
+ if (w->vc && w->vc->mouse) {
+ w->vc->mouse(w->vc, button, event->state, event->x, event->y);
+ return TRUE;
+ }
+ x3sync();
+ return FALSE;
+}
+
+static gboolean x3view_pointer_motion(GtkWidget *widget, GdkEventButton *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+
+ if (w->vc && w->vc->mouse) {
+ w->vc->mouse(w->vc, 0, event->state,
+ event->x, event->y);
+ return TRUE;
+ }
+ x3sync();
+ return FALSE;
+}
+
+static gboolean x3view_key_press(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ x3widget_view *w = (x3widget_view *)data;
+
+ if (w->vc && w->vc->key)
+ return w->vc->key(w->vc, gdk_keyval_name(event->keyval),
+ event->state, event->keyval);
+ x3sync();
+ return FALSE;
+}
+
+x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc)
+{
+ GtkWidget *container;
+ GtkWidget *event_target;
+ GtkWidget *drawing_area;
+ x3widget_view *result = (x3widget_view *)malloc(sizeof(x3widget_view));
+ GdkEventMask eventmask = 0;
+
+ if (flags & x3view_scroll) {
+ container = gtk_scrolled_window_new(NULL, NULL);
+ drawing_area = gtk_layout_new(NULL, NULL);
+ event_target = drawing_area;
+ /* todo: more intelligent size requesting of view */
+ gtk_widget_set_size_request(drawing_area, 1500, 1500);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(container),
+ drawing_area);
+ } else {
+ container = gtk_event_box_new();
+ drawing_area = gtk_drawing_area_new();
+ event_target = container;
+ gtk_container_add(GTK_CONTAINER(container), drawing_area);
+ }
+ gtk_widget_show(container);
+
+ if (flags & x3view_key) {
+ g_object_set(GTK_OBJECT(event_target), "can-focus", TRUE, NULL);
+ eventmask |= GDK_KEY_PRESS_MASK;
+ g_signal_connect(G_OBJECT(event_target), "key_press_event",
+ G_CALLBACK(x3view_key_press), result);
+ }
+ if (flags & x3view_click) {
+ eventmask |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
+ g_signal_connect(G_OBJECT(event_target), "button_press_event",
+ G_CALLBACK(x3view_button_press), result);
+ g_signal_connect(G_OBJECT(event_target), "button_release_event",
+ G_CALLBACK(x3view_button_press), result);
+ }
+ if (flags & x3view_hover) {
+ eventmask |= GDK_POINTER_MOTION_MASK;
+ g_signal_connect(G_OBJECT(event_target), "motion_notify_event",
+ G_CALLBACK(x3view_pointer_motion), result);
+ }
+ gtk_widget_add_events(event_target, eventmask);
+
+ g_signal_connect(G_OBJECT(drawing_area), "expose_event",
+ G_CALLBACK(x3view_expose), result);
+ gtk_widget_show(drawing_area);
+ if (flags & x3view_rgb)
+ gtk_widget_set_double_buffered(drawing_area, FALSE);
+
+ x3widget_init(&result->base, parent, NULL, container);
+ result->flags = flags;
+ result->vc = vc;
+ return &result->base;
+}
+
+void x3view_dirty(x3widget *w)
+{
+ gtk_widget_queue_draw(w->widget);
+}
+
+static void
+x3scrollto_adj(GtkAdjustment *adj, int v, int size)
+{
+ if (adj && v != -1) {
+ if (size >= adj->page_size) {
+ /* target is bigger than adj; center as best as possible */
+ gtk_adjustment_set_value(adj, v - 0.5 * (size - adj->page_size));
+ } else if (adj->value > v) {
+ gtk_adjustment_set_value(adj, v);
+ } else if (adj->value + adj->page_size < v + size) {
+ gtk_adjustment_set_value(adj, v + size - adj->page_size);
+ }
+ }
+}
+
+void x3view_scrollto(x3widget *w, int x, int y, int width, int height)
+{
+ if (GTK_IS_SCROLLED_WINDOW(w->widget)) {
+ GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(w->widget);
+ x3scrollto_adj(gtk_scrolled_window_get_hadjustment(sw), x, width);
+ x3scrollto_adj(gtk_scrolled_window_get_vadjustment(sw), y, height);
+ }
+}
+
+void x3viewclient_init(x3viewclient *vc)
+{
+ vc->destroy = NULL;
+ vc->mouse = NULL;
+ vc->key = NULL;
+ vc->draw = NULL;
+}
+
+/* An argument can be made against the "fill" flag, because the same effect
+ as turning off fill can be achieved with the align widget. */
+void x3setpacking(x3widget *w, int fill, int expand, int padding)
+{
+ if (GTK_IS_BOX(w->widget)) {
+ x3widget_box *wb= (x3widget_box *)w;
+ wb->fill = fill;
+ wb->expand = expand;
+ wb->padding = padding;
+ }
+}
+
+typedef struct {
+ GtkWidget *parent;
+ int resize[2];
+ int shrink[2];
+ int i;
+} x3pane_setsizing_ctx;
+
+static void
+x3pane_setsizing_callback(GtkWidget *child, gpointer data)
+{
+ x3pane_setsizing_ctx *ctx = (x3pane_setsizing_ctx *)data;
+ gtk_container_child_set(GTK_CONTAINER(ctx->parent),
+ child,
+ "resize", ctx->resize[ctx->i],
+ "shrink", ctx->shrink[ctx->i],
+ NULL);
+ ctx->i++;
+}
+
+/* This implementation only works if the sizing is set _after_ the
+ * children are added. It wouldn't be too hard to fix, by putting the
+ * info in the pane's x3widget struct. */
+void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink,
+ int child2_resize, int child2_shrink)
+{
+ x3pane_setsizing_ctx ctx;
+
+ ctx.parent = w->widget;
+ ctx.resize[0] = child1_resize;
+ ctx.shrink[0] = child1_shrink;
+ ctx.resize[1] = child2_resize;
+ ctx.shrink[1] = child2_shrink;
+ ctx.i = 0;
+ if (GTK_IS_PANED(w->widget)) {
+ gtk_container_foreach(GTK_CONTAINER(w->widget),
+ x3pane_setsizing_callback,
+ (gpointer)&ctx);
+ }
+}
+
+
+void x3setactive(x3widget *w, int active)
+{
+ gtk_widget_set_sensitive(w->widget, active != 0);
+}
+
+int x3hasfocus(x3widget *w)
+{
+ GtkWidget *widget = w->widget;
+ while (GTK_IS_CONTAINER(widget) && !GTK_IS_LAYOUT(widget))
+ widget = x3_gtkwidget_getchild(widget);
+ return GTK_WIDGET_HAS_FOCUS(widget);
+}
+
+/* 2d drawing functions, implemented using cairo */
+
+void
+x3moveto(x3dc *dc, double x, double y)
+{
+ cairo_move_to(dc->cr, x, y);
+}
+
+void
+x3lineto(x3dc *dc, double x, double y)
+{
+ cairo_line_to(dc->cr, x, y);
+}
+
+void
+x3curveto(x3dc *dc,
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3)
+{
+ cairo_curve_to(dc->cr, x1, y1, x2, y2, x3, y3);
+}
+
+void
+x3closepath(x3dc *dc)
+{
+ cairo_close_path(dc->cr);
+}
+
+void
+x3rectangle(x3dc *dc, double x, double y, double width, double height)
+{
+ cairo_rectangle(dc->cr, x, y, width, height);
+}
+
+void
+x3getcurrentpoint(x3dc *dc, double *px, double *py)
+{
+ cairo_get_current_point(dc->cr, px, py);
+}
+
+void
+x3setrgba(x3dc *dc, unsigned int rgba)
+{
+ cairo_set_source_rgba(dc->cr,
+ ((rgba >> 24) & 0xff) * (1.0/255),
+ ((rgba >> 16) & 0xff) * (1.0/255),
+ ((rgba >> 8) & 0xff) * (1.0/255),
+ (rgba & 0xff) * (1.0/255));
+}
+
+void
+x3setlinewidth(x3dc *dc, double w)
+{
+ cairo_set_line_width(dc->cr, w);
+}
+
+void
+x3fill(x3dc *dc)
+{
+ cairo_fill(dc->cr);
+}
+
+void
+x3stroke(x3dc *dc)
+{
+ cairo_stroke(dc->cr);
+}
+
+void
+x3selectfont(x3dc *dc, char *fontname, int slant, int weight)
+{
+ cairo_select_font_face(dc->cr, fontname, slant, weight);
+}
+
+void
+x3setfontsize(x3dc *dc, double size)
+{
+ cairo_set_font_size(dc->cr, size);
+}
+
+void
+x3showtext(x3dc *dc, char *text)
+{
+ cairo_show_text(dc->cr, text);
+}
+
+void
+x3textextents(x3dc *dc, char *text, x3extents *extents)
+{
+ cairo_text_extents(dc->cr, text, extents);
+}
diff --git a/third_party/spiro/x3/x3win32.c b/third_party/spiro/x3/x3win32.c
new file mode 100644
index 0000000..45e7d3c
--- /dev/null
+++ b/third_party/spiro/x3/x3win32.c
@@ -0,0 +1,174 @@
+#include "x3.h"
+#include "x3common.h"
+#include <stdio.h> /* for printf only, probably remove in production */
+
+HINSTANCE theInstance = NULL;
+
+void x3init_win32(HINSTANCE hInstance)
+{
+ theInstance = hInstance;
+}
+
+void x3widget_init(x3widget *w, const x3type *type)
+{
+ w->type = type;
+ w->name = NULL;
+ w->parent = NULL;
+ w->var = x3winnone;
+ w->u.hwnd = NULL;
+ w->n_children = 0;
+ w->children = NULL;
+}
+
+LRESULT CALLBACK x3WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (iMsg) {
+ case WM_DESTROY:
+ if (--x3n_winopen <= 0) {
+ PostQuitMessage(0);
+ return 0;
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, iMsg, wParam, lParam);
+}
+
+void x3window_sizereq(x3widget *w)
+{
+}
+
+void x3window_sizealloc(x3widget *w, x3rect *r)
+{
+ int i;
+ RECT rect;
+ x3rect child_r;
+
+ GetClientRect(w->u.hwnd, &rect);
+ child_r.x0 = 0;
+ child_r.x1 = rect.right - rect.left;
+ child_r.y0 = 0;
+ child_r.y1 = rect.bottom - rect.top;
+ printf("x3window_sizealloc (%ld, %ld) - (%ld, %ld)\n",
+ rect.left, rect.top, rect.right, rect.bottom);
+ for (i = 0; i < w->n_children; i++) {
+ x3widget *child = w->children[i];
+ if (child->type->sizealloc)
+ child->type->sizealloc(child, &child_r);
+ child->flags &= ~x3flag_needsizealloc;
+ }
+}
+
+x3type x3windowtype = { x3window_sizereq,
+ x3window_sizealloc,
+ x3add_default };
+
+x3widget *x3window(x3windowflags flags, char *label,
+ x3window_callback callback, void *data)
+{
+ HWND hwnd;
+ DWORD style = WS_OVERLAPPEDWINDOW;
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ WNDCLASSEX wndclass;
+
+ wndclass.cbSize = sizeof(wndclass);
+ wndclass.style = CS_HREDRAW | CS_VREDRAW;
+ wndclass.lpfnWndProc = x3WndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = theInstance;
+ wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = "x3win";
+ wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+
+ RegisterClassEx(&wndclass);
+
+ hwnd = CreateWindowEx(0, "x3win", "My window",
+ style, 100, 100, 300, 300,
+ NULL, NULL,
+ theInstance, NULL);
+ x3widget_init(result, &x3windowtype);
+ result->var = x3winhwnd;
+ result->u.hwnd = hwnd;
+ x3_nwinopen++;
+ //ShowWindow(hwnd, SW_SHOWNORMAL);
+ return result;
+
+}
+
+void x3_window_show(x3widget *w)
+{
+ ShowWindow(w->u.hwnd, SW_SHOW);
+}
+
+static HWND x3hwnd_of(x3widget *w)
+{
+ while (w->parent) w = w->parent;
+ return w->var == x3winhwnd ? w->u.hwnd : NULL;
+}
+
+static x3widget *x3widget_new_hwnd(x3widget *parent, char *name,
+ const x3type *type,
+ HWND hwnd)
+{
+ x3widget *result = (x3widget *)malloc(sizeof(x3widget));
+ x3widget_init(result, type);
+ result->name = name ? strdup(name) : NULL;
+ result->var = x3winhwnd;
+ result->u.hwnd = hwnd;
+ x3add(parent, result);
+ x3qsizereq(result);
+ return result;
+}
+
+void x3button_sizereq(x3widget *w)
+{
+ w->sizerequest.x0 = 0;
+ w->sizerequest.y0 = 0;
+ w->sizerequest.x1 = 100;
+ w->sizerequest.y1 = 20;
+#ifdef VERBOSE
+ printf("button sizereq = (%d, %d) - (%d, %d)\n",
+ w->sizerequest.x0, w->sizerequest.y0,
+ w->sizerequest.x1, w->sizerequest.y1);
+#endif
+}
+
+void x3button_sizealloc(x3widget *w, x3rect *r)
+{
+ printf("button sizealloc = (%g, %g) - (%g, %g)\n",
+ r->x0, r->y0, r->x1, r->y1);
+ if (w->var == x3winhwnd) {
+ SetWindowPos(w->u.hwnd, HWND_TOP,
+ r->x0, r->y0, r->x1 - r->x0, r->y1 - r->y0,
+ SWP_NOZORDER);
+ }
+}
+
+x3type x3buttontype = { x3button_sizereq,
+ x3button_sizealloc,
+ x3add_default };
+
+x3widget *x3button(x3widget *parent, char *cmd, char *label)
+{
+ HWND hwnd;
+
+ hwnd = CreateWindow("button", label,
+ WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+ 10, 10, 100, 20, x3hwnd_of(parent), NULL,
+ theInstance, NULL);
+ return x3widget_new_hwnd(parent, cmd, &x3buttontype, hwnd);
+}
+
+void x3main(void)
+{
+ MSG msg;
+
+ x3sync();
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}