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;

Flags for pglOpenWindow()

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);
}