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