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*/