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