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