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