5b80d76c68b5aad9cb6183b64515b79a67044e58
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001, 2002 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #include <X11/Intrinsic.h>
13
14 extern XtAppContext app;
15
16 #define PROGCLASS       "GLText"
17 #define HACK_INIT       init_text
18 #define HACK_DRAW       draw_text
19 #define HACK_RESHAPE    reshape_text
20 #define HACK_HANDLE_EVENT text_handle_event
21 #define EVENT_MASK      PointerMotionMask
22 #define sws_opts        xlockmore_opts
23
24 #define DEF_TEXT        "(default)"
25 #define DEF_SPIN        "XYZ"
26 #define DEF_WANDER      "True"
27
28 #define DEFAULTS        "*delay:        10000       \n" \
29                         "*showFPS:      False       \n" \
30                         "*wireframe:    False       \n" \
31                         "*spin:       " DEF_SPIN   "\n" \
32                         "*wander:     " DEF_WANDER "\n" \
33                         "*text:       " DEF_TEXT   "\n"
34
35
36 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
37
38 #ifdef SMOOTH_TUBE
39 # define TUBE_FACES  12   /* how densely to render tubes */
40 #else
41 # define TUBE_FACES  8
42 #endif
43
44
45 #undef countof
46 #define countof(x) (sizeof((x))/sizeof((*x)))
47
48 #include "xlockmore.h"
49 #include "colors.h"
50 #include "tube.h"
51 #include "rotator.h"
52 #include "gltrackball.h"
53 #include <time.h>
54 #include <sys/time.h>
55 #include <ctype.h>
56
57 #ifdef USE_GL /* whole file */
58
59 #ifdef HAVE_UNAME
60 # include <sys/utsname.h>
61 #endif /* HAVE_UNAME */
62
63
64 #include <GL/glu.h>
65 #include "glutstroke.h"
66 #include "glut_roman.h"
67 #define GLUT_FONT (&glutStrokeRoman)
68
69
70 typedef struct {
71   GLXContext *glx_context;
72   rotator *rot;
73   trackball_state *trackball;
74   Bool button_down_p;
75
76   GLuint text_list;
77
78   int ncolors;
79   XColor *colors;
80   int ccolor;
81
82   char *text;
83
84 } text_configuration;
85
86 static text_configuration *tps = NULL;
87
88 static char *text_fmt;
89 static char *do_spin;
90 static Bool do_wander;
91
92 static XrmOptionDescRec opts[] = {
93   { "-text",   ".text",   XrmoptionSepArg, 0 },
94   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
95   { "+spin",   ".spin",   XrmoptionNoArg, "" },
96   { "-wander", ".wander", XrmoptionNoArg, "True" },
97   { "+wander", ".wander", XrmoptionNoArg, "False" }
98 };
99
100 static argtype vars[] = {
101   {(caddr_t *) &text_fmt,  "text",   "Text",   DEF_TEXT,   t_String},
102   {(caddr_t *) &do_spin,   "spin",   "Spin",   DEF_SPIN,   t_String},
103   {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
104 };
105
106 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
107
108
109 /* Window management, etc
110  */
111 void
112 reshape_text (ModeInfo *mi, int width, int height)
113 {
114   GLfloat h = (GLfloat) height / (GLfloat) width;
115
116   glViewport (0, 0, (GLint) width, (GLint) height);
117
118   glMatrixMode(GL_PROJECTION);
119   glLoadIdentity();
120   gluPerspective (30.0, 1/h, 1.0, 100.0);
121
122   glMatrixMode(GL_MODELVIEW);
123   glLoadIdentity();
124   gluLookAt( 0.0, 0.0, 30.0,
125              0.0, 0.0, 0.0,
126              0.0, 1.0, 0.0);
127
128   glClear(GL_COLOR_BUFFER_BIT);
129 }
130
131
132 static void
133 gl_init (ModeInfo *mi)
134 {
135   text_configuration *tp = &tps[MI_SCREEN(mi)];
136   int wire = MI_IS_WIREFRAME(mi);
137
138   static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
139
140   if (!wire)
141     {
142       glLightfv(GL_LIGHT0, GL_POSITION, pos);
143       glEnable(GL_CULL_FACE);
144       glEnable(GL_LIGHTING);
145       glEnable(GL_LIGHT0);
146       glEnable(GL_DEPTH_TEST);
147     }
148
149   tp->text_list = glGenLists (1);
150   glNewList (tp->text_list, GL_COMPILE);
151   glEndList ();
152 }
153
154
155 static void
156 parse_text (ModeInfo *mi)
157 {
158   text_configuration *tp = &tps[MI_SCREEN(mi)];
159
160   if (tp->text) free (tp->text);
161
162   if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
163     {
164 # ifdef HAVE_UNAME
165       struct utsname uts;
166
167       if (uname (&uts) < 0)
168         {
169           tp->text = strdup("uname() failed");
170         }
171       else
172         {
173           char *s;
174           if ((s = strchr(uts.nodename, '.')))
175             *s = 0;
176           tp->text = (char *) malloc(strlen(uts.nodename) +
177                                      strlen(uts.sysname) +
178                                      strlen(uts.version) +
179                                      strlen(uts.release) + 10);
180 #  ifdef _AIX
181           sprintf(tp->text, "%s\n%s %s.%s",
182                   uts.nodename, uts.sysname, uts.version, uts.release);
183 #  else  /* !_AIX */
184           sprintf(tp->text, "%s\n%s %s",
185                   uts.nodename, uts.sysname, uts.release);
186 #  endif /* !_AIX */
187         }
188 # else  /* !HAVE_UNAME */
189 #  ifdef VMS
190       tp->text = strdup(getenv("SYS$NODE"));
191 #  else
192       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
193 #  endif
194 # endif /* !HAVE_UNAME */
195     }
196   else if (!strchr (text_fmt, '%'))
197     {
198       tp->text = strdup (text_fmt);
199     }
200   else
201     {
202       time_t now = time ((time_t *) 0);
203       struct tm *tm = localtime (&now);
204       int L = strlen(text_fmt) + 100;
205       tp->text = (char *) malloc (L);
206       *tp->text = 0;
207       strftime (tp->text, L-1, text_fmt, tm);
208       if (!*tp->text)
209         sprintf (tp->text, "strftime error:\n%s", text_fmt);
210     }
211 }
212
213
214 Bool
215 text_handle_event (ModeInfo *mi, XEvent *event)
216 {
217   text_configuration *tp = &tps[MI_SCREEN(mi)];
218
219   if (event->xany.type == ButtonPress &&
220       event->xbutton.button & Button1)
221     {
222       tp->button_down_p = True;
223       gltrackball_start (tp->trackball,
224                          event->xbutton.x, event->xbutton.y,
225                          MI_WIDTH (mi), MI_HEIGHT (mi));
226       return True;
227     }
228   else if (event->xany.type == ButtonRelease &&
229            event->xbutton.button & Button1)
230     {
231       tp->button_down_p = False;
232       return True;
233     }
234   else if (event->xany.type == MotionNotify &&
235            tp->button_down_p)
236     {
237       gltrackball_track (tp->trackball,
238                          event->xmotion.x, event->xmotion.y,
239                          MI_WIDTH (mi), MI_HEIGHT (mi));
240       return True;
241     }
242
243   return False;
244 }
245
246
247 void 
248 init_text (ModeInfo *mi)
249 {
250   text_configuration *tp;
251   int i;
252
253   if (!tps) {
254     tps = (text_configuration *)
255       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
256     if (!tps) {
257       fprintf(stderr, "%s: out of memory\n", progname);
258       exit(1);
259     }
260
261     tp = &tps[MI_SCREEN(mi)];
262   }
263
264   tp = &tps[MI_SCREEN(mi)];
265
266   if ((tp->glx_context = init_GL(mi)) != NULL) {
267     gl_init(mi);
268     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
269   }
270
271   {
272     Bool spinx=False, spiny=False, spinz=False;
273     double spin_speed   = 1.0;
274     double wander_speed = 0.05;
275     double spin_accel   = 1.0;
276
277     char *s = do_spin;
278     while (*s)
279       {
280         if      (*s == 'x' || *s == 'X') spinx = True;
281         else if (*s == 'y' || *s == 'Y') spiny = True;
282         else if (*s == 'z' || *s == 'Z') spinz = True;
283         else
284           {
285             fprintf (stderr,
286          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
287                      progname, do_spin);
288             exit (1);
289           }
290         s++;
291       }
292
293     tp->rot = make_rotator (spinx ? spin_speed : 0,
294                             spiny ? spin_speed : 0,
295                             spinz ? spin_speed : 0,
296                             spin_accel,
297                             do_wander ? wander_speed : 0,
298                             False);
299     tp->trackball = gltrackball_init ();
300   }
301
302   tp->ncolors = 255;
303   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
304   make_smooth_colormap (0, 0, 0,
305                         tp->colors, &tp->ncolors,
306                         False, 0, False);
307
308   /* brighter colors, please... */
309   for (i = 0; i < tp->ncolors; i++)
310     {
311       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
312       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
313       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
314     }
315
316   parse_text (mi);
317
318 }
319
320
321 static int
322 fill_character (GLUTstrokeFont font, int c, Bool wire)
323 {
324   GLfloat tube_width = 10;
325
326   const StrokeCharRec *ch;
327   const StrokeRec *stroke;
328   const CoordRec *coord;
329   StrokeFontPtr fontinfo;
330   int i, j;
331
332   fontinfo = (StrokeFontPtr) font;
333
334   if (c < 0 || c >= fontinfo->num_chars)
335     return 0;
336   ch = &(fontinfo->ch[c]);
337   if (ch)
338     {
339       GLfloat lx=0, ly=0;
340       for (i = ch->num_strokes, stroke = ch->stroke;
341            i > 0; i--, stroke++) {
342         for (j = stroke->num_coords, coord = stroke->coord;
343              j > 0; j--, coord++)
344           {
345 # ifdef SMOOTH_TUBE
346             int smooth = True;
347 # else
348             int smooth = False;
349 # endif
350             if (j != stroke->num_coords)
351               tube (lx,       ly,       0,
352                     coord->x, coord->y, 0,
353                     tube_width,
354                     tube_width * 0.15,
355                     TUBE_FACES, smooth, wire);
356             lx = coord->x;
357             ly = coord->y;
358           }
359       }
360       return (int) (ch->right + tube_width/2);
361     }
362   return 0;
363 }
364
365
366 static int
367 text_extents (const char *string, int *wP, int *hP)
368 {
369   const char *s, *start;
370   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
371   int lines = 0;
372   *wP = 0;
373   *hP = 0;
374   start = string;
375   s = start;
376   while (1)
377     if (*s == '\n' || *s == 0)
378       {
379         int w = 0;
380         while (start < s)
381           {
382             w += glutStrokeWidth(GLUT_FONT, *start);
383             start++;
384           }
385         start = s+1;
386
387         if (w > *wP) *wP = w;
388         *hP += line_height;
389         s++;
390         lines++;
391         if (*s == 0) break;
392       }
393     else
394       s++;
395
396   return lines;
397 }
398
399
400 static void
401 fill_string (const char *string, Bool wire)
402 {
403   const char *s, *start;
404   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
405   int off;
406   GLfloat x = 0, y = 0;
407   int lines;
408
409   int ow, oh;
410   lines = text_extents (string, &ow, &oh);
411
412   y = oh / 2 - line_height;
413
414   start = string;
415   s = start;
416   while (1)
417     if (*s == '\n' || *s == 0)
418       {
419         int line_w = 0;
420         const char *s2;
421         const char *lstart = start;
422         const char *lend = s;
423
424         /* strip off whitespace at beginning and end of line
425            (since we're centering.) */
426         while (lend > lstart && isspace(lend[-1]))
427           lend--;
428         while (lstart < lend && isspace(*lstart))
429           lstart++;
430
431         for (s2 = lstart; s2 < lend; s2++)
432           line_w += glutStrokeWidth (GLUT_FONT, *s2);
433
434         x = (-ow/2) + ((ow-line_w)/2);
435         for (s2 = lstart; s2 < lend; s2++)
436           {
437             glPushMatrix();
438             glTranslatef(x, y, 0);
439             off = fill_character (GLUT_FONT, *s2, wire);
440             x += off;
441             glPopMatrix();
442           }
443
444         start = s+1;
445
446         y -= line_height;
447         if (*s == 0) break;
448         s++;
449       }
450     else
451       s++;
452 }
453
454
455 void
456 draw_text (ModeInfo *mi)
457 {
458   text_configuration *tp = &tps[MI_SCREEN(mi)];
459   Display *dpy = MI_DISPLAY(mi);
460   Window window = MI_WINDOW(mi);
461   int wire = MI_IS_WIREFRAME(mi);
462
463   static GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
464   static GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
465
466   if (!tp->glx_context)
467     return;
468
469   if (strchr (text_fmt, '%'))
470     {
471       static time_t last_update = -1;
472       time_t now = time ((time_t *) 0);
473       if (now != last_update) /* do it once a second */
474         parse_text (mi);
475       last_update = now;
476     }
477
478   glShadeModel(GL_SMOOTH);
479
480   glEnable(GL_DEPTH_TEST);
481   glEnable(GL_NORMALIZE);
482   glEnable(GL_CULL_FACE);
483
484   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
485
486   glPushMatrix ();
487
488   glScalef(1.1, 1.1, 1.1);
489
490   {
491     double x, y, z;
492     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
493     glTranslatef((x - 0.5) * 8,
494                  (y - 0.5) * 8,
495                  (z - 0.5) * 8);
496
497     gltrackball_rotate (tp->trackball);
498
499     get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
500     glRotatef (x * 360, 1.0, 0.0, 0.0);
501     glRotatef (y * 360, 0.0, 1.0, 0.0);
502     glRotatef (z * 360, 0.0, 0.0, 1.0);
503   }
504
505
506   glColor4fv (white);
507
508   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
509   color[1] = tp->colors[tp->ccolor].green / 65536.0;
510   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
511   tp->ccolor++;
512   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
513
514   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
515
516   glScalef(0.01, 0.01, 0.01);
517
518   fill_string(tp->text, wire);
519
520   glPopMatrix ();
521
522   if (mi->fps_p) do_fps (mi);
523   glFinish();
524
525   glXSwapBuffers(dpy, window);
526 }
527
528 #endif /* USE_GL */