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
delete_text(w,event,params,num_params)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
dismiss_text(w,event,params,num_params)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
key_dismiss_text(w,event,params,num_params)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
rip_dismiss_text(w,event,params,num_params)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
add_to_text_window(wp,attr,str)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
display_text_window(wp,blocking)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
create_text_window(wp)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
destroy_text_window(wp)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
clear_text_window(wp)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
append_text_buffer(tb,str,concat)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
init_text_buffer(tb)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
clear_text_buffer(tb)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
free_text_buffer(tb)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
calculate_rip_text(int how)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
rip_exposed(w,client_data,widget_data)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
create_ripout_widget(Widget parent)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*/
625