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