/* $Revision: 1.5 $ */ /* compile: cc -o textfun textfun.c -lXm -lXt -lGL -lXext -lX11 -lm */ /* * textfun demonstrates pulling X bitmap fonts from the X server into an * OpenGL client and converting the bitmaps into OpenGL display lists with * transformable geometry. Text can then be displayed from any perspective * in 3D. * * Motif is used for the user interface. The program renders OpenGL into a * standard Motif drawing area and does not use any special OpenGL widget. * Pull down menus with toggles and radio buttons are used. The animation is * controled by X Toolkit work procs; iconfiying textfun will stop the work * proc and resume it when the program is uniconified. * * Various fonts can be switched between. A number of the fonts are X scalable * fonts demonstrating how the blocky nature of the text can be minimized * with higher resolution fonts. * * Mark J. Kilgard * mjk@sgi.com * Silicon Graphics, Inc. * March 7, 1994 */ #include #include #include #include #ifdef IRIX_5_1_MOTIF_BUG_WORKAROUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define GL_ERROR_CHECK() \ { /* for help debugging, report any OpenGL errors that occur per frame */ \ GLenum error; \ while((error = glGetError()) != GL_NO_ERROR) \ fprintf(stderr, "GL error: %s, line %d\n", gluErrorString(error), __LINE__); \ } #else #define GL_ERROR_CHECK() { /* nothing */ } #endif typedef struct { short width; short height; short xoffset; short yoffset; short advance; char *bitmap; } PerCharInfo, *PerCharInfoPtr; typedef struct { int min_char; int max_char; int max_ascent; int max_descent; GLuint dlist_base; PerCharInfo glyph[1]; } FontInfo, *FontInfoPtr; typedef struct { char *name; char *xlfd; XFontStruct *xfont; FontInfoPtr fontinfo; } FontEntry, *FontEntryPtr; static int dblBuf[] = { GLX_DOUBLEBUFFER, GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, None }; static int *snglBuf = &dblBuf[1]; static String fallbackResources[] = { "*sgiMode: true", /* try to enable IRIX 5.2+ look & feel */ "*useSchemes: all", /* and SGI schemes */ "*title: OpenGL text transformation", "*glxarea*width: 300", "*glxarea*height: 300", NULL }; static FontEntry fontEntry[] = { {"Fixed", "fixed", NULL, NULL}, {"Utopia", "-adobe-utopia-medium-r-normal--20-*-*-*-p-*-iso8859-1", NULL, NULL}, {"Schoolbook", "-adobe-new century schoolbook-bold-i-normal--20-*-*-*-p-*-iso8859-1", NULL, NULL}, {"Rock", "-sgi-rock-medium-r-normal--20-*-*-*-p-*-iso8859-1", NULL, NULL}, {"Rock (hi-res)", "-sgi-rock-medium-r-normal--50-*-*-*-p-*-iso8859-1", NULL, NULL}, {"Curl", "-sgi-curl-medium-r-normal--20-*-*-*-p-*-*-*", NULL, NULL}, {"Curl (hi-res)", "-sgi-curl-medium-r-normal--50-*-*-*-p-*-*-*", NULL, NULL}, {"Dingbats", "-adobe-itc zapf dingbats-medium-r-normal--35-*-*-*-p-*-adobe-fontspecific", NULL, NULL} }; #define NUM_FONT_ENTRIES sizeof(fontEntry)/sizeof(FontEntry) static char *defaultMessage[] = {"OpenGL ", "rocks into", "the Future!"}; #define NUM_DEFAULT_MESSAGES sizeof(defaultMessage)/sizeof(char*) Display *dpy; GLboolean doubleBuffer = GL_TRUE, motion = GL_FALSE, rotation = GL_FALSE, wobbling = GL_FALSE, made_current = GL_FALSE, dollying = GL_TRUE; XtAppContext app; XtWorkProcId workId = 0; Widget toplevel = NULL; Widget mainw, menubar, menupane, btn, cascade, frame, glxarea; GLXContext cx; XVisualInfo *vi; Colormap cmap; Arg menuPaneArgs[4], args[1]; GLfloat theta = 0, delta = 0; GLfloat distance = 19, angle = 0, wobble_angle = 0; GLuint base; int numMessages; char **messages; void draw(Widget w) { GLfloat red, green, blue; int i; glClear(GL_DEPTH_BUFFER_BIT); /* paint black to blue smooth shaded polygon for background */ glDisable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glBegin(GL_POLYGON); glColor3f(1.0, 1.0, 1.0); glVertex3f(-20, 20, -19); glVertex3f(20, 20, -19); glColor3f(0.0, 0.0, 1.0); glVertex3f(20, -20, -19); glVertex3f(-20, -20, -19); glEnd(); glEnable(GL_DEPTH_TEST); glShadeModel(GL_FLAT); glPushMatrix(); glTranslatef(0, 0, -distance); glRotatef(angle, 0, 0, 1); glRotatef(wobble_angle, 0, 1, 0); glCallList(base); glPopMatrix(); if (doubleBuffer) glXSwapBuffers(dpy, XtWindow(w)); if (!glXIsDirect(dpy, cx)) glFinish(); /* avoid indirect rendering latency from * queuing */ GL_ERROR_CHECK(); } void resize(Widget w, XtPointer data, XtPointer callData) { Dimension width, height; /* * It is possible for a drawing area widget's resize callback to be * called before the window is realized, and therefore before we have * made our OpenGL context to the window ID. So only let the glViewPort * call happen if we really have "made_current". */ if (made_current) { XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); glViewport(0, 0, (GLint) width, (GLint) height); } } void tick(void) { if (dollying) { theta += 0.1; distance = cos(theta) * 7 + 12; } if (rotation) angle -= 6; if (wobbling) { delta += 0.1; wobble_angle = sin(delta) * 40; } } Boolean animate(XtPointer data) { tick(); draw(glxarea); return False; /* leave work proc active */ } void syncstate(void) { if (motion && (dollying || rotation || wobbling)) { if (workId == 0) workId = XtAppAddWorkProc(app, animate, NULL); } else if (workId != 0) { XtRemoveWorkProc(workId); workId = 0; } } void toggle(void) { motion = !motion; /* toggle */ syncstate(); } void dolly(void) { dollying = !dollying; /* toggle */ syncstate(); } void rotate(void) { rotation = !rotation; /* toggle */ syncstate(); } void wobble(void) { wobbling = !wobbling; /* toggle */ syncstate(); } void quit(Widget w, XtPointer data, XtPointer callData) { exit(0); } void input(Widget w, XtPointer data, XtPointer callData) { XmDrawingAreaCallbackStruct *cd = (XmDrawingAreaCallbackStruct *) callData; char buf[1]; KeySym keysym; int rc; if (cd->event->type == KeyPress) if (XLookupString((XKeyEvent *) cd->event, buf, 1, &keysym, NULL) == 1) switch (keysym) { case XK_space: if (!motion) { /* advance one frame if not in motion */ tick(); draw(w); } break; case XK_Escape: exit(0); } } void map_state_changed(Widget w, XtPointer data, XEvent * event, Boolean * cont) { switch (event->type) { case MapNotify: syncstate(); break; case UnmapNotify: if (motion) { XtRemoveWorkProc(workId); workId = 0; } break; } } /* #define REPORT_GLYPHS */ #ifdef REPORT_GLYPHS #define DEBUG_GLYPH4(msg,a,b,c,d) printf(msg,a,b,c,d) #define DEBUG_GLYPH(msg) printf(msg) #else #define DEBUG_GLYPH4(msg,a,b,c,d) { /* nothing */ } #define DEBUG_GLYPH(msg) { /* nothing */ } #endif #define MAX_GLYPHS_PER_GRAB 512 /* this is big enough for 2^9 glyph character sets */ FontInfoPtr SuckGlyphsFromServer(Display * dpy, Font font) { Pixmap offscreen; XFontStruct *fontinfo; XImage *image; GC xgc; XGCValues values; int numchars; int width, height, pixwidth; int i, j; XCharStruct *charinfo; XChar2b character; char *bitmapData; int x, y; int spanLength; int charWidth, charHeight, maxSpanLength; int grabList[MAX_GLYPHS_PER_GRAB]; int glyphsPerGrab = MAX_GLYPHS_PER_GRAB; int numToGrab, thisglyph; FontInfoPtr myfontinfo; fontinfo = XQueryFont(dpy, font); if (!fontinfo) return NULL; numchars = fontinfo->max_char_or_byte2 - fontinfo->min_char_or_byte2 + 1; if (numchars < 1) return NULL; myfontinfo = (FontInfoPtr) malloc(sizeof(FontInfo) + (numchars - 1) * sizeof(PerCharInfo)); if (!myfontinfo) return NULL; myfontinfo->min_char = fontinfo->min_char_or_byte2; myfontinfo->max_char = fontinfo->max_char_or_byte2; myfontinfo->max_ascent = fontinfo->max_bounds.ascent; myfontinfo->max_descent = fontinfo->max_bounds.descent; myfontinfo->dlist_base = 0; width = fontinfo->max_bounds.rbearing - fontinfo->min_bounds.lbearing; height = fontinfo->max_bounds.ascent + fontinfo->max_bounds.descent; maxSpanLength = (width + 7) / 8; /* * Be careful determining the width of the pixmap; the X protocol allows * pixmaps of width 2^16-1 (unsigned short size) but drawing coordinates * max out at 2^15-1 (signed short size). If the width is too large, * we need to limit the glyphs per grab. */ if ((glyphsPerGrab * 8 * maxSpanLength) >= (1 << 15)) { glyphsPerGrab = (1 << 15) / (8 * maxSpanLength); } pixwidth = glyphsPerGrab * 8 * maxSpanLength; offscreen = XCreatePixmap(dpy, RootWindow(dpy, DefaultScreen(dpy)), pixwidth, height, 1); values.font = font; values.background = 0; values.foreground = 0; xgc = XCreateGC(dpy, offscreen, GCFont | GCBackground | GCForeground, &values); XFillRectangle(dpy, offscreen, xgc, 0, 0, 8 * maxSpanLength * glyphsPerGrab, height); XSetForeground(dpy, xgc, 1); numToGrab = 0; if (fontinfo->per_char == NULL) { charinfo = &(fontinfo->min_bounds); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; spanLength = (charWidth + 7) / 8; } for (i = 0; i < numchars; i++) { if (fontinfo->per_char != NULL) { charinfo = &(fontinfo->per_char[i]); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; if (charWidth == 0 || charHeight == 0) { /* Still must move raster pos even if empty character */ myfontinfo->glyph[i].width = 0; myfontinfo->glyph[i].height = 0; myfontinfo->glyph[i].xoffset = 0; myfontinfo->glyph[i].yoffset = 0; myfontinfo->glyph[i].advance = charinfo->width; myfontinfo->glyph[i].bitmap = NULL; goto PossiblyDoGrab; } } grabList[numToGrab] = i; /* XXX is this right for large fonts? */ character.byte2 = (i + fontinfo->min_char_or_byte2) & 255; character.byte1 = (i + fontinfo->min_char_or_byte2) >> 8; /* * XXX we could use XDrawImageString16 which would also paint the * backing rectangle but X server bugs in some scalable font * rasterizers makes it more effective to do XFillRectangles to clear * the pixmap and XDrawImage16 for the text. */ XDrawString16(dpy, offscreen, xgc, -charinfo->lbearing + 8 * maxSpanLength * numToGrab, charinfo->ascent, &character, 1); numToGrab++; PossiblyDoGrab: if (numToGrab >= glyphsPerGrab || i == numchars - 1) { image = XGetImage(dpy, offscreen, 0, 0, pixwidth, height, 1, XYPixmap); for (j = 0; j < numToGrab; j++) { thisglyph = grabList[j]; if (fontinfo->per_char != NULL) { charinfo = &(fontinfo->per_char[thisglyph]); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; spanLength = (charWidth + 7) / 8; } bitmapData = calloc(height * spanLength, sizeof(char)); if (!bitmapData) goto FreeFontAndReturn; DEBUG_GLYPH4("index %d, glyph %d (%d by %d)\n", j, thisglyph + fontinfo->min_char_or_byte2, charWidth, charHeight); for (y = 0; y < charHeight; y++) { for (x = 0; x < charWidth; x++) { /* * XXX The algorithm used to suck across the font ensures * that each glyph begins on a byte boundary. In theory * this would make it convienent to copy the glyph into * a byte oriented bitmap. We actually use the XGetPixel * function to extract each pixel from the image which is * not that efficient. We could either do tighter packing * in the pixmap or more efficient extraction from the * image. Oh well. */ if (XGetPixel(image, j * maxSpanLength * 8 + x, charHeight - 1 - y)) { DEBUG_GLYPH("x"); bitmapData[y * spanLength + x / 8] |= (1 << (x & 7)); } else { DEBUG_GLYPH(" "); } } DEBUG_GLYPH("\n"); } myfontinfo->glyph[thisglyph].width = charWidth; myfontinfo->glyph[thisglyph].height = charHeight; myfontinfo->glyph[thisglyph].xoffset = -charinfo->lbearing; myfontinfo->glyph[thisglyph].yoffset = charinfo->descent; myfontinfo->glyph[thisglyph].advance = charinfo->width; myfontinfo->glyph[thisglyph].bitmap = bitmapData; } XDestroyImage(image); numToGrab = 0; /* do we need to clear the offscreen pixmap to get more? */ if (i < numchars - 1) { XSetForeground(dpy, xgc, 0); XFillRectangle(dpy, offscreen, xgc, 0, 0, 8 * maxSpanLength * glyphsPerGrab, height); XSetForeground(dpy, xgc, 1); } } } XFreeGC(dpy, xgc); XFreePixmap(dpy, offscreen); return myfontinfo; FreeFontAndReturn: XDestroyImage(image); XFreeGC(dpy, xgc); XFreePixmap(dpy, offscreen); for (j = i - 1; j >= 0; j--) { if (myfontinfo->glyph[j].bitmap) free(myfontinfo->glyph[j].bitmap); } free(myfontinfo); return NULL; } void MakeCube(void) { /* * No back side to the cube is drawn since the animation makes sure the * back side can never be visible. The "wobble" function is constrained * so not to rotate far enough around to reveal the back side. */ glNewList(1, GL_COMPILE); glBegin(GL_QUAD_STRIP); /* back left post */ glColor3f(6.0, 0.5, 0.5); glVertex3f(0, 0, 0); glVertex3f(0, 1, 0); /* front left post */ glVertex3f(0, 0, 1); glVertex3f(0, 1, 1); glColor3f(1.0, 0.0, 0.0); /* front right post */ glVertex3f(1, 0, 1); glVertex3f(1, 1, 1); /* back right post */ glColor3f(6.0, 0.5, 0.5); glVertex3f(1, 0, 0); glVertex3f(1, 1, 0); glEnd(); glBegin(GL_QUADS); /* top face */ glVertex3f(1, 1, 0); glVertex3f(1, 1, 1); glVertex3f(0, 1, 1); glVertex3f(0, 1, 0); /* bottom face */ glVertex3f(1, 0, 0); glVertex3f(1, 0, 1); glVertex3f(0, 0, 1); glVertex3f(0, 0, 0); glEnd(); glEndList(); } void MakeGlyphDisplayList(FontInfoPtr font, int c) { PerCharInfoPtr glyph; char *bitmapData; int width, height, spanLength; int x, y; if (c < font->min_char || c > font->max_char) return; if (font->dlist_base == 0) { font->dlist_base = glGenLists(font->max_char - font->min_char + 1); if (font->dlist_base == 0) XtAppError(app, "could not generate font display lists"); } glyph = &font->glyph[c - font->min_char]; glNewList(c - font->min_char + font->dlist_base, GL_COMPILE); bitmapData = glyph->bitmap; if (bitmapData) { int oldx = 0, oldy = 0; glPushMatrix(); glTranslatef(-glyph->xoffset, -glyph->yoffset, 0); width = glyph->width; spanLength = (width + 7) / 8; height = glyph->height; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (bitmapData[y * spanLength + x / 8] & (1 << (x & 7))) { int y1, count; /* * Fonts tend to have good vertical repetion. If we find that * the vertically adjacent pixels in the glyph bitmap are also enabled, * we can scale a single cube instead of drawing a cube per pixel. */ for (y1 = y + 1, count = 1; y < height; y1++, count++) { if (!(bitmapData[y1 * spanLength + x / 8] & (1 << (x & 7)))) break; } glTranslatef(x - oldx, y - oldy, 0); oldx = x; oldy = y; if (count > 1) { glPushMatrix(); glScalef(1, count, 1); glCallList(1); glPopMatrix(); y += count - 1; } else { glCallList(1); } } } } glPopMatrix(); } glTranslatef(glyph->advance, 0, 0); glEndList(); } GLuint GetGlyphDisplayList(FontInfoPtr font, int c) { PerCharInfoPtr glyph; if (c < font->min_char || c > font->max_char) return 0; if (font->dlist_base == 0) XtAppError(app, "font not display listed"); return c - font->min_char + font->dlist_base; } MakeStringDisplayList(FontInfoPtr font, unsigned char *message, GLuint dlist) { unsigned char *c; for (c = message; *c != '\0'; c++) { MakeGlyphDisplayList(font, *c); } glNewList(dlist, GL_COMPILE); for (c = message; *c != '\0'; c++) { glCallList(GetGlyphDisplayList(font, *c)); } glEndList(); } int GetStringLength(FontInfoPtr font, unsigned char *message) { unsigned char *c; int ch; int width = 0; for (c = message; *c != '\0'; c++) { ch = *c; if (ch >= font->min_char && ch <= font->max_char) { width += font->glyph[ch - font->min_char].advance; } } return width; } SetupMessageDisplayList(FontEntryPtr fontEntry, int num, char *message[]) { FontInfoPtr fontinfo = fontEntry->fontinfo; GLfloat scaleFactor; int totalHeight, maxWidth, height, width; int i; if (!fontinfo) { fontinfo = SuckGlyphsFromServer(dpy, fontEntry->xfont->fid); fontEntry->fontinfo = fontinfo; } height = fontinfo->max_ascent + fontinfo->max_descent; maxWidth = 0; for (i = 0; i < num; i++) { MakeStringDisplayList(fontinfo, message[i], base + i + 1); width = GetStringLength(fontinfo, message[i]); if (width > maxWidth) maxWidth = width; } #define SHRINK_FACTOR 25.0 /* empirical */ totalHeight = height * num - fontinfo->max_descent; if (maxWidth > totalHeight) { scaleFactor = SHRINK_FACTOR / maxWidth; } else { scaleFactor = SHRINK_FACTOR / totalHeight; } glNewList(base, GL_COMPILE); glScalef(scaleFactor, scaleFactor, 1); /* 1 in Z gives glyphs constant depth */ for (i = 0; i < num; i++) { glPushMatrix(); width = GetStringLength(fontinfo, message[i]); glTranslatef(-width / 2.0, height * (num - i - 1) - totalHeight / 2.0, 0); glCallList(base + i + 1); glPopMatrix(); } glEndList(); } void fontSelect(Widget widget, XtPointer client_data, XmRowColumnCallbackStruct * cbs) { XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *) cbs->callbackstruct; FontEntryPtr fontEntry = (FontEntryPtr) cbs->data; if (state->set) { SetupMessageDisplayList(fontEntry, numMessages, messages); if (!motion) draw(glxarea); } } void neverCalled(void) { } void main(int argc, char *argv[]) { int i; #ifdef IRIX_5_1_MOTIF_BUG_WORKAROUND /* * XXX Unfortunately a bug in the IRIX 5.1 Motif shared library * causes a BadMatch X protocol error if the SGI look&feel * is enabled for this program. If we detect we are on an * IRIX 5.1 system, skip the first two fallback resources which * specify using the SGI look&feel. */ struct utsname versionInfo; if(uname(&versionInfo) >= 0) { if(!strcmp(versionInfo.sysname, "IRIX") && !strncmp(versionInfo.release, "5.1", 3)) { toplevel = XtAppInitialize(&app, "Textfun", NULL, 0, &argc, argv, &fallbackResources[2], NULL, 0); } } if(toplevel == NULL) { toplevel = XtAppInitialize(&app, "Textfun", NULL, 0, &argc, argv, fallbackResources, NULL, 0); } #else toplevel = XtAppInitialize(&app, "Textfun", NULL, 0, &argc, argv, fallbackResources, NULL, 0); #endif dpy = XtDisplay(toplevel); /* find an OpenGL-capable RGB visual with depth buffer */ vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf); if (vi == NULL) { vi = glXChooseVisual(dpy, DefaultScreen(dpy), snglBuf); if (vi == NULL) XtAppError(app, "no RGB visual with depth buffer"); doubleBuffer = GL_FALSE; } for (i = 0; i < NUM_FONT_ENTRIES; i++) { fontEntry[i].xfont = XLoadQueryFont(dpy, fontEntry[i].xlfd); if (i == 0 && !fontEntry[i].xfont) XtAppError(app, "could not get basic font"); } fontEntry[0].fontinfo = SuckGlyphsFromServer(dpy, fontEntry[0].xfont->fid); if (!fontEntry[0].fontinfo) XtAppError(app, "could not get font glyphs"); /* create an OpenGL rendering context */ cx = glXCreateContext(dpy, vi, /* no display list sharing */ None, /* favor direct */ GL_TRUE); if (cx == NULL) XtAppError(app, "could not create rendering context"); /* create an X colormap since probably not using default visual */ cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone); XtVaSetValues(toplevel, XtNvisual, vi->visual, XtNdepth, vi->depth, XtNcolormap, cmap, NULL); XtAddEventHandler(toplevel, StructureNotifyMask, False, map_state_changed, NULL); mainw = XmCreateMainWindow(toplevel, "mainw", NULL, 0); XtManageChild(mainw); /* create menu bar */ menubar = XmCreateMenuBar(mainw, "menubar", NULL, 0); XtManageChild(menubar); /* hack around Xt's ignorance of visuals */ XtSetArg(menuPaneArgs[0], XmNdepth, DefaultDepthOfScreen(XtScreen(mainw))); XtSetArg(menuPaneArgs[1], XmNcolormap, DefaultColormapOfScreen(XtScreen(mainw))); /* create File pulldown menu: Quit */ menupane = XmCreatePulldownMenu(menubar, "menupane", menuPaneArgs, 2); btn = XmCreatePushButton(menupane, "Quit", NULL, 0); XtAddCallback(btn, XmNactivateCallback, quit, NULL); XtManageChild(btn); XtSetArg(args[0], XmNsubMenuId, menupane); cascade = XmCreateCascadeButton(menubar, "File", args, 1); XtManageChild(cascade); /* create Options pulldown menu: Motion, Dolly, Rotate, Wobble */ menupane = XmCreatePulldownMenu(menubar, "menupane", menuPaneArgs, 2); btn = XmCreateToggleButton(menupane, "Motion", NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc) toggle, NULL); XtManageChild(btn); btn = XmCreateToggleButton(menupane, "Dolly", NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc) dolly, NULL); XtVaSetValues(btn, XmNset, True, NULL); XtManageChild(btn); btn = XmCreateToggleButton(menupane, "Rotate", NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc) rotate, NULL); XtManageChild(btn); btn = XmCreateToggleButton(menupane, "Wobble", NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc) wobble, NULL); XtManageChild(btn); XtSetArg(args[0], XmNsubMenuId, menupane); cascade = XmCreateCascadeButton(menubar, "Options", args, 1); XtManageChild(cascade); XtSetArg(menuPaneArgs[2], XmNradioBehavior, True); XtSetArg(menuPaneArgs[3], XmNradioAlwaysOne, True); menupane = XmCreatePulldownMenu(menubar, "menupane", menuPaneArgs, 4); XtAddCallback(menupane, XmNentryCallback, (XtCallbackProc) fontSelect, NULL); for (i = 0; i < NUM_FONT_ENTRIES; i++) { btn = XmCreateToggleButton(menupane, fontEntry[i].name, NULL, 0); XtAddCallback(btn, XmNvalueChangedCallback, (XtCallbackProc) neverCalled, &fontEntry[i]); if (i == 0) XtVaSetValues(btn, XmNset, True, NULL); if (!fontEntry[i].xfont) XtSetSensitive(btn, False); XtManageChild(btn); } XtSetArg(args[0], XmNsubMenuId, menupane); cascade = XmCreateCascadeButton(menubar, "Font", args, 1); XtManageChild(cascade); /* create framed drawing area for OpenGL rendering */ frame = XmCreateFrame(mainw, "frame", NULL, 0); XtManageChild(frame); glxarea = XtCreateManagedWidget("glxarea", xmDrawingAreaWidgetClass, frame, NULL, 0); XtAddCallback(glxarea, XmNexposeCallback, (XtCallbackProc) draw, NULL); XtAddCallback(glxarea, XmNresizeCallback, resize, NULL); XtAddCallback(glxarea, XmNinputCallback, input, NULL); /* set up application's window layout */ XmMainWindowSetAreas(mainw, menubar, NULL, NULL, NULL, frame); XtRealizeWidget(toplevel); /* * Once widget is realized (ie, associated with a created X window), we * can bind the OpenGL rendering context to the window. */ glXMakeCurrent(dpy, XtWindow(glxarea), cx); made_current = GL_TRUE; /* setup OpenGL state */ glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glClearDepth(1.0); glMatrixMode(GL_PROJECTION); glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 80); glMatrixMode(GL_MODELVIEW); MakeCube(); if (argv[1] != NULL) { numMessages = argc - 1; messages = &argv[1]; } else { numMessages = NUM_DEFAULT_MESSAGES; messages = defaultMessage; } base = glGenLists(numMessages + 1); SetupMessageDisplayList(&fontEntry[0], numMessages, messages); tick(); /* start event processing */ XtAppMainLoop(app); }