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*/