1 /* NetHack 3.7 winmesg.c $NHDT-Date: 1596498373 2020/08/03 23:46:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.12 $ */
2 /* Copyright (c) Dean Luick, 1992 */
3 /* NetHack may be freely redistributed. See license for details. */
4
5 /*
6 * Message window routines.
7 *
8 * Global functions:
9 * create_message_window()
10 * destroy_message_window()
11 * display_message_window()
12 * append_message()
13 */
14
15 #ifndef SYSV
16 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
17 #endif
18
19 #include <X11/Intrinsic.h>
20 #include <X11/StringDefs.h>
21 #include <X11/Shell.h>
22 #include <X11/Xaw/Cardinals.h>
23 #include <X11/Xaw/Viewport.h>
24 #include <X11/Xatom.h>
25
26 #ifdef PRESERVE_NO_SYSV
27 #ifdef SYSV
28 #undef SYSV
29 #endif
30 #undef PRESERVE_NO_SYSV
31 #endif
32
33 #include "xwindow.h" /* Window widget declarations */
34
35 #include "hack.h"
36 #include "winX.h"
37
38 static struct line_element *get_previous(struct line_element *);
39 static void set_circle_buf(struct mesg_info_t *, int);
40 static char *split(char *, XFontStruct *, Dimension);
41 static void add_line(struct mesg_info_t *, const char *);
42 static void redraw_message_window(struct xwindow *);
43 static void mesg_check_size_change(struct xwindow *);
44 static void mesg_exposed(Widget, XtPointer, XtPointer);
45 static void get_gc(Widget, struct mesg_info_t *);
46 static void mesg_resized(Widget, XtPointer, XtPointer);
47
48 static char mesg_translations[] = "#override\n\
49 <Key>Left: scroll(4)\n\
50 <Key>Right: scroll(6)\n\
51 <Key>Up: scroll(8)\n\
52 <Key>Down: scroll(2)\n\
53 <Btn4Down>: scroll(8)\n\
54 <Btn5Down>: scroll(2)\n\
55 <Key>: input()";
56
57 /* Move the message window's vertical scrollbar's slider to the bottom. */
58 void
set_message_slider(struct xwindow * wp)59 set_message_slider(struct xwindow *wp)
60 {
61 Widget scrollbar;
62 float top;
63
64 scrollbar = XtNameToWidget(XtParent(wp->w), "vertical");
65
66 if (scrollbar) {
67 top = 1.0;
68 XtCallCallbacks(scrollbar, XtNjumpProc, &top);
69 }
70 }
71
72 void
create_message_window(struct xwindow * wp,boolean create_popup,Widget parent)73 create_message_window(struct xwindow *wp, /* window pointer */
74 boolean create_popup, Widget parent)
75 {
76 Arg args[8];
77 Cardinal num_args;
78 Widget viewport;
79 struct mesg_info_t *mesg_info;
80
81 wp->type = NHW_MESSAGE;
82
83 wp->mesg_information = mesg_info =
84 (struct mesg_info_t *) alloc(sizeof (struct mesg_info_t));
85
86 mesg_info->fs = 0;
87 mesg_info->num_lines = 0;
88 mesg_info->head = mesg_info->line_here = mesg_info->last_pause =
89 mesg_info->last_pause_head = (struct line_element *) 0;
90 mesg_info->dirty = False;
91 mesg_info->viewport_width = mesg_info->viewport_height = 0;
92
93 if (iflags.msg_history < (unsigned) appResources.message_lines)
94 iflags.msg_history = (unsigned) appResources.message_lines;
95 if (iflags.msg_history > MAX_HISTORY) /* a sanity check */
96 iflags.msg_history = MAX_HISTORY;
97
98 set_circle_buf(mesg_info, (int) iflags.msg_history);
99
100 /* Create a popup that becomes the parent. */
101 if (create_popup) {
102 num_args = 0;
103 XtSetArg(args[num_args], XtNallowShellResize, True);
104 num_args++;
105
106 wp->popup = parent =
107 XtCreatePopupShell("message_popup", topLevelShellWidgetClass,
108 toplevel, args, num_args);
109 /*
110 * If we're here, then this is an auxiliary message window. If we're
111 * cancelled via a delete window message, we should just pop down.
112 */
113 }
114
115 /*
116 * Create the viewport. We only want the vertical scroll bar ever to be
117 * visible. If we allow the horizontal scrollbar to be visible it will
118 * always be visible, due to the stupid way the Athena viewport operates.
119 */
120 num_args = 0;
121 XtSetArg(args[num_args], XtNallowVert, True);
122 num_args++;
123 viewport = XtCreateManagedWidget(
124 "mesg_viewport", /* name */
125 viewportWidgetClass, /* widget class from Window.h */
126 parent, /* parent widget */
127 args, /* set some values */
128 num_args); /* number of values to set */
129
130 /*
131 * Create a message window. We will change the width and height once
132 * we know what font we are using.
133 */
134 num_args = 0;
135 if (!create_popup) {
136 XtSetArg(args[num_args], XtNtranslations,
137 XtParseTranslationTable(mesg_translations));
138 num_args++;
139 }
140 wp->w = XtCreateManagedWidget(
141 "message", /* name */
142 windowWidgetClass, /* widget class from Window.h */
143 viewport, /* parent widget */
144 args, /* set some values */
145 num_args); /* number of values to set */
146
147 XtAddCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer) 0);
148
149 /*
150 * Now adjust the height and width of the message window so that it
151 * is appResources.message_lines high and DEFAULT_MESSAGE_WIDTH wide.
152 */
153
154 /* Get the font information. */
155 num_args = 0;
156 XtSetArg(args[num_args], XtNfont, &mesg_info->fs);
157 num_args++;
158 XtGetValues(wp->w, args, num_args);
159
160 /* Save character information for fast use later. */
161 mesg_info->char_width = mesg_info->fs->max_bounds.width;
162 mesg_info->char_height =
163 mesg_info->fs->max_bounds.ascent + mesg_info->fs->max_bounds.descent;
164 mesg_info->char_ascent = mesg_info->fs->max_bounds.ascent;
165 mesg_info->char_lbearing = -mesg_info->fs->min_bounds.lbearing;
166
167 get_gc(wp->w, mesg_info);
168
169 wp->pixel_height = ((int) iflags.msg_history) * mesg_info->char_height;
170
171 /* If a variable spaced font, only use 2/3 of the default size */
172 if (mesg_info->fs->min_bounds.width != mesg_info->fs->max_bounds.width) {
173 wp->pixel_width = ((2 * DEFAULT_MESSAGE_WIDTH) / 3)
174 * mesg_info->fs->max_bounds.width;
175 } else
176 wp->pixel_width =
177 (DEFAULT_MESSAGE_WIDTH * mesg_info->fs->max_bounds.width);
178
179 /* Set the new width and height. */
180 num_args = 0;
181 XtSetArg(args[num_args], XtNwidth, wp->pixel_width);
182 num_args++;
183 XtSetArg(args[num_args], XtNheight, wp->pixel_height);
184 num_args++;
185 XtSetValues(wp->w, args, num_args);
186
187 /* make sure viewport height makes sense before realizing it */
188 num_args = 0;
189 mesg_info->viewport_height =
190 appResources.message_lines * mesg_info->char_height;
191 XtSetArg(args[num_args], XtNheight, mesg_info->viewport_height);
192 num_args++;
193 XtSetValues(viewport, args, num_args);
194
195 XtAddCallback(wp->w, XtNresizeCallback, mesg_resized, (XtPointer) 0);
196
197 /*
198 * If we have created our own popup, then realize it so that the
199 * viewport is also realized.
200 */
201 if (create_popup) {
202 XtRealizeWidget(wp->popup);
203 XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
204 &wm_delete_window, 1);
205 }
206 }
207
208 void
destroy_message_window(struct xwindow * wp)209 destroy_message_window(struct xwindow *wp)
210 {
211 if (wp->popup) {
212 nh_XtPopdown(wp->popup);
213 if (!wp->keep_window)
214 XtDestroyWidget(wp->popup), wp->popup = (Widget) 0;
215 }
216 if (wp->mesg_information) {
217 set_circle_buf(wp->mesg_information, 0); /* free buffer list */
218 free((genericptr_t) wp->mesg_information), wp->mesg_information = 0;
219 }
220 if (wp->keep_window)
221 XtRemoveCallback(wp->w, XtNexposeCallback, mesg_exposed,
222 (XtPointer) 0);
223 else
224 wp->type = NHW_NONE;
225 }
226
227 /* Redraw message window if new lines have been added. */
228 void
display_message_window(struct xwindow * wp)229 display_message_window(struct xwindow *wp)
230 {
231 set_message_slider(wp);
232 if (wp->mesg_information->dirty)
233 redraw_message_window(wp);
234 }
235
236 /*
237 * Append a line of text to the message window. Split the line if the
238 * rendering of the text is too long for the window.
239 */
240 void
append_message(struct xwindow * wp,const char * str)241 append_message(struct xwindow *wp, const char *str)
242 {
243 char *mark, *remainder, buf[BUFSZ];
244
245 if (!str)
246 return;
247
248 Strcpy(buf, str); /* we might mark it up */
249
250 remainder = buf;
251 do {
252 mark = remainder;
253 remainder = split(mark, wp->mesg_information->fs, wp->pixel_width);
254 add_line(wp->mesg_information, mark);
255 } while (remainder);
256 }
257
258 /* private functions =======================================================
259 */
260
261 /*
262 * Return the element in the circular linked list just before the given
263 * element.
264 */
265 static struct line_element *
get_previous(struct line_element * mark)266 get_previous(struct line_element *mark)
267 {
268 struct line_element *curr;
269
270 if (!mark)
271 return (struct line_element *) 0;
272
273 for (curr = mark; curr->next != mark; curr = curr->next)
274 ;
275 return curr;
276 }
277
278 /*
279 * Set the information buffer size to count lines. We do this by creating
280 * a circular linked list of elements, each of which represents a line of
281 * text. New buffers are created as needed, old ones are freed if they
282 * are no longer used.
283 */
284 static void
set_circle_buf(struct mesg_info_t * mesg_info,int count)285 set_circle_buf(struct mesg_info_t *mesg_info, int count)
286 {
287 int i;
288 struct line_element *tail, *curr, *head;
289
290 if (count < 0)
291 panic("set_circle_buf: bad count [= %d]", count);
292 if (count == mesg_info->num_lines)
293 return; /* no change in size */
294
295 if (count < mesg_info->num_lines) {
296 /*
297 * Toss num_lines - count line entries from our circular list.
298 *
299 * We lose lines from the front (top) of the list. We _know_
300 * the list is non_empty.
301 */
302 tail = get_previous(mesg_info->head);
303 for (i = mesg_info->num_lines - count; i > 0; i--) {
304 curr = mesg_info->head;
305 mesg_info->head = curr->next;
306 if (curr->line)
307 free((genericptr_t) curr->line);
308 free((genericptr_t) curr);
309 }
310 if (count == 0) {
311 /* make sure we don't have a dangling pointer */
312 mesg_info->head = (struct line_element *) 0;
313 } else {
314 tail->next = mesg_info->head; /* link the tail to the head */
315 }
316 } else {
317 /*
318 * Add count - num_lines blank lines to the head of the list.
319 *
320 * Create a separate list, keeping track of the tail.
321 */
322 for (head = tail = 0, i = 0; i < count - mesg_info->num_lines; i++) {
323 curr = (struct line_element *) alloc(sizeof(struct line_element));
324 curr->line = 0;
325 curr->buf_length = 0;
326 curr->str_length = 0;
327 if (tail) {
328 tail->next = curr;
329 tail = curr;
330 } else {
331 head = tail = curr;
332 }
333 }
334 /*
335 * Complete the circle by making the new tail point to the old head
336 * and the old tail point to the new head. If our line count was
337 * zero, then make the new list circular.
338 */
339 if (mesg_info->num_lines) {
340 curr = get_previous(mesg_info->head); /* get end of old list */
341
342 tail->next = mesg_info->head; /* new tail -> old head */
343 curr->next = head; /* old tail -> new head */
344 } else {
345 tail->next = head;
346 }
347 mesg_info->head = head;
348 }
349
350 mesg_info->num_lines = count;
351 /* Erase the line on a resize. */
352 mesg_info->last_pause = (struct line_element *) 0;
353 }
354
355 /*
356 * Make sure the given string is shorter than the given pixel width. If
357 * not, back up from the end by words until we find a place to split.
358 */
359 static char *
split(char * s,XFontStruct * fs,Dimension pixel_width)360 split(char *s,
361 XFontStruct *fs, /* Font for the window. */
362 Dimension pixel_width)
363 {
364 char save, *end, *remainder;
365
366 save = '\0';
367 remainder = 0;
368 end = eos(s); /* point to null at end of string */
369
370 /* assume that if end == s, XXXXXX returns 0) */
371 while ((Dimension) XTextWidth(fs, s, (int) strlen(s)) > pixel_width) {
372 *end-- = save;
373 while (*end != ' ') {
374 if (end == s)
375 panic("split: eos!");
376 --end;
377 }
378 save = *end;
379 *end = '\0';
380 remainder = end + 1;
381 }
382 return remainder;
383 }
384
385 /*
386 * Add a line of text to the window. The first line in the curcular list
387 * becomes the last. So all we have to do is copy the new line over the
388 * old one. If the line buffer is too small, then allocate a new, larger
389 * one.
390 */
391 static void
add_line(struct mesg_info_t * mesg_info,const char * s)392 add_line(struct mesg_info_t *mesg_info, const char *s)
393 {
394 register struct line_element *curr = mesg_info->head;
395 register int new_line_length = strlen(s);
396
397 if (new_line_length + 1 > curr->buf_length) {
398 if (curr->line)
399 free(curr->line); /* free old line */
400
401 curr->buf_length = new_line_length + 1;
402 curr->line = (char *) alloc((unsigned) curr->buf_length);
403 }
404
405 Strcpy(curr->line, s); /* copy info */
406 curr->str_length = new_line_length; /* save string length */
407
408 mesg_info->head = mesg_info->head->next; /* move head to next line */
409 mesg_info->dirty = True; /* we have undrawn lines */
410 }
411
412 /*
413 * Save a position in the text buffer so we can draw a line to seperate
414 * text from the last time this function was called.
415 *
416 * Save the head position, since it is the line "after" the last displayed
417 * line in the message window. The window redraw routine will draw a
418 * line above this saved pointer.
419 */
420 void
set_last_pause(struct xwindow * wp)421 set_last_pause(struct xwindow *wp)
422 {
423 register struct mesg_info_t *mesg_info = wp->mesg_information;
424
425 #ifdef ERASE_LINE
426 /*
427 * If we've erased the pause line and haven't added any new lines,
428 * don't try to erase the line again.
429 */
430 if (!mesg_info->last_pause
431 && mesg_info->last_pause_head == mesg_info->head)
432 return;
433
434 if (mesg_info->last_pause == mesg_info->head) {
435 /* No new messages in last turn. Redraw window to erase line. */
436 mesg_info->last_pause = (struct line_element *) 0;
437 mesg_info->last_pause_head = mesg_info->head;
438 redraw_message_window(wp);
439 } else {
440 #endif
441 mesg_info->last_pause = mesg_info->head;
442 #ifdef ERASE_LINE
443 }
444 #endif
445 }
446
447 static void
redraw_message_window(struct xwindow * wp)448 redraw_message_window(struct xwindow *wp)
449 {
450 struct mesg_info_t *mesg_info = wp->mesg_information;
451 register struct line_element *curr;
452 register int row, y_base;
453
454 /*
455 * Do this the cheap and easy way. Clear the window and just redraw
456 * the whole thing.
457 *
458 * This could be done more effecently with one call to XDrawText() instead
459 * of many calls to XDrawString(). Maybe later.
460 *
461 * Only need to clear if window has new text.
462 */
463 if (mesg_info->dirty) {
464 XClearWindow(XtDisplay(wp->w), XtWindow(wp->w));
465 mesg_info->line_here = mesg_info->last_pause;
466 }
467
468 /* For now, just update the whole shootn' match. */
469 for (y_base = row = 0, curr = mesg_info->head; row < mesg_info->num_lines;
470 row++, y_base += mesg_info->char_height, curr = curr->next) {
471 XDrawString(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc,
472 mesg_info->char_lbearing, mesg_info->char_ascent + y_base,
473 curr->line, curr->str_length);
474 /*
475 * This draws a line at the _top_ of the line of text pointed to by
476 * mesg_info->last_pause.
477 */
478 if (appResources.message_line && curr == mesg_info->line_here) {
479 XDrawLine(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc, 0,
480 y_base, wp->pixel_width, y_base);
481 }
482 }
483
484 mesg_info->dirty = False;
485 }
486
487 /*
488 * Check the size of the viewport. If it has shrunk, then we want to
489 * move the vertical slider to the bottom.
490 */
491 static void
mesg_check_size_change(struct xwindow * wp)492 mesg_check_size_change(struct xwindow *wp)
493 {
494 struct mesg_info_t *mesg_info = wp->mesg_information;
495 Arg arg[2];
496 Dimension new_width, new_height;
497 Widget viewport;
498
499 viewport = XtParent(wp->w);
500
501 XtSetArg(arg[0], XtNwidth, &new_width);
502 XtSetArg(arg[1], XtNheight, &new_height);
503 XtGetValues(viewport, arg, TWO);
504
505 /* Only move slider to bottom if new size is smaller. */
506 if (new_width < mesg_info->viewport_width
507 || new_height < mesg_info->viewport_height) {
508 set_message_slider(wp);
509 }
510
511 mesg_info->viewport_width = new_width;
512 mesg_info->viewport_height = new_height;
513 }
514
515 /* Event handler for message window expose events. */
516 /*ARGSUSED*/
517 static void
mesg_exposed(Widget w,XtPointer client_data,XtPointer widget_data)518 mesg_exposed(Widget w,
519 XtPointer client_data, /* unused */
520 XtPointer widget_data) /* expose event from Window widget */
521 {
522 XExposeEvent *event = (XExposeEvent *) widget_data;
523
524 nhUse(client_data);
525
526 if (XtIsRealized(w) && event->count == 0) {
527 struct xwindow *wp;
528 Display *dpy;
529 Window win;
530 XEvent evt;
531
532 /*
533 * Drain all pending expose events for the message window;
534 * we'll redraw the whole thing at once.
535 */
536 dpy = XtDisplay(w);
537 win = XtWindow(w);
538 while (XCheckTypedWindowEvent(dpy, win, Expose, &evt))
539 continue;
540
541 wp = find_widget(w);
542 if (wp->keep_window && !wp->mesg_information)
543 return;
544 mesg_check_size_change(wp);
545 redraw_message_window(wp);
546 }
547 }
548
549 static void
get_gc(Widget w,struct mesg_info_t * mesg_info)550 get_gc(Widget w, struct mesg_info_t *mesg_info)
551 {
552 XGCValues values;
553 XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont;
554 Pixel fgpixel, bgpixel;
555 Arg arg[2];
556
557 XtSetArg(arg[0], XtNforeground, &fgpixel);
558 XtSetArg(arg[1], XtNbackground, &bgpixel);
559 XtGetValues(w, arg, TWO);
560
561 values.foreground = fgpixel;
562 values.background = bgpixel;
563 values.function = GXcopy;
564 values.font = WindowFont(w);
565 mesg_info->gc = XtGetGC(w, mask, &values);
566 }
567
568 /*
569 * Handle resizes on a message window. Correct saved pixel height and width.
570 * Adjust circle buffer to accomidate the new size.
571 *
572 * Problem: If the resize decreases the width of the window such that
573 * some lines are now longer than the window, they will be cut off by
574 * X itself. All new lines will be split to the new size, but the ends
575 * of the old ones will not be seen again unless the window is lengthened.
576 * I don't deal with this problem because it isn't worth the trouble.
577 */
578 /* ARGSUSED */
579 static void
mesg_resized(Widget w,XtPointer call_data,XtPointer client_data)580 mesg_resized(Widget w, XtPointer call_data, XtPointer client_data)
581 {
582 Arg args[4];
583 Cardinal num_args;
584 Dimension pixel_width, pixel_height;
585 struct xwindow *wp;
586 #ifdef VERBOSE
587 int old_lines;
588
589 old_lines = wp->mesg_information->num_lines;
590 ;
591 #endif
592
593 nhUse(call_data);
594 nhUse(client_data);
595
596 num_args = 0;
597 XtSetArg(args[num_args], XtNwidth, &pixel_width);
598 num_args++;
599 XtSetArg(args[num_args], XtNheight, &pixel_height);
600 num_args++;
601 XtGetValues(w, args, num_args);
602
603 wp = find_widget(w);
604 wp->pixel_width = pixel_width;
605 wp->pixel_height = pixel_height;
606
607 set_circle_buf(wp->mesg_information,
608 (int) pixel_height / wp->mesg_information->char_height);
609
610 #ifdef VERBOSE
611 printf("Message resize. Pixel: width = %d, height = %d; Lines: old = "
612 "%d, new = %d\n",
613 pixel_width, pixel_height, old_lines,
614 wp->mesg_information->num_lines);
615 #endif
616 }
617
618 /*winmesg.c*/
619