Copyright © 2008, 2009, 2010 Marc Balmer. All rights reserved.
#include <stdio.h>
#include <Xm/PushB.h>
void button_pushed(Widget, XtPointer, XtPointer);
int
main(int argc, char *argv[])
{
Widget toplevel, button;
XtAppContext app;
XmString label;
XtSetLanguageProc(NULL, NULL, NULL);
toplevel = XtVaAppInitialize(&app, "Hello", NULL, 0, &argc, argv,
NULL, NULL);
label = XmStringCreateLocalized("Push here to say hello");
button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass,
toplevel,XmNlabelString, label, NULL);
XmStringFree(label);
XtAddCallback(button, XmNactivateCallback, button_pushed, NULL);
XtRealizeWidget(toplevel);
XtAppMainLoop(app);
}
void
button_pushed(Widget widget, XtPointer client_data, XtPointer call_data)
{
printf("Hello yourself!\n");
}
When a mere 1:1 mapping of the C functions to Lua is provided, the resulting Lua program will look like this:
require "motif"
function button_pushed(button)
print("Hello yourself!")
end
XtSetLanguageProc(nil, nil, nil)
toplevel, app = XtAppInitialize("XLua")
label = XmStringCreateLocalized("Push here to say hello")
button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, toplevel,
XmNlabelString, label)
XmStringFree(label)
XtAddCallback(button, XmNactivateCallback, button_pushed)
XtRealizeWidget(toplevel)
XtAppMainLoop(app)
This code still looks like the C program and it has no real advantage over the original version. The only difference is that Lua is being used instead of C. So the real goal is to use Lua's table constructor syntax to declare the hierarchy of user interface elements. The small hello word example should rather look like the following example:
require "motif"
button = PushButton {
labelString = "Push here to say hello",
activateCallback = function ()
print("Hello yourself!")
end
}
SetLanguageProc(nil, nil, nil)
toplevel, app = Initialize("XLua")
Realize(toplevel, button)
MainLoop(app)
Lua's table constructor syntax is used to define a (very simple) widget hierarchy, whith just one push button that has two arguments, a label string and a callback function that is called when the button is pressed. One thing to note is that Lua will call a global function "PushButton" when creating the "button" table. This function call can be used to actually create the widget. A first approach could be to write "constructor" functions for all widgets that create the widget when called; thus providing a direct mapping from the table construction process to the widget creation process. But this will not work.
Consider a true hierarchy of elements like in the following, longer, example:
require "motif"
gui = Form {
fractionBase = 3,
Frame {
topAttachment = ATTACH_FORM,
rightAttachment = ATTACH_FORM,
bottomAttachment = ATTACH_POSITION,
leftAttachment = ATTACH_FORM,
bottomPosition = 1,
LabelGadget {
labelString = "Motif Buttons in Lua",
childType = FRAME_TITLE_CHILD
},
RowColumn {
PushButton {
labelString = "Button one",
activateCallback = function()
print("Button one was pressed")
end
},
PushButton {
labelString = "Button two",
activateCallback = function()
print("Button two was pressed")
end
},
}
},
TabStack {
topAttachment = ATTACH_POSITION,
rightAttachment = ATTACH_FORM,
bottomAttachment = ATTACH_FORM,
leftAttachment = ATTACH_FORM,
topPosition = 2,
tabSide = TABS_ON_BOTTOM,
tabStyle = TABS_ROUNDED,
tabCornerPercent = 100,
tabMarginHeight = 64,
tabMarginWidth = 64,
products = LabelGadget {
labelString = "frame title",
},
groups = Frame {
LabelGadget {
labelString = "frame title",
childType = FRAME_TITLE_CHILD,
},
rc = RowColumn {
PushButton {
labelString = "pushme",
activateCallback = function (w)
print(tf:GetString())
tfield = w:Parent().t
print(tfield:GetString())
end
},
t = TextField {
activateCallback = function (w)
print(w:GetString())
end,
valueChangedCallback = function (w)
print(w:GetString())
end,
focusCallback = function (w)
print("Button down " ..
w:GetString())
end
}
}
},
}
}
tf = gui[3].groups.rc.t
SetLanguageProc(nil, nil, nil)
toplevel, app = Initialize("XPoS")
Realize(toplevel, gui)
MainLoop(app)
The problem is that while the widget hierarchy must be created with the top element first (the Form) and then adding the child elements in descending order, Lua constructs the table from the inside out, starting with the leaf nodes. In the example above, Lua would first create the PushButton, then add it to the RowColumn, then add the RowColumn to the Frame etc. Lua creates leaf widgets when the parent widget has not yet been created.
A working approach is to just "take note" in the constructor functions which widget has to be created, but to delay the actual widget creation until the whole table tree has been constructed by Lua. Once this is done, a function that actually creates the widgets, starting with the top element, can be called.
The function "Realize(toplevel, gui)" does exactly that.
So what do constructor functions look like? They are called by Lua with the corresponding table object on the top of the stack, so a field, __widgetClass, can be set to hold the widgetClass as lightuserdata which will later be used to create the widget.
static int
PushButton(lua_State *L)
{
lua_pushlightuserdata(L, xmPushButtonWidgetClass);
lua_setfield(L, -2, "__widgetClass");
return 1;
}
The "Realize()" function will create the widget hierarchy in the following steps:
static Widget
Realize(lua_State *L, int parentObj, Widget parent, const char *name)
{
Arg args[MAXARGS];
WidgetClass class;
Widget widget;
XmString s;
struct cb_data *cbd;
char nam[64], *nm;
int n, narg, t;
const char *cb;
const char *func;
widget = NULL;
narg = 0;
lua_pushstring(L, "__widgetClass");
lua_rawget(L, -2);
class = (WidgetClass)lua_topointer(L, -1);
lua_pop(L, 1);
if (class != NULL) {
t = lua_gettop(L);
lua_pushnil(L);
while (lua_next(L, t) != 0) {
switch (lua_type(L, -2)) {
case LUA_TSTRING:
strlcpy(nam, lua_tostring(L, -2), sizeof nam);
break;
case LUA_TNUMBER:
snprintf(nam, sizeof n, "%.f", lua_tonumber(L, -2));
break;
default:
strlcpy(nam, name, sizeof nam);
}
switch (lua_type(L, -1)) {
case LUA_TSTRING:
XtSetArg(args[narg], nm, lua_tostring(L, -1));
narg++;
break;
case LUA_TNUMBER:
nm = strdup(nam);
XtSetArg(args[narg], nm, (XtArgVal)
(int)lua_tonumber(L, -1));
narg++;
break;
case LUA_TBOOLEAN:
nm = strdup(nam);
XtSetArg(args[narg], nm, (XtArgVal)
(int)lua_toboolean(L, -1));
narg++;
break;
}
lua_pop(L, 1);
}
}
if (class == xmDialogShellWidgetClass)
widget = XtCreatePopupShell(name, class, parent, args, narg);
else if (class != NULL)
widget = XtCreateManagedWidget(name, class, parent, args, narg);
if (widget != NULL) {
lua_pushlightuserdata(L, widget);
lua_setfield(L, -2, "__widget");
luaL_getmetatable(L, WIDGET_METATABLE);
lua_setmetatable(L, -2);
}
t = lua_gettop(L);
lua_pushnil(L);
while (lua_next(L, t) != 0) {
switch (lua_type(L, -2)) {
case LUA_TSTRING:
strlcpy(nam, lua_tostring(L, -2), sizeof nam);
break;
case LUA_TNUMBER:
snprintf(nam, sizeof n, "%.f", lua_tonumber(L, -2));
break;
default:
strlcpy(nam, name, sizeof nam);
}
switch (lua_type(L, -1)) {
case LUA_TTABLE:
if (widget == NULL)
CreateWidgetHierarchy(L, t, parent, nam);
else
CreateWidgetHierarchy(L, t, widget, nam);
break;
case LUA_TFUNCTION:
if (widget == NULL)
break;
cbd = malloc(sizeof(struct cb_data));
if (cbd == NULL)
luaL_error(L, "memory error");
cbd->L = L;
lua_pushvalue(L, -1);
cbd->ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pushvalue(L, t);
cbd->obj = luaL_ref(L, LUA_REGISTRYINDEX);
XtAddCallback(widget, nam, lm_Callback, cbd);
XtAddCallback(widget, XmNdestroyCallback,
lm_DestroyCallback, cbd);
break;
}
lua_pop(L, 1);
}
for (n = 0; n < narg; n++)
free(args[n].name);
if (parentObj > 0) {
lua_pushvalue(L, parentObj);
lua_setfield(L, -2, "__parent");
}
return widget;
}
Comments
Full source code on github.