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
set_message_slider(wp)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
create_message_window(wp,create_popup,parent)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
destroy_message_window(wp)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
display_message_window(wp)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
append_message(wp,str)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 *
get_previous(mark)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
set_circle_buf(mesg_info,count)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 *
split(s,fs,pixel_width)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
add_line(mesg_info,s)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
set_last_pause(wp)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
redraw_message_window(wp)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
mesg_check_size_change(wp)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
mesg_exposed(w,client_data,widget_data)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
get_gc(w,mesg_info)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
mesg_resized(w,client_data,call_data)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*/
626