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