0e83ec34af50c1237832afa5692412a6bbcef68c
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001, 2002, 2003, 2004 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:        20000       \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 HAVE_LOCALE_H
58 # include <locale.h>
59 #endif /* HAVE_LOCALE_H */
60
61 #ifdef USE_GL /* whole file */
62
63 #ifdef HAVE_UNAME
64 # include <sys/utsname.h>
65 #endif /* HAVE_UNAME */
66
67
68 #include <GL/glu.h>
69 #include "glutstroke.h"
70 #include "glut_roman.h"
71 #define GLUT_FONT (&glutStrokeRoman)
72
73
74 typedef struct {
75   GLXContext *glx_context;
76   rotator *rot;
77   trackball_state *trackball;
78   Bool button_down_p;
79
80   GLuint text_list;
81
82   int ncolors;
83   XColor *colors;
84   int ccolor;
85
86   char *text;
87
88 } text_configuration;
89
90 static text_configuration *tps = NULL;
91
92 static char *text_fmt;
93 static char *do_spin;
94 static Bool do_wander;
95
96 static XrmOptionDescRec opts[] = {
97   { "-text",   ".text",   XrmoptionSepArg, 0 },
98   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
99   { "+spin",   ".spin",   XrmoptionNoArg, "" },
100   { "-wander", ".wander", XrmoptionNoArg, "True" },
101   { "+wander", ".wander", XrmoptionNoArg, "False" }
102 };
103
104 static argtype vars[] = {
105   {&text_fmt,  "text",   "Text",   DEF_TEXT,   t_String},
106   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_String},
107   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
108 };
109
110 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
111
112
113 /* Window management, etc
114  */
115 void
116 reshape_text (ModeInfo *mi, int width, int height)
117 {
118   GLfloat h = (GLfloat) height / (GLfloat) width;
119
120   glViewport (0, 0, (GLint) width, (GLint) height);
121
122   glMatrixMode(GL_PROJECTION);
123   glLoadIdentity();
124   gluPerspective (30.0, 1/h, 1.0, 100.0);
125
126   glMatrixMode(GL_MODELVIEW);
127   glLoadIdentity();
128   gluLookAt( 0.0, 0.0, 30.0,
129              0.0, 0.0, 0.0,
130              0.0, 1.0, 0.0);
131
132   glClear(GL_COLOR_BUFFER_BIT);
133 }
134
135
136 static void
137 gl_init (ModeInfo *mi)
138 {
139   text_configuration *tp = &tps[MI_SCREEN(mi)];
140   int wire = MI_IS_WIREFRAME(mi);
141
142   static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
143
144   if (!wire)
145     {
146       glLightfv(GL_LIGHT0, GL_POSITION, pos);
147       glEnable(GL_CULL_FACE);
148       glEnable(GL_LIGHTING);
149       glEnable(GL_LIGHT0);
150       glEnable(GL_DEPTH_TEST);
151     }
152
153   tp->text_list = glGenLists (1);
154   glNewList (tp->text_list, GL_COMPILE);
155   glEndList ();
156 }
157
158
159 /* The GLUT font only has ASCII characters in them, so do what we can to
160    convert Latin1 characters to the nearest ASCII equivalent... 
161  */
162 static void
163 latin1_to_ascii (char *s)
164 {
165   unsigned char *us = (unsigned char *) s;
166   const unsigned char ascii[95] = {
167     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
168     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
169     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
170     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
171     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
172     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
173     'u', 'u', 'y', 'p', 'y' };
174   while (*us)
175     {
176       if (*us >= 161)
177         *us = ascii[*us - 161];
178       else if (*us > 127)
179         *us = '?';
180       us++;
181     }
182 }
183
184
185 static void
186 parse_text (ModeInfo *mi)
187 {
188   text_configuration *tp = &tps[MI_SCREEN(mi)];
189
190   if (tp->text) free (tp->text);
191
192   if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
193     {
194 # ifdef HAVE_UNAME
195       struct utsname uts;
196
197       if (uname (&uts) < 0)
198         {
199           tp->text = strdup("uname() failed");
200         }
201       else
202         {
203           char *s;
204           if ((s = strchr(uts.nodename, '.')))
205             *s = 0;
206           tp->text = (char *) malloc(strlen(uts.nodename) +
207                                      strlen(uts.sysname) +
208                                      strlen(uts.version) +
209                                      strlen(uts.release) + 10);
210 #  if defined(_AIX)
211           sprintf(tp->text, "%s\n%s %s.%s",
212                   uts.nodename, uts.sysname, uts.version, uts.release);
213 #  elif defined(__APPLE__)  /* MacOS X + XDarwin */
214           sprintf(tp->text, "%s\n%s %s\n%s",
215                   uts.nodename, uts.sysname, uts.release, uts.machine);
216 #  else
217           sprintf(tp->text, "%s\n%s %s",
218                   uts.nodename, uts.sysname, uts.release);
219 #  endif /* special system types */
220         }
221 # else  /* !HAVE_UNAME */
222 #  ifdef VMS
223       tp->text = strdup(getenv("SYS$NODE"));
224 #  else
225       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
226 #  endif
227 # endif /* !HAVE_UNAME */
228     }
229   else if (!strchr (text_fmt, '%'))
230     {
231       tp->text = strdup (text_fmt);
232     }
233   else
234     {
235       time_t now = time ((time_t *) 0);
236       struct tm *tm = localtime (&now);
237       int L = strlen(text_fmt) + 100;
238       tp->text = (char *) malloc (L);
239       *tp->text = 0;
240       strftime (tp->text, L-1, text_fmt, tm);
241       if (!*tp->text)
242         sprintf (tp->text, "strftime error:\n%s", text_fmt);
243     }
244
245   latin1_to_ascii (tp->text);
246 }
247
248
249 Bool
250 text_handle_event (ModeInfo *mi, XEvent *event)
251 {
252   text_configuration *tp = &tps[MI_SCREEN(mi)];
253
254   if (event->xany.type == ButtonPress &&
255       event->xbutton.button & Button1)
256     {
257       tp->button_down_p = True;
258       gltrackball_start (tp->trackball,
259                          event->xbutton.x, event->xbutton.y,
260                          MI_WIDTH (mi), MI_HEIGHT (mi));
261       return True;
262     }
263   else if (event->xany.type == ButtonRelease &&
264            event->xbutton.button & Button1)
265     {
266       tp->button_down_p = False;
267       return True;
268     }
269   else if (event->xany.type == MotionNotify &&
270            tp->button_down_p)
271     {
272       gltrackball_track (tp->trackball,
273                          event->xmotion.x, event->xmotion.y,
274                          MI_WIDTH (mi), MI_HEIGHT (mi));
275       return True;
276     }
277
278   return False;
279 }
280
281
282 void 
283 init_text (ModeInfo *mi)
284 {
285   text_configuration *tp;
286   int i;
287
288 # ifdef HAVE_SETLOCALE
289   setlocale (LC_TIME, "");      /* for strftime() calls */
290 # endif
291
292   if (!tps) {
293     tps = (text_configuration *)
294       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
295     if (!tps) {
296       fprintf(stderr, "%s: out of memory\n", progname);
297       exit(1);
298     }
299
300     tp = &tps[MI_SCREEN(mi)];
301   }
302
303   tp = &tps[MI_SCREEN(mi)];
304
305   if ((tp->glx_context = init_GL(mi)) != NULL) {
306     gl_init(mi);
307     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
308   }
309
310   {
311     Bool spinx=False, spiny=False, spinz=False;
312     double spin_speed   = 0.5;
313     double wander_speed = 0.02;
314     double spin_accel   = 0.5;
315
316     char *s = do_spin;
317     while (*s)
318       {
319         if      (*s == 'x' || *s == 'X') spinx = True;
320         else if (*s == 'y' || *s == 'Y') spiny = True;
321         else if (*s == 'z' || *s == 'Z') spinz = True;
322         else
323           {
324             fprintf (stderr,
325          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
326                      progname, do_spin);
327             exit (1);
328           }
329         s++;
330       }
331
332     tp->rot = make_rotator (spinx ? spin_speed : 0,
333                             spiny ? spin_speed : 0,
334                             spinz ? spin_speed : 0,
335                             spin_accel,
336                             do_wander ? wander_speed : 0,
337                             False);
338     tp->trackball = gltrackball_init ();
339   }
340
341   tp->ncolors = 255;
342   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
343   make_smooth_colormap (0, 0, 0,
344                         tp->colors, &tp->ncolors,
345                         False, 0, False);
346
347   /* brighter colors, please... */
348   for (i = 0; i < tp->ncolors; i++)
349     {
350       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
351       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
352       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
353     }
354
355   parse_text (mi);
356
357 }
358
359
360 static int
361 fill_character (GLUTstrokeFont font, int c, Bool wire)
362 {
363   GLfloat tube_width = 10;
364
365   const StrokeCharRec *ch;
366   const StrokeRec *stroke;
367   const CoordRec *coord;
368   StrokeFontPtr fontinfo;
369   int i, j;
370
371   fontinfo = (StrokeFontPtr) font;
372
373   if (c < 0 || c >= fontinfo->num_chars)
374     return 0;
375   ch = &(fontinfo->ch[c]);
376   if (ch)
377     {
378       GLfloat lx=0, ly=0;
379       for (i = ch->num_strokes, stroke = ch->stroke;
380            i > 0; i--, stroke++) {
381         for (j = stroke->num_coords, coord = stroke->coord;
382              j > 0; j--, coord++)
383           {
384 # ifdef SMOOTH_TUBE
385             int smooth = True;
386 # else
387             int smooth = False;
388 # endif
389             if (j != stroke->num_coords)
390               tube (lx,       ly,       0,
391                     coord->x, coord->y, 0,
392                     tube_width,
393                     tube_width * 0.15,
394                     TUBE_FACES, smooth, True, wire);
395             lx = coord->x;
396             ly = coord->y;
397           }
398       }
399       return (int) (ch->right + tube_width/2);
400     }
401   return 0;
402 }
403
404
405 static int
406 text_extents (const char *string, int *wP, int *hP)
407 {
408   const char *s, *start;
409   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
410   int lines = 0;
411   *wP = 0;
412   *hP = 0;
413   start = string;
414   s = start;
415   while (1)
416     if (*s == '\n' || *s == 0)
417       {
418         int w = 0;
419         while (start < s)
420           {
421             w += glutStrokeWidth(GLUT_FONT, *start);
422             start++;
423           }
424         start = s+1;
425
426         if (w > *wP) *wP = w;
427         *hP += line_height;
428         lines++;
429         if (*s == 0) break;
430         s++;
431       }
432     else
433       s++;
434
435   return lines;
436 }
437
438
439 static void
440 fill_string (const char *string, Bool wire)
441 {
442   const char *s, *start;
443   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
444   int off;
445   GLfloat x = 0, y = 0;
446   int lines;
447
448   int ow, oh;
449   lines = text_extents (string, &ow, &oh);
450
451   y = oh / 2 - line_height;
452
453   start = string;
454   s = start;
455   while (1)
456     if (*s == '\n' || *s == 0)
457       {
458         int line_w = 0;
459         const char *s2;
460         const char *lstart = start;
461         const char *lend = s;
462
463         /* strip off whitespace at beginning and end of line
464            (since we're centering.) */
465         while (lend > lstart && isspace(lend[-1]))
466           lend--;
467         while (lstart < lend && isspace(*lstart))
468           lstart++;
469
470         for (s2 = lstart; s2 < lend; s2++)
471           line_w += glutStrokeWidth (GLUT_FONT, *s2);
472
473         x = (-ow/2) + ((ow-line_w)/2);
474         for (s2 = lstart; s2 < lend; s2++)
475           {
476             glPushMatrix();
477             glTranslatef(x, y, 0);
478             off = fill_character (GLUT_FONT, *s2, wire);
479             x += off;
480             glPopMatrix();
481           }
482
483         start = s+1;
484
485         y -= line_height;
486         if (*s == 0) break;
487         s++;
488       }
489     else
490       s++;
491 }
492
493
494 void
495 draw_text (ModeInfo *mi)
496 {
497   text_configuration *tp = &tps[MI_SCREEN(mi)];
498   Display *dpy = MI_DISPLAY(mi);
499   Window window = MI_WINDOW(mi);
500   int wire = MI_IS_WIREFRAME(mi);
501
502   static GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
503   static GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
504
505   if (!tp->glx_context)
506     return;
507
508   if (strchr (text_fmt, '%'))
509     {
510       static time_t last_update = -1;
511       time_t now = time ((time_t *) 0);
512       if (now != last_update) /* do it once a second */
513         parse_text (mi);
514       last_update = now;
515     }
516
517   glShadeModel(GL_SMOOTH);
518
519   glEnable(GL_DEPTH_TEST);
520   glEnable(GL_NORMALIZE);
521   glEnable(GL_CULL_FACE);
522
523   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
524
525   glPushMatrix ();
526
527   glScalef(1.1, 1.1, 1.1);
528
529   {
530     double x, y, z;
531     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
532     glTranslatef((x - 0.5) * 8,
533                  (y - 0.5) * 8,
534                  (z - 0.5) * 8);
535
536     gltrackball_rotate (tp->trackball);
537
538     get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
539     glRotatef (x * 360, 1.0, 0.0, 0.0);
540     glRotatef (y * 360, 0.0, 1.0, 0.0);
541     glRotatef (z * 360, 0.0, 0.0, 1.0);
542   }
543
544
545   glColor4fv (white);
546
547   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
548   color[1] = tp->colors[tp->ccolor].green / 65536.0;
549   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
550   tp->ccolor++;
551   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
552
553   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
554
555   glScalef(0.01, 0.01, 0.01);
556
557   fill_string(tp->text, wire);
558
559   glPopMatrix ();
560
561   if (mi->fps_p) do_fps (mi);
562   glFinish();
563
564   glXSwapBuffers(dpy, window);
565 }
566
567 #endif /* USE_GL */