Document explicitly what m-prefix does to each command
[aNetHack.git] / win / X11 / winmesg.c
blob5a019ee2c865680bd2049770a3e5da8616b48de7
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. */
5 /*
6 * Message window routines.
8 * Global functions:
9 * create_message_window()
10 * destroy_message_window()
11 * display_message_window()
12 * append_message()
15 #ifndef SYSV
16 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
17 #endif
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>
26 #ifdef PRESERVE_NO_SYSV
27 #ifdef SYSV
28 #undef SYSV
29 #endif
30 #undef PRESERVE_NO_SYSV
31 #endif
33 #include "xwindow.h" /* Window widget declarations */
35 #include "hack.h"
36 #include "winX.h"
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));
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 <Key>: input()";
55 /* Move the message window's vertical scrollbar's slider to the bottom. */
56 void
57 set_message_slider(wp)
58 struct xwindow *wp;
60 Widget scrollbar;
61 float top;
63 scrollbar = XtNameToWidget(XtParent(wp->w), "vertical");
65 if (scrollbar) {
66 top = 1.0;
67 XtCallCallbacks(scrollbar, XtNjumpProc, &top);
71 void
72 create_message_window(wp, create_popup, parent)
73 struct xwindow *wp; /* window pointer */
74 boolean create_popup;
75 Widget parent;
77 Arg args[8];
78 Cardinal num_args;
79 Widget viewport;
80 struct mesg_info_t *mesg_info;
82 wp->type = NHW_MESSAGE;
84 wp->mesg_information = mesg_info =
85 (struct mesg_info_t *) alloc(sizeof (struct mesg_info_t));
87 mesg_info->fs = 0;
88 mesg_info->num_lines = 0;
89 mesg_info->head = mesg_info->line_here = mesg_info->last_pause =
90 mesg_info->last_pause_head = (struct line_element *) 0;
91 mesg_info->dirty = False;
92 mesg_info->viewport_width = mesg_info->viewport_height = 0;
94 if (iflags.msg_history < (unsigned) appResources.message_lines)
95 iflags.msg_history = (unsigned) appResources.message_lines;
96 if (iflags.msg_history > MAX_HISTORY) /* a sanity check */
97 iflags.msg_history = MAX_HISTORY;
99 set_circle_buf(mesg_info, (int) iflags.msg_history);
101 /* Create a popup that becomes the parent. */
102 if (create_popup) {
103 num_args = 0;
104 XtSetArg(args[num_args], XtNallowShellResize, True);
105 num_args++;
107 wp->popup = parent =
108 XtCreatePopupShell("message_popup", topLevelShellWidgetClass,
109 toplevel, args, num_args);
111 * If we're here, then this is an auxiliary message window. If we're
112 * cancelled via a delete window message, we should just pop down.
117 * Create the viewport. We only want the vertical scroll bar ever to be
118 * visible. If we allow the horizontal scrollbar to be visible it will
119 * always be visible, due to the stupid way the Athena viewport operates.
121 num_args = 0;
122 XtSetArg(args[num_args], XtNallowVert, True);
123 num_args++;
124 viewport = XtCreateManagedWidget(
125 "mesg_viewport", /* name */
126 viewportWidgetClass, /* widget class from Window.h */
127 parent, /* parent widget */
128 args, /* set some values */
129 num_args); /* number of values to set */
132 * Create a message window. We will change the width and height once
133 * we know what font we are using.
135 num_args = 0;
136 if (!create_popup) {
137 XtSetArg(args[num_args], XtNtranslations,
138 XtParseTranslationTable(mesg_translations));
139 num_args++;
141 wp->w = XtCreateManagedWidget(
142 "message", /* name */
143 windowWidgetClass, /* widget class from Window.h */
144 viewport, /* parent widget */
145 args, /* set some values */
146 num_args); /* number of values to set */
148 XtAddCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer) 0);
151 * Now adjust the height and width of the message window so that it
152 * is appResources.message_lines high and DEFAULT_MESSAGE_WIDTH wide.
155 /* Get the font information. */
156 num_args = 0;
157 XtSetArg(args[num_args], XtNfont, &mesg_info->fs);
158 num_args++;
159 XtGetValues(wp->w, args, num_args);
161 /* Save character information for fast use later. */
162 mesg_info->char_width = mesg_info->fs->max_bounds.width;
163 mesg_info->char_height =
164 mesg_info->fs->max_bounds.ascent + mesg_info->fs->max_bounds.descent;
165 mesg_info->char_ascent = mesg_info->fs->max_bounds.ascent;
166 mesg_info->char_lbearing = -mesg_info->fs->min_bounds.lbearing;
168 get_gc(wp->w, mesg_info);
170 wp->pixel_height = ((int) iflags.msg_history) * mesg_info->char_height;
172 /* If a variable spaced font, only use 2/3 of the default size */
173 if (mesg_info->fs->min_bounds.width != mesg_info->fs->max_bounds.width) {
174 wp->pixel_width = ((2 * DEFAULT_MESSAGE_WIDTH) / 3)
175 * mesg_info->fs->max_bounds.width;
176 } else
177 wp->pixel_width =
178 (DEFAULT_MESSAGE_WIDTH * mesg_info->fs->max_bounds.width);
180 /* Set the new width and height. */
181 num_args = 0;
182 XtSetArg(args[num_args], XtNwidth, wp->pixel_width);
183 num_args++;
184 XtSetArg(args[num_args], XtNheight, wp->pixel_height);
185 num_args++;
186 XtSetValues(wp->w, args, num_args);
188 /* make sure viewport height makes sense before realizing it */
189 num_args = 0;
190 mesg_info->viewport_height =
191 appResources.message_lines * mesg_info->char_height;
192 XtSetArg(args[num_args], XtNheight, mesg_info->viewport_height);
193 num_args++;
194 XtSetValues(viewport, args, num_args);
196 XtAddCallback(wp->w, XtNresizeCallback, mesg_resized, (XtPointer) 0);
199 * If we have created our own popup, then realize it so that the
200 * viewport is also realized.
202 if (create_popup) {
203 XtRealizeWidget(wp->popup);
204 XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
205 &wm_delete_window, 1);
209 void
210 destroy_message_window(wp)
211 struct xwindow *wp;
213 if (wp->popup) {
214 nh_XtPopdown(wp->popup);
215 if (!wp->keep_window)
216 XtDestroyWidget(wp->popup), wp->popup = (Widget) 0;
218 if (wp->mesg_information) {
219 set_circle_buf(wp->mesg_information, 0); /* free buffer list */
220 free((genericptr_t) wp->mesg_information), wp->mesg_information = 0;
222 if (wp->keep_window)
223 XtRemoveCallback(wp->w, XtNexposeCallback, mesg_exposed,
224 (XtPointer) 0);
225 else
226 wp->type = NHW_NONE;
229 /* Redraw message window if new lines have been added. */
230 void
231 display_message_window(wp)
232 struct xwindow *wp;
234 set_message_slider(wp);
235 if (wp->mesg_information->dirty)
236 redraw_message_window(wp);
240 * Append a line of text to the message window. Split the line if the
241 * rendering of the text is too long for the window.
243 void
244 append_message(wp, str)
245 struct xwindow *wp;
246 const char *str;
248 char *mark, *remainder, buf[BUFSZ];
250 if (!str)
251 return;
253 Strcpy(buf, str); /* we might mark it up */
255 remainder = buf;
256 do {
257 mark = remainder;
258 remainder = split(mark, wp->mesg_information->fs, wp->pixel_width);
259 add_line(wp->mesg_information, mark);
260 } while (remainder);
263 /* private functions =======================================================
267 * Return the element in the circular linked list just before the given
268 * element.
270 static struct line_element *
271 get_previous(mark)
272 struct line_element *mark;
274 struct line_element *curr;
276 if (!mark)
277 return (struct line_element *) 0;
279 for (curr = mark; curr->next != mark; curr = curr->next)
281 return curr;
285 * Set the information buffer size to count lines. We do this by creating
286 * a circular linked list of elements, each of which represents a line of
287 * text. New buffers are created as needed, old ones are freed if they
288 * are no longer used.
290 static void
291 set_circle_buf(mesg_info, count)
292 struct mesg_info_t *mesg_info;
293 int count;
295 int i;
296 struct line_element *tail, *curr, *head;
298 if (count < 0)
299 panic("set_circle_buf: bad count [= %d]", count);
300 if (count == mesg_info->num_lines)
301 return; /* no change in size */
303 if (count < mesg_info->num_lines) {
305 * Toss num_lines - count line entries from our circular list.
307 * We lose lines from the front (top) of the list. We _know_
308 * the list is non_empty.
310 tail = get_previous(mesg_info->head);
311 for (i = mesg_info->num_lines - count; i > 0; i--) {
312 curr = mesg_info->head;
313 mesg_info->head = curr->next;
314 if (curr->line)
315 free((genericptr_t) curr->line);
316 free((genericptr_t) curr);
318 if (count == 0) {
319 /* make sure we don't have a dangling pointer */
320 mesg_info->head = (struct line_element *) 0;
321 } else {
322 tail->next = mesg_info->head; /* link the tail to the head */
324 } else {
326 * Add count - num_lines blank lines to the head of the list.
328 * Create a separate list, keeping track of the tail.
330 for (head = tail = 0, i = 0; i < count - mesg_info->num_lines; i++) {
331 curr = (struct line_element *) alloc(sizeof(struct line_element));
332 curr->line = 0;
333 curr->buf_length = 0;
334 curr->str_length = 0;
335 if (tail) {
336 tail->next = curr;
337 tail = curr;
338 } else {
339 head = tail = curr;
343 * Complete the circle by making the new tail point to the old head
344 * and the old tail point to the new head. If our line count was
345 * zero, then make the new list circular.
347 if (mesg_info->num_lines) {
348 curr = get_previous(mesg_info->head); /* get end of old list */
350 tail->next = mesg_info->head; /* new tail -> old head */
351 curr->next = head; /* old tail -> new head */
352 } else {
353 tail->next = head;
355 mesg_info->head = head;
358 mesg_info->num_lines = count;
359 /* Erase the line on a resize. */
360 mesg_info->last_pause = (struct line_element *) 0;
364 * Make sure the given string is shorter than the given pixel width. If
365 * not, back up from the end by words until we find a place to split.
367 static char *
368 split(s, fs, pixel_width)
369 char *s;
370 XFontStruct *fs; /* Font for the window. */
371 Dimension pixel_width;
373 char save, *end, *remainder;
375 save = '\0';
376 remainder = 0;
377 end = eos(s); /* point to null at end of string */
379 /* assume that if end == s, XXXXXX returns 0) */
380 while ((Dimension) XTextWidth(fs, s, (int) strlen(s)) > pixel_width) {
381 *end-- = save;
382 while (*end != ' ') {
383 if (end == s)
384 panic("split: eos!");
385 --end;
387 save = *end;
388 *end = '\0';
389 remainder = end + 1;
391 return remainder;
395 * Add a line of text to the window. The first line in the curcular list
396 * becomes the last. So all we have to do is copy the new line over the
397 * old one. If the line buffer is too small, then allocate a new, larger
398 * one.
400 static void
401 add_line(mesg_info, s)
402 struct mesg_info_t *mesg_info;
403 const char *s;
405 register struct line_element *curr = mesg_info->head;
406 register int new_line_length = strlen(s);
408 if (new_line_length + 1 > curr->buf_length) {
409 if (curr->line)
410 free(curr->line); /* free old line */
412 curr->buf_length = new_line_length + 1;
413 curr->line = (char *) alloc((unsigned) curr->buf_length);
416 Strcpy(curr->line, s); /* copy info */
417 curr->str_length = new_line_length; /* save string length */
419 mesg_info->head = mesg_info->head->next; /* move head to next line */
420 mesg_info->dirty = True; /* we have undrawn lines */
424 * Save a position in the text buffer so we can draw a line to seperate
425 * text from the last time this function was called.
427 * Save the head position, since it is the line "after" the last displayed
428 * line in the message window. The window redraw routine will draw a
429 * line above this saved pointer.
431 void
432 set_last_pause(wp)
433 struct xwindow *wp;
435 register struct mesg_info_t *mesg_info = wp->mesg_information;
437 #ifdef ERASE_LINE
439 * If we've erased the pause line and haven't added any new lines,
440 * don't try to erase the line again.
442 if (!mesg_info->last_pause
443 && mesg_info->last_pause_head == mesg_info->head)
444 return;
446 if (mesg_info->last_pause == mesg_info->head) {
447 /* No new messages in last turn. Redraw window to erase line. */
448 mesg_info->last_pause = (struct line_element *) 0;
449 mesg_info->last_pause_head = mesg_info->head;
450 redraw_message_window(wp);
451 } else {
452 #endif
453 mesg_info->last_pause = mesg_info->head;
454 #ifdef ERASE_LINE
456 #endif
459 static void
460 redraw_message_window(wp)
461 struct xwindow *wp;
463 struct mesg_info_t *mesg_info = wp->mesg_information;
464 register struct line_element *curr;
465 register int row, y_base;
468 * Do this the cheap and easy way. Clear the window and just redraw
469 * the whole thing.
471 * This could be done more effecently with one call to XDrawText() instead
472 * of many calls to XDrawString(). Maybe later.
474 * Only need to clear if window has new text.
476 if (mesg_info->dirty) {
477 XClearWindow(XtDisplay(wp->w), XtWindow(wp->w));
478 mesg_info->line_here = mesg_info->last_pause;
481 /* For now, just update the whole shootn' match. */
482 for (y_base = row = 0, curr = mesg_info->head; row < mesg_info->num_lines;
483 row++, y_base += mesg_info->char_height, curr = curr->next) {
484 XDrawString(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc,
485 mesg_info->char_lbearing, mesg_info->char_ascent + y_base,
486 curr->line, curr->str_length);
488 * This draws a line at the _top_ of the line of text pointed to by
489 * mesg_info->last_pause.
491 if (appResources.message_line && curr == mesg_info->line_here) {
492 XDrawLine(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc, 0,
493 y_base, wp->pixel_width, y_base);
497 mesg_info->dirty = False;
501 * Check the size of the viewport. If it has shrunk, then we want to
502 * move the vertical slider to the bottom.
504 static void
505 mesg_check_size_change(wp)
506 struct xwindow *wp;
508 struct mesg_info_t *mesg_info = wp->mesg_information;
509 Arg arg[2];
510 Dimension new_width, new_height;
511 Widget viewport;
513 viewport = XtParent(wp->w);
515 XtSetArg(arg[0], XtNwidth, &new_width);
516 XtSetArg(arg[1], XtNheight, &new_height);
517 XtGetValues(viewport, arg, TWO);
519 /* Only move slider to bottom if new size is smaller. */
520 if (new_width < mesg_info->viewport_width
521 || new_height < mesg_info->viewport_height) {
522 set_message_slider(wp);
525 mesg_info->viewport_width = new_width;
526 mesg_info->viewport_height = new_height;
529 /* Event handler for message window expose events. */
530 /*ARGSUSED*/
531 static void
532 mesg_exposed(w, client_data, widget_data)
533 Widget w;
534 XtPointer client_data; /* unused */
535 XtPointer widget_data; /* expose event from Window widget */
537 XExposeEvent *event = (XExposeEvent *) widget_data;
539 nhUse(client_data);
541 if (XtIsRealized(w) && event->count == 0) {
542 struct xwindow *wp;
543 Display *dpy;
544 Window win;
545 XEvent evt;
548 * Drain all pending expose events for the message window;
549 * we'll redraw the whole thing at once.
551 dpy = XtDisplay(w);
552 win = XtWindow(w);
553 while (XCheckTypedWindowEvent(dpy, win, Expose, &evt))
554 continue;
556 wp = find_widget(w);
557 if (wp->keep_window && !wp->mesg_information)
558 return;
559 mesg_check_size_change(wp);
560 redraw_message_window(wp);
564 static void
565 get_gc(w, mesg_info)
566 Widget w;
567 struct mesg_info_t *mesg_info;
569 XGCValues values;
570 XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont;
571 Pixel fgpixel, bgpixel;
572 Arg arg[2];
574 XtSetArg(arg[0], XtNforeground, &fgpixel);
575 XtSetArg(arg[1], XtNbackground, &bgpixel);
576 XtGetValues(w, arg, TWO);
578 values.foreground = fgpixel;
579 values.background = bgpixel;
580 values.function = GXcopy;
581 values.font = WindowFont(w);
582 mesg_info->gc = XtGetGC(w, mask, &values);
586 * Handle resizes on a message window. Correct saved pixel height and width.
587 * Adjust circle buffer to accomidate the new size.
589 * Problem: If the resize decreases the width of the window such that
590 * some lines are now longer than the window, they will be cut off by
591 * X itself. All new lines will be split to the new size, but the ends
592 * of the old ones will not be seen again unless the window is lengthened.
593 * I don't deal with this problem because it isn't worth the trouble.
595 /* ARGSUSED */
596 static void
597 mesg_resized(w, client_data, call_data)
598 Widget w;
599 XtPointer call_data, client_data;
601 Arg args[4];
602 Cardinal num_args;
603 Dimension pixel_width, pixel_height;
604 struct xwindow *wp;
605 #ifdef VERBOSE
606 int old_lines;
608 old_lines = wp->mesg_information->num_lines;
610 #endif
612 nhUse(call_data);
613 nhUse(client_data);
615 num_args = 0;
616 XtSetArg(args[num_args], XtNwidth, &pixel_width);
617 num_args++;
618 XtSetArg(args[num_args], XtNheight, &pixel_height);
619 num_args++;
620 XtGetValues(w, args, num_args);
622 wp = find_widget(w);
623 wp->pixel_width = pixel_width;
624 wp->pixel_height = pixel_height;
626 set_circle_buf(wp->mesg_information,
627 (int) pixel_height / wp->mesg_information->char_height);
629 #ifdef VERBOSE
630 printf("Message resize. Pixel: width = %d, height = %d; Lines: old = "
631 "%d, new = %d\n",
632 pixel_width, pixel_height, old_lines,
633 wp->mesg_information->num_lines);
634 #endif
637 /*winmesg.c*/