A Simple PGL Example
#include "pgl.h"
String config[] = { "*geometry: =400x400", NULL };
static void draw_me(int winid, pglCallbackInfo info)
{
glClearIndex(0);
glClear(GL_COLOR_BUFFER_BIT);
}
main(int argc, char** argv)
{
int window_id;
pglInitialize("Sample", config);
window_id = pglOpenWindow(argv[0], draw_me, PGL_INDEX);
pglSetCallback(window_id, PGL_EXPOSE, draw_me);
pglManageWindows();
pglMainLoop();
}
Possible Reasons for Performing a Callback Routine
PGL_EXPOSE Window Exposure (Redraw)
PGL_RESIZE Window Resize
PGL_INPUT Input Event Received (mouse, keyboard, etc)
PGL_TIMEOUT Timer Alarm
PGL_IDLE Idling (nothing else to do)
Callback Routines
typedef void pglCallback(int winid, pglCallbackInfo info);
typedef struct _pglCallbackInfo {
int reason;
XEvent* event;
Dimension width;
Dimension height;
} *pglCallbackInfo;
PGL_DOUBLE
PGL_SINGLE
PGL_RGBA
PGL_INDEX
PGL.C File
The PGL context structure - where I remember everything about a window
typedef struct {
int doneInit;
GLXContext context;
Widget widget;
Widget parent;
Widget shell;
Widget menu;
pglCallback* initCB;
pglCallback* exposeCB;
pglCallback* resizeCB;
pglCallback* inputCB;
pglCallback* idleCB;
pglCallback* timerCB;
unsigned long timeo;
XtIntervalId timid;
XtWorkProcId wpid;
} pglContext;
Some default resources, in case the user doesn't specify any
static String pglResources[] = {
"*pglWindow.geometry: 200x200",
"*pglMenuShell*background: Gray80",
NULL
};
The PGL Routines
The Main Routines
Widget
pglInitialize(char* appClassName, char** userResources)
{
Arg xargs[10];
int xn = 0;
int n = 1;
/* if we've already done this once, just return the widget */
if ( xwidget ) return xwidget;
[...combine pglResources with userResources...]
/* Do X11 initialization */
XtSetArg(xargs[xn], XmNmappedWhenManaged, False); xn++;
xwidget = XtAppInitialize(&xcontext, appClassName,
NULL, 0, &n, &appClassName, userResources, xargs, xn);
return xwidget;
}
void
pglHandleEvents(void)
{
XtInputMask m;
while ( (m=XtAppPending(xcontext)) != 0 ) {
XtAppProcessEvent(xcontext, m);
}
}
/*
(from the XtAppPending manual page...)
The XtAppPending function returns a nonzero value if there
are events pending from the X server, timer pending, or
other input sources pending.
(from the XtAppProcessEvent manual page...)
The XtAppProcessEvent function processes one timer,
alternate input, signal source, or X event. If there is
nothing of the appropriate type to process,
XtAppProcessEvent blocks until there is. If there is more
than one type of thing available to process, it is undefined
which will get processed. Usually, this procedure is not
called by client applications (see XtAppMainLoop).
XtAppProcessEvent processes timer events by calling any
appropriate timer callbacks, alternate input by calling any
appropriate alternate input callbacks, signal source by
calling any appropriate signal callbacks, and X events by
calling XtDispatchEvent.
*/
void
pglMainLoop(void)
{
XtAppMainLoop(xcontext);
}
/*
(from the XtAppMainLoop manual page...)
The XtAppMainLoop function first reads the next incoming X
event by calling XtAppNextEvent and then it dispatches the
event to the appropriate registered procedure by calling
XtDispatchEvent. This constitutes the main loop of X
Toolkit applications, and, as such, it does not return.
Applications are expected to exit in response to some user
action. There is nothing special about XtAppMainLoop; it is
simply an infinite loop that calls XtAppNextEvent and then
XtDispatchEvent.
*/
int
pglOpenWindow(char* title, pglCallback initRoutine, int flags)
{
Atom DELETE_ATOM;
Widget shell;
Arg xargs[10];
int xn = 0;
XtSetArg(xargs[xn], XmNtitle, title); xn++;
shell = XtCreatePopupShell("pglWindow",
topLevelShellWidgetClass, xwidget, xargs, xn);
DELETE_ATOM = XmInternAtom(XtDisplay(shell), "WM_DELETE_WINDOW", False);
XmAddWMProtocolCallback(shell, DELETE_ATOM, QCB, NULL);
xn = 0;
XtSetArg(xargs[xn], GLwNdoublebuffer, !!(flags&PGL_DOUBLE)); xn++;
XtSetArg(xargs[xn], GLwNrgba, !!(flags&PGL_RGBA)); xn++;
/* ASSUMES 12-bit Z-BUFFER!! */
XtSetArg(xargs[xn], GLwNdepthSize, 12); xn++;
return(pglDoOpen(shell, "pglCanvas", initRoutine, xargs, xn));
}
int
pglOpenSubwindow(Widget parent, char* name, pglCallback initRoutine, int flags)
{
Arg xargs[10];
int xn = 0;
XtSetArg(xargs[xn], GLwNdoublebuffer, !!(flags&PGL_DOUBLE)); xn++;
XtSetArg(xargs[xn], GLwNrgba, !!(flags&PGL_RGBA)); xn++;
/* ASSUMES 12-bit Z-BUFFER!! */
XtSetArg(xargs[xn], GLwNdepthSize, 12); xn++;
ctxt[gwinid].shell = NULL;
return(pglDoOpen(parent, name, initRoutine, xargs, xn));
}
void
pglManageWindows(void)
{
int j;
for ( j = 0; j < gwinid; j++ )
if ( ctxt[j].widget ) {
XtManageChild(ctxt[j].widget);
XtRealizeWidget(ctxt[j].parent);
if ( ctxt[j].shell )
XtPopup(ctxt[j].shell, XtGrabNone);
}
}
void
pglManageWindow(int winid)
{
XtManageChild(ctxt[winid].widget);
}
void
pglUnmanageWindow(int winid)
{
XtUnmanageChild(ctxt[winid].widget);
}
void
pglSwapBuffers(int winid)
{
GLwDrawingAreaSwapBuffers(ctxt[winid].widget);
}
The "get" Routines for Getting Info From PGL
XtAppContext pglGetAppContext(void) { return xcontext; }
Widget pglGetAppWidget(void) { return xwidget; }
GLXContext pglGetGLContext(int winid) { return ctxt[winid].context; }
Widget pglGetGLWidget(int winid) { return ctxt[winid].widget; }
Widget pglGetShell(int winid) { return ctxt[winid].parent; }
The "set" Routines for Setting Various States
void
pglSetCurrentWindow(int winid)
{
GLwDrawingAreaMakeCurrent(ctxt[winid].widget,
ctxt[winid].context);
}
void
pglSetCallback(int winid, int reason, pglCallback func)
{
switch ( reason ) {
case PGL_GINIT:
ctxt[winid].initCB = func;
break;
case PGL_EXPOSE:
ctxt[winid].exposeCB = func;
break;
case PGL_RESIZE:
ctxt[winid].resizeCB = func;
break;
case PGL_INPUT:
ctxt[winid].inputCB = func;
break;
case PGL_TIMEOUT:
ctxt[winid].timerCB = func;
if ( ctxt[winid].doneInit )
if ( func && !ctxt[winid].timid ) {
ctxt[winid].timid = XtAppAddTimeOut(xcontext,
ctxt[winid].timeo,
(XtTimerCallbackProc)timerCB,
(XtPointer)winid);
} else if ( !func && ctxt[winid].timid ) {
XtRemoveTimeOut(ctxt[winid].timid);
ctxt[winid].timid = 0;
}
break;
case PGL_IDLE:
ctxt[winid].idleCB = func;
if ( ctxt[winid].doneInit )
if ( func && !ctxt[winid].wpid ) {
ctxt[winid].wpid = XtAppAddWorkProc(xcontext,
idleCB,
(XtPointer)winid);
} else if ( !func && ctxt[winid].wpid ) {
XtRemoveWorkProc(ctxt[winid].wpid);
ctxt[winid].wpid = 0;
}
break;
}
}
void
pglSetColors(int winid, int num, float*rgb)
{
Arg xargs;
Colormap map;
XColor* c;
int j;
XtSetArg(xargs, XmNcolormap, &map);
XtGetValues(ctxt[winid].widget, &xargs, 1);
c = (XColor*)malloc(num*sizeof(XColor));
for ( j = 0; j < num; j++ ) {
c[j].pixel = j;
c[j].red = (unsigned short)(rgb[3*j] * 65535.0 + 0.5);
c[j].green = (unsigned short)(rgb[3*j+1] * 65535.0 + 0.5);
c[j].blue = (unsigned short)(rgb[3*j+2] * 65535.0 + 0.5);
c[j].flags = DoRed | DoGreen | DoBlue;
}
XStoreColors(XtDisplay(xwidget), map, c, num);
}
void
pglSetTimeout(int winid, float seconds)
{
/* translate seconds into (rounded) milliseconds, and save it */
ctxt[winid].timeo = (int)(seconds*1000.0 + 0.5);
}
The Internal Routines
static int
pglDoOpen(Widget parent, char* name, pglCallback initRoutine,
ArgList xargs, int xn)
{
Widget glw;
if ( gwinid >= nslots ) {
nslots *= 2;
ctxt = (pglContext*) realloc(ctxt, nslots*sizeof(pglContext));
}
ctxt[gwinid].menu = NULL;
ctxt[gwinid].context = NULL;
ctxt[gwinid].exposeCB = NULL;
ctxt[gwinid].resizeCB = stdResize;
ctxt[gwinid].inputCB = NULL;
ctxt[gwinid].idleCB = NULL;
ctxt[gwinid].timerCB = NULL;
ctxt[gwinid].timeo = 33;
ctxt[gwinid].wpid = 0;
ctxt[gwinid].timid = 0;
ctxt[gwinid].parent = parent;
ctxt[gwinid].initCB = initRoutine;
XtAddCallback(parent, XmNdestroyCallback, destroyCB, (XtPointer)gwinid);
/* Creates a "Motif Drawing Area" GL Widget
* - see the GLwMDrawingArea manual page*/
ctxt[gwinid].widget = glw = GLwCreateMDrawingArea(parent, name, xargs, xn);
XtAddCallback(glw, GLwNginitCallback, initCB, (XtPointer)gwinid);
XtAddCallback(glw, GLwNexposeCallback, exposeCB, (XtPointer)gwinid);
XtAddCallback(glw, GLwNresizeCallback, resizeCB, (XtPointer)gwinid);
XtAddCallback(glw, GLwNinputCallback, inputCB, (XtPointer)gwinid);
return(gwinid++);
}
The Callbacks
static void
QCB(Widget w, XtPointer client_data, XtPointer call_data)
{
exit(0);
}
static void
initCB(Widget w, XtPointer client_data, XtPointer call_data)
{
Arg xargs[1];
XVisualInfo *vi;
int winid = (int)client_data;
XtSetArg(xargs[0], GLwNvisualInfo, &vi);
XtGetValues(w, xargs, 1);
ctxt[winid].context = glXCreateContext(XtDisplay(w),
vi, 0, GL_TRUE);
ctxt[winid].doneInit = 1;
GLwDrawingAreaMakeCurrent(w, ctxt[winid].context);
if ( ctxt[winid].initCB )
ctxt[winid].initCB(winid, (pglCallbackInfo)call_data);
if ( ctxt[winid].wpid )
XtRemoveWorkProc(ctxt[winid].wpid);
if ( ctxt[winid].idleCB )
ctxt[winid].wpid = XtAppAddWorkProc(xcontext, timerCB, (XtPointer)winid);
}
static void
exposeCB(Widget w, XtPointer client_data, XtPointer call_data)
{
int winid = (int)client_data;
GLwDrawingAreaMakeCurrent(w, ctxt[winid].context);
if ( ctxt[winid].exposeCB )
ctxt[winid].exposeCB(winid, (pglCallbackInfo)call_data);
}
static void
resizeCB(Widget w, XtPointer client_data, XtPointer call_data)
{
int winid = (int)client_data;
GLwDrawingAreaMakeCurrent(w, ctxt[winid].context);
if ( ctxt[winid].resizeCB )
ctxt[winid].resizeCB(winid, (pglCallbackInfo)call_data);
}
static void
inputCB(Widget w, XtPointer client_data, XtPointer call_data)
{
int winid = (int)client_data;
if ( ctxt[winid].inputCB ) {
GLwDrawingAreaMakeCurrent(w, ctxt[winid].context);
ctxt[winid].inputCB(winid, (pglCallbackInfo)call_data);
}
}
static void
destroyCB(Widget w, XtPointer client_data, XtPointer call_data)
{
int winid = (int)client_data;
if ( ctxt[winid].wpid ) XtRemoveWorkProc(ctxt[winid].wpid);
if ( ctxt[winid].timid ) XtRemoveTimeOut(ctxt[winid].timid);
if ( ctxt[winid].menu ) XtDestroyWidget(ctxt[winid].menu);
if ( ctxt[winid].shell ) XtDestroyWidget(ctxt[winid].shell);
ctxt[winid].menu = NULL;
ctxt[winid].shell = NULL;
ctxt[winid].widget = NULL;
ctxt[winid].context = NULL;
ctxt[winid].initCB = NULL;
ctxt[winid].exposeCB = NULL;
ctxt[winid].resizeCB = NULL;
ctxt[winid].inputCB = NULL;
}
static Boolean
idleCB(XtPointer client_data)
{
int winid = (int)client_data;
if ( ctxt[winid].idleCB ) {
GLwDrawingAreaMakeCurrent(ctxt[winid].widget, ctxt[winid].context);
ctxt[winid].idleCB(winid, &idleinfo);
}
return False;
}
static void
timerCB(XtPointer client_data, XtIntervalId* tid)
{
int winid = (int)client_data;
if ( ctxt[winid].timerCB ) {
/* re-queue for next period! */
ctxt[winid].timid = XtAppAddTimeOut(xcontext,
ctxt[winid].timeo, timerCB, (XtPointer)winid);
GLwDrawingAreaMakeCurrent(ctxt[winid].widget, ctxt[winid].context);
ctxt[winid].timerCB(winid, &timerinfo);
}
}
static void
stdResize(int winid, pglCallbackInfo info)
{
glViewport(0, 0, info->width, info->height);
if ( ctxt[winid].exposeCB )
ctxt[winid].exposeCB(winid, info);
}
static void
pglMenuPost(Widget w, XtPointer client, XEvent *event, Boolean *dispatch)
{
Arg xargs[2];
int xn;
int button;
Widget me = (Widget)client;
XButtonEvent bev = event->xbutton;
xn = 0;
XtSetArg(xargs[xn], XmNwhichButton, &button);
xn++;
XtGetValues(me, xargs, xn);
if( bev.button != button) return;
XmMenuPosition(me, &bev);
XtManageChild(me);
}
The String-Printing Routines
GLuint
pglMakeFont(char* fontname) {
unsigned int first;
unsigned int last;
Font id;
GLuint base;
XFontStruct* fontInfo;
fontInfo = XLoadQueryFont(XtDisplay(xwidget), fontname);
if (fontInfo == NULL) {
fprintf(stderr, "no font found\n");
exit(0);
}
id = fontInfo->fid;
first = fontInfo->min_char_or_byte2;
last = fontInfo->max_char_or_byte2;
base = glGenLists(last+1);
if (base == 0) {
fprintf(stderr, "out of display lists\n");
exit(0);
}
glXUseXFont(id, first, last-first+1, base+first);
return base;
}
void
pglDrawString(char *s, GLuint fontid) {
glPushAttrib(GL_LIST_BIT);
glListBase(fontid);
glCallLists(strlen(s), GL_UNSIGNED_BYTE, (unsigned char *)s);
glPopAttrib();
}
The Menu Creation Routines
void
pglMakeMenu(int winid, char* title)
{
Arg xargs[20];
int xn = 0;
Widget popupMenu;
Widget menuShell;
XmString xmstr = XmStringCreateLtoR(title, XmSTRING_DEFAULT_CHARSET);
/* make top level menu shell widget */
XtInitializeWidgetClass(xmMenuShellWidgetClass);
XtSetArg(xargs[xn], XmNwidth, 1); xn++;
XtSetArg(xargs[xn], XmNheight, 1); xn++;
menuShell = XtCreatePopupShell("pglMenuShell", xmMenuShellWidgetClass,
ctxt[winid].parent, xargs, xn);
/* ...put a popup widget inside it */
xn = 0;
XtSetArg(xargs[xn], XmNrowColumnType, XmMENU_POPUP); xn++;
XtSetArg(xargs[xn], XmNx, 0); xn++;
XtSetArg(xargs[xn], XmNy, 0); xn++;
XtSetArg(xargs[xn], XmNwidth, 195); xn++;
XtSetArg(xargs[xn], XmNheight, 79); xn++;
popupMenu = XtCreateWidget("popupMenu", xmRowColumnWidgetClass,
menuShell, xargs, xn);
/* ...give it a title */
XtVaCreateManagedWidget("menuTitle", xmLabelWidgetClass,
popupMenu, XmNlabelString, xmstr, NULL);
XmStringFree(xmstr);
/* register the event handler to pop it up on RIGHTMOUSE */
XtAddEventHandler(XtParent(XtParent(popupMenu)),
ButtonPressMask, False, pglMenuPost, (XtPointer)popupMenu);
/* save a pointer to this menu */
if ( ctxt[winid].menu )
XtDestroyWidget(ctxt[winid].menu);
ctxt[winid].menu = popupMenu;
}
void
pglAddMenuItem(int winid, char* item, int value, void (*cb)(Widget, XtPointer, XtPointer))
{
Widget button;
XmString xmstr = XmStringCreateLtoR(item, XmSTRING_DEFAULT_CHARSET);
if ( ctxt[winid].menu ) {
/* add a pushbutton to the appropriate menu */
button = XtVaCreateManagedWidget("menuButton", xmPushButtonWidgetClass,
ctxt[winid].menu, XmNlabelString, xmstr, NULL);
if ( cb ) XtAddCallback(button, XmNactivateCallback, cb, (XtPointer)value);
}
XmStringFree(xmstr);
}