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