1    | /*	SCCS Id: @(#)wintext.c	3.3	96/04/05	*/
2    | /* Copyright (c) Dean Luick, 1992				  */
3    | /* NetHack may be freely redistributed.  See license for details. */
4    | 
5    | /*
6    |  * File for dealing with text windows.
7    |  *
8    |  *	+ No global functions.
9    |  */
10   | 
11   | #ifndef SYSV
12   | #define PRESERVE_NO_SYSV	/* X11 include files may define SYSV */
13   | #endif
14   | 
15   | #include <X11/Intrinsic.h>
16   | #include <X11/StringDefs.h>
17   | #include <X11/Shell.h>
18   | #include <X11/Xos.h>
19   | #include <X11/Xaw/Form.h>
20   | #include <X11/Xaw/AsciiText.h>
21   | #include <X11/Xaw/Cardinals.h>
22   | #include <X11/Xatom.h>
23   | 
24   | #ifdef PRESERVE_NO_SYSV
25   | # ifdef SYSV
26   | #  undef SYSV
27   | # endif
28   | # undef PRESERVE_NO_SYSV
29   | #endif
30   | 
31   | #include "hack.h"
32   | #include "winX.h"
33   | #include "xwindow.h"
34   | 
35   | #ifdef GRAPHIC_TOMBSTONE
36   | #include <X11/xpm.h>
37   | #endif
38   | 
39   | 
40   | #define TRANSIENT_TEXT	/* text window is a transient window (no positioning) */
41   | 
42   | static const char text_translations[] =
43   |     "#override\n\
44   |      <BtnDown>: dismiss_text()\n\
45   |      <Key>: key_dismiss_text()";
46   | 
47   | #ifdef GRAPHIC_TOMBSTONE
48   | static const char rip_translations[] =
49   |     "#override\n\
50   |      <BtnDown>: rip_dismiss_text()\n\
51   |      <Key>: rip_dismiss_text()";
52   | 
53   | static Widget FDECL(create_ripout_widget, (Widget));
54   | #endif
55   | 
56   | /*ARGSUSED*/
57   | void
58   | delete_text(w, event, params, num_params)
59   |     Widget w;
60   |     XEvent *event;
61   |     String *params;
62   |     Cardinal *num_params;
63   | {
64   |     struct xwindow *wp;
65   |     struct text_info_t *text_info;
66   | 
67   |     wp = find_widget(w);
68   |     text_info = wp->text_information;
69   | 
70   |     nh_XtPopdown(wp->popup);
71   | 
72   |     if (text_info->blocked) {
73   | 	exit_x_event = TRUE;
74   |     } else if (text_info->destroy_on_ack) {
75   | 	destroy_text_window(wp);
76   |     }
77   | }
78   | 
79   | /*
80   |  * Callback used for all text windows.  The window is poped down on any key
81   |  * or button down event.  It is destroyed if the main nethack code is done
82   |  * with it.
83   |  */
84   | /*ARGSUSED*/
85   | void
86   | dismiss_text(w, event, params, num_params)
87   |     Widget w;
88   |     XEvent *event;
89   |     String *params;
90   |     Cardinal *num_params;
91   | {
92   |     struct xwindow *wp;
93   |     struct text_info_t *text_info;
94   | 
95   |     wp = find_widget(w);
96   |     text_info = wp->text_information;
97   | 
98   |     nh_XtPopdown(wp->popup);
99   | 
100  |     if (text_info->blocked) {
101  | 	exit_x_event = TRUE;
102  |     } else if (text_info->destroy_on_ack) {
103  | 	destroy_text_window(wp);
104  |     }
105  | }
106  | 
107  | /* Dismiss when a non-modifier key pressed. */
108  | void
109  | key_dismiss_text(w, event, params, num_params)
110  |     Widget w;
111  |     XEvent *event;
112  |     String *params;
113  |     Cardinal *num_params;
114  | {
115  |     char ch = key_event_to_char((XKeyEvent *) event);
116  |     if (ch) dismiss_text(w, event, params, num_params);
117  | }
118  | 
119  | #ifdef GRAPHIC_TOMBSTONE
120  | /* Dismiss from clicking on rip image. */
121  | void
122  | rip_dismiss_text(w, event, params, num_params)
123  |     Widget w;
124  |     XEvent *event;
125  |     String *params;
126  |     Cardinal *num_params;
127  | {
128  |     dismiss_text(XtParent(w), event, params, num_params);
129  | }
130  | #endif
131  | 
132  | 
133  | /* ARGSUSED */
134  | void
135  | add_to_text_window(wp, attr, str)
136  |     struct xwindow *wp;
137  |     int attr;	/* currently unused */
138  |     const char *str;
139  | {
140  |     struct text_info_t *text_info = wp->text_information;
141  |     int width;
142  | 
143  |     append_text_buffer(&text_info->text, str, FALSE);
144  | 
145  |     /* Calculate text width and save longest line */
146  |     width = XTextWidth(text_info->fs, str, (int) strlen(str));
147  |     if (width > text_info->max_width)
148  | 	text_info->max_width = width;
149  | }
150  | 
151  | void
152  | display_text_window(wp, blocking)
153  |     struct xwindow *wp;
154  |     boolean blocking;
155  | {
156  |     struct text_info_t *text_info;
157  |     Arg args[8];
158  |     Cardinal num_args;
159  |     Dimension width, height;
160  |     int nlines;
161  | 
162  |     text_info = wp->text_information;
163  |     width  = text_info->max_width + text_info->extra_width;
164  |     text_info->blocked = blocking;
165  |     text_info->destroy_on_ack = FALSE;
166  | 
167  |     /*
168  |      * Calculate the number of lines to use.  First, find the number of
169  |      * lines that would fit on the screen.  Next, remove four of these
170  |      * lines to give room for a possible window manager titlebar (some
171  |      * wm's put a titlebar on transient windows).  Make sure we have
172  |      * _some_ lines.  Finally, use the number of lines in the text if
173  |      * there are fewer than the max.
174  |      */
175  |     nlines = (XtScreen(wp->w)->height - text_info->extra_height) /
176  | 			(text_info->fs->ascent + text_info->fs->descent);
177  |     nlines -= 4;
178  | 
179  |     if (nlines > text_info->text.num_lines)
180  | 	nlines = text_info->text.num_lines;
181  |     if (nlines <= 0) nlines = 1;
182  | 
183  |     /* Font height is ascent + descent. */
184  |     height = (nlines * (text_info->fs->ascent + text_info->fs->descent))
185  | 						    + text_info->extra_height;
186  | 
187  |     num_args = 0;
188  | 
189  |     if (nlines < text_info->text.num_lines) {
190  | 	/* add on width of scrollbar.  Really should look this up,
191  | 	 * but can't until the window is realized.  Chicken-and-egg problem.
192  | 	 */
193  | 	width += 20;
194  |     }
195  | 
196  | #ifdef GRAPHIC_TOMBSTONE
197  |     if (text_info->is_rip) {
198  | 	Widget rip = create_ripout_widget(XtParent(wp->w));
199  | 	XtSetArg(args[num_args], XtNfromVert, rip);	num_args++;
200  |     }
201  | #endif
202  | 
203  |     if (width > (Dimension) XtScreen(wp->w)->width) { /* too wide for screen */
204  | 	/* Back off some amount - we really need to back off the scrollbar */
205  | 	/* width plus some extra.					   */
206  | 	width = XtScreen(wp->w)->width - 20;
207  |     }
208  |     XtSetArg(args[num_args], XtNstring, text_info->text.text);	num_args++;
209  |     XtSetArg(args[num_args], XtNwidth,  width);			num_args++;
210  |     XtSetArg(args[num_args], XtNheight, height);		num_args++;
211  |     XtSetValues(wp->w, args, num_args);
212  | 
213  | #ifdef TRANSIENT_TEXT
214  |     XtRealizeWidget(wp->popup);
215  |     XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
216  | 		    &wm_delete_window, 1);
217  |     positionpopup(wp->popup, FALSE);
218  | #endif
219  | 
220  |     nh_XtPopup(wp->popup, (int)XtGrabNone, wp->w);
221  | 
222  |     /* Kludge alert.  Scrollbars are not sized correctly by the Text widget */
223  |     /* if added before the window is displayed, so do it afterward. */
224  |     num_args = 0;
225  |     if (nlines < text_info->text.num_lines) {	/* add vert scrollbar */
226  | 	XtSetArg(args[num_args], XtNscrollVertical, XawtextScrollAlways);
227  | 								num_args++;
228  |     }
229  |     if (width >= (Dimension) (XtScreen(wp->w)->width-20)) {	/* too wide */
230  | 	XtSetArg(args[num_args], XtNscrollHorizontal, XawtextScrollAlways);
231  | 								num_args++;
232  |     }
233  |     if (num_args) XtSetValues(wp->w, args, num_args);
234  | 
235  |     /* We want the user to acknowlege. */
236  |     if (blocking) {
237  | 	(void) x_event(EXIT_ON_EXIT);
238  | 	nh_XtPopdown(wp->popup);
239  |     }
240  | }
241  | 
242  | 
243  | void
244  | create_text_window(wp)
245  |     struct xwindow *wp;
246  | {
247  |     struct text_info_t *text_info;
248  |     Arg args[8];
249  |     Cardinal num_args;
250  |     Position top_margin, bottom_margin, left_margin, right_margin;
251  |     Widget form;
252  | 
253  |     wp->type = NHW_TEXT;
254  | 
255  |     wp->text_information = text_info =
256  | 		    (struct text_info_t *) alloc(sizeof(struct text_info_t));
257  | 
258  |     init_text_buffer(&text_info->text);
259  |     text_info->max_width      = 0;
260  |     text_info->extra_width    = 0;
261  |     text_info->extra_height   = 0;
262  |     text_info->blocked	      = FALSE;
263  |     text_info->destroy_on_ack = TRUE;	/* Ok to destroy before display */
264  | #ifdef GRAPHIC_TOMBSTONE
265  |     text_info->is_rip	      = FALSE;
266  | #endif
267  | 
268  |     num_args = 0;
269  |     XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
270  |     XtSetArg(args[num_args], XtNtranslations,
271  | 		XtParseTranslationTable(text_translations));	num_args++;
272  | 
273  | #ifdef TRANSIENT_TEXT
274  |     wp->popup = XtCreatePopupShell("text", transientShellWidgetClass,
275  | 				   toplevel, args, num_args);
276  | #else
277  |     wp->popup = XtCreatePopupShell("text", topLevelShellWidgetClass,
278  | 				   toplevel, args, num_args);
279  | #endif
280  |     XtOverrideTranslations(wp->popup,
281  | 	XtParseTranslationTable("<Message>WM_PROTOCOLS: delete_text()"));
282  | 
283  |     num_args = 0;
284  |     XtSetArg(args[num_args], XtNallowShellResize, True);	num_args++;
285  |     form = XtCreateManagedWidget("form", formWidgetClass, wp->popup,
286  | 		args, num_args);
287  | 
288  |     num_args = 0;
289  |     XtSetArg(args[num_args], XtNdisplayCaret, False);		num_args++;
290  |     XtSetArg(args[num_args], XtNresize, XawtextResizeBoth);	num_args++;
291  |     XtSetArg(args[num_args], XtNtranslations,
292  | 		XtParseTranslationTable(text_translations));	num_args++;
293  | 
294  |     wp->w = XtCreateManagedWidget(
295  | 		killer && WIN_MAP == WIN_ERR ?
296  | 				  "tombstone" : "text_text", /* name */
297  | 		asciiTextWidgetClass,
298  | 		form,			/* parent widget */
299  | 		args,			/* set some values */
300  | 		num_args);		/* number of values to set */
301  | 
302  |     /* Get the font and margin information. */
303  |     num_args = 0;
304  |     XtSetArg(args[num_args], XtNfont,	      &text_info->fs); num_args++;
305  |     XtSetArg(args[num_args], XtNtopMargin,    &top_margin);    num_args++;
306  |     XtSetArg(args[num_args], XtNbottomMargin, &bottom_margin); num_args++;
307  |     XtSetArg(args[num_args], XtNleftMargin,   &left_margin);   num_args++;
308  |     XtSetArg(args[num_args], XtNrightMargin,  &right_margin);  num_args++;
309  |     XtGetValues(wp->w, args, num_args);
310  | 
311  |     text_info->extra_width  = left_margin + right_margin;
312  |     text_info->extra_height = top_margin + bottom_margin;
313  | }
314  | 
315  | void
316  | destroy_text_window(wp)
317  |     struct xwindow *wp;
318  | {
319  |     /* Don't need to pop down, this only called from dismiss_text(). */
320  | 
321  |     struct text_info_t *text_info = wp->text_information;
322  | 
323  |     /*
324  |      * If the text window was blocked, then the user has already ACK'ed
325  |      * it and we are free to really destroy the window.  Otherwise, don't
326  |      * destroy until the user dismisses the window via a key or button
327  |      * press.
328  |      */
329  |     if (text_info->blocked || text_info->destroy_on_ack) {
330  | 	XtDestroyWidget(wp->popup);
331  | 	free_text_buffer(&text_info->text);
332  | 	free((genericptr_t)text_info),  wp->text_information = 0;
333  | 	wp->type = NHW_NONE;	/* allow reuse */
334  |     } else {
335  | 	text_info->destroy_on_ack = TRUE;	/* destroy on next ACK */
336  |     }
337  | }
338  | 
339  | void
340  | clear_text_window(wp)
341  |     struct xwindow *wp;
342  | {
343  |     clear_text_buffer(&wp->text_information->text);
344  | }
345  | 
346  | 
347  | /* text buffer routines ---------------------------------------------------- */
348  | 
349  | /* Append a line to the text buffer. */
350  | void
351  | append_text_buffer(tb, str, concat)
352  |     struct text_buffer *tb;
353  |     const char *str;
354  |     boolean concat;
355  | {
356  |     char *copy;
357  |     int length;
358  | 
359  |     if (!tb->text) panic("append_text_buffer:  null text buffer");
360  | 
361  |     if (str) {
362  | 	length = strlen(str);
363  |     } else {
364  | 	length = 0;
365  |     }
366  | 
367  |     if (length + tb->text_last + 1 >= tb->text_size) {
368  | 	/* we need to go to a bigger buffer! */
369  | #ifdef VERBOSE
370  | 	printf("append_text_buffer: text buffer growing from %d to %d bytes\n",
371  | 				tb->text_size, 2*tb->text_size);
372  | #endif
373  | 	copy = (char *) alloc((unsigned)tb->text_size*2);
374  | 	(void) memcpy(copy, tb->text, tb->text_last);
375  | 	free(tb->text);
376  | 	tb->text = copy;
377  | 	tb->text_size *= 2;
378  |     }
379  | 
380  |     if (tb->num_lines) {	/* not first --- append a newline */
381  | 	char appchar = '\n';
382  | 
383  | 	if(concat && !index("!.?'\")", tb->text[tb->text_last-1])) {
384  | 	    appchar = ' ';
385  | 	    tb->num_lines--; /* offset increment at end of function */
386  | 	}
387  | 
388  | 	*(tb->text + tb->text_last) = appchar;
389  | 	tb->text_last++;
390  |     }
391  | 
392  |     if (str) {
393  | 	(void) memcpy((tb->text+tb->text_last), str, length+1);
394  | 	if(length) {
395  | 	    /* Remove all newlines. Otherwise we have a confused line count. */
396  | 	    copy = (tb->text+tb->text_last);
397  | 	    while ((copy = index(copy, '\n')) != (char*)0)
398  | 		*copy = ' ';
399  | 	}
400  | 
401  | 	tb->text_last += length;
402  |     }
403  |     tb->text[tb->text_last] = '\0';
404  |     tb->num_lines++;
405  | }
406  | 
407  | /* Initialize text buffer. */
408  | void
409  | init_text_buffer(tb)
410  |     struct text_buffer *tb;
411  | {
412  |     tb->text	  = (char *) alloc(START_SIZE);
413  |     tb->text[0]   = '\0';
414  |     tb->text_size = START_SIZE;
415  |     tb->text_last = 0;
416  |     tb->num_lines = 0;
417  | }
418  | 
419  | /* Empty the text buffer */
420  | void
421  | clear_text_buffer(tb)
422  |     struct text_buffer *tb;
423  | {
424  |     tb->text_last = 0;
425  |     tb->text[0]   = '\0';
426  |     tb->num_lines = 0;
427  | }
428  | 
429  | /* Free up allocated memory. */
430  | void
431  | free_text_buffer(tb)
432  |     struct text_buffer *tb;
433  | {
434  |     free(tb->text);
435  |     tb->text = (char *) 0;
436  |     tb->text_size = 0;
437  |     tb->text_last = 0;
438  |     tb->num_lines = 0;
439  | }
440  | 
441  | 
442  | #ifdef GRAPHIC_TOMBSTONE
443  | 
444  | static void FDECL(rip_exposed, (Widget,XtPointer,XtPointer));
445  | 
446  | static XImage* rip_image=0;
447  | 
448  | 
449  | #define STONE_LINE_LEN 16	/* # chars that fit on one line */
450  | #define NAME_LINE 0		/* line # for player name */
451  | #define GOLD_LINE 1		/* line # for amount of gold */
452  | #define DEATH_LINE 2		/* line # for death description */
453  | #define YEAR_LINE 6		/* line # for year */
454  | 
455  | static char rip_line[YEAR_LINE+1][STONE_LINE_LEN+1];
456  | 
457  | extern const char *killed_by_prefix[];
458  | 
459  | void
460  | calculate_rip_text(int how)
461  | {
462  | 	/* Follows same algorithm as genl_outrip() */
463  | 
464  | 	char buf[BUFSZ];
465  | 	char *dpx;
466  | 	int line;
467  | 
468  | 	/* Put name on stone */
469  | 	Sprintf(rip_line[NAME_LINE], "%s", plname);
470  | 
471  | 	/* Put $ on stone */
472  | 	Sprintf(rip_line[GOLD_LINE], "%ld Au", u.ugold);
473  | 
474  | 	/* Put together death description */
475  | 	switch (killer_format) {
476  | 		default: impossible("bad killer format?");
477  | 		case KILLED_BY_AN:
478  | 			Strcpy(buf, killed_by_prefix[how]);
479  | 			Strcat(buf, an(killer));
480  | 			break;
481  | 		case KILLED_BY:
482  | 			Strcpy(buf, killed_by_prefix[how]);
483  | 			Strcat(buf, killer);
484  | 			break;
485  | 		case NO_KILLER_PREFIX:
486  | 			Strcpy(buf, killer);
487  | 			break;
488  | 	}
489  | 
490  | 	/* Put death type on stone */
491  | 	for (line=DEATH_LINE, dpx = buf; line<YEAR_LINE; line++) {
492  | 		register int i,i0;
493  | 		char tmpchar;
494  | 
495  | 		if ( (i0=strlen(dpx)) > STONE_LINE_LEN) {
496  | 			for(i = STONE_LINE_LEN;
497  | 			    ((i0 > STONE_LINE_LEN) && i); i--)
498  | 				if(dpx[i] == ' ') i0 = i;
499  | 			if(!i) i0 = STONE_LINE_LEN;
500  | 		}
501  | 		tmpchar = dpx[i0];
502  | 		dpx[i0] = 0;
503  | 		strcpy(rip_line[line], dpx);
504  | 		if (tmpchar != ' ') {
505  | 			dpx[i0] = tmpchar;
506  | 			dpx= &dpx[i0];
507  | 		} else  dpx= &dpx[i0+1];
508  | 	}
509  | 
510  | 	/* Put year on stone */
511  | 	Sprintf(rip_line[YEAR_LINE], "%4d", getyear());
512  | }
513  | 
514  | 
515  | /*
516  |  * RIP image expose callback.
517  |  */
518  | /*ARGSUSED*/
519  | static void
520  | rip_exposed(w, client_data, widget_data)
521  |     Widget w;
522  |     XtPointer client_data;	/* unused */
523  |     XtPointer widget_data;	/* expose event from Window widget */
524  | {
525  |     XExposeEvent *event = (XExposeEvent *) widget_data;
526  |     Display* dpy=XtDisplay(w);
527  |     Arg args[8];
528  |     XGCValues values;
529  |     XtGCMask mask;
530  |     GC gc;
531  |     static Pixmap rip_pixmap=None;
532  |     int i, x, y;
533  | 
534  |     if (!XtIsRealized(w) || event->count > 0) return;
535  | 
536  |     if (rip_pixmap == None && rip_image) {
537  | 	rip_pixmap = XCreatePixmap(dpy, XtWindow(w),
538  | 			rip_image->width,
539  | 			rip_image->height,
540  | 			DefaultDepth(dpy, DefaultScreen(dpy)));
541  | 	XPutImage(dpy, rip_pixmap,
542  | 		DefaultGC(dpy, DefaultScreen(dpy)),
543  | 		rip_image,
544  | 		0,0, 0,0,		/* src, dest top left */
545  | 		rip_image->width,
546  | 		rip_image->height);
547  | 	XDestroyImage(rip_image);	/* data bytes free'd also */
548  |     }
549  | 
550  |     mask = GCFunction | GCForeground | GCGraphicsExposures | GCFont;
551  |     values.graphics_exposures = False;
552  |     XtSetArg(args[0], XtNforeground, &values.foreground);
553  |     XtGetValues(w, args, 1);
554  |     values.function = GXcopy;
555  |     values.font = WindowFont(w);
556  |     gc = XtGetGC(w, mask, &values);
557  | 
558  |     if (rip_pixmap != None) {
559  | 	XCopyArea(dpy, rip_pixmap, XtWindow(w), gc,
560  | 		event->x, event->y, event->width, event->height,
561  | 		event->x, event->y);
562  |     }
563  | 
564  |     x=appResources.tombtext_x;
565  |     y=appResources.tombtext_y;
566  |     for (i=0; i<=YEAR_LINE; i++) {
567  | 	int len=strlen(rip_line[i]);
568  | 	XFontStruct* font=WindowFontStruct(w);
569  | 	int width=XTextWidth(font, rip_line[i], len);
570  | 	XDrawString(dpy, XtWindow(w), gc,
571  | 		x-width/2, y, rip_line[i], len);
572  | 	x+=appResources.tombtext_dx;
573  | 	y+=appResources.tombtext_dy;
574  |     }
575  | 
576  |     XtReleaseGC(w, gc);
577  | }
578  | 
579  | /*
580  |  * The ripout window creation routine.
581  |  */
582  | static Widget
583  | create_ripout_widget(Widget parent)
584  | {
585  |     Widget imageport;
586  |     Arg args[16];
587  |     Cardinal num_args;
588  | 
589  |     static int rip_width, rip_height;
590  | 
591  |     if (!rip_image) {
592  | 	XpmAttributes attributes;
593  | 	int errorcode;
594  | 
595  | 	attributes.valuemask = XpmCloseness;
596  | 	attributes.closeness = 65535; /* Try anything */
597  | 	errorcode = XpmReadFileToImage(XtDisplay(parent), appResources.tombstone, &rip_image, 0, &attributes);
598  | 	if (errorcode != XpmSuccess) {
599  | 	    char buf[BUFSZ];
600  | 	    Sprintf(buf, "Failed to load %s: %s", appResources.tombstone,
601  | 			XpmGetErrorString(errorcode));
602  | 	    X11_raw_print(buf);
603  | 	}
604  | 	rip_width = rip_image->width;
605  | 	rip_height = rip_image->height;
606  |     }
607  | 
608  |     num_args = 0;
609  |     XtSetArg(args[num_args], XtNwidth, rip_width); num_args++;
610  |     XtSetArg(args[num_args], XtNheight, rip_height); num_args++;
611  |     XtSetArg(args[num_args], XtNtranslations,
612  | 		XtParseTranslationTable(rip_translations));	num_args++;
613  | 
614  |     imageport = XtCreateManagedWidget("rip", windowWidgetClass,
615  | 		parent, args, num_args);
616  | 
617  |     XtAddCallback(imageport, XtNexposeCallback, rip_exposed, (XtPointer) 0);
618  | 
619  |     return imageport;
620  | }
621  | 
622  | #endif /* GRAPHIC_TOMBSTONE */
623  | 
624  | /*wintext.c*/