--- /dev/null
+/* SCCS Id: @(#)winmesg.c 3.3 96/04/05 */
+/* Copyright (c) Dean Luick, 1992 */
+/* NetHack may be freely redistributed. See license for details. */
+
+/*
+ * Message window routines.
+ *
+ * Global functions:
+ * create_message_window()
+ * destroy_message_window()
+ * display_message_window()
+ * append_message()
+ */
+
+#ifndef SYSV
+#define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
+#endif
+
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#include <X11/Xaw/Cardinals.h>
+#include <X11/Xaw/Viewport.h>
+#include <X11/Xatom.h>
+
+#ifdef PRESERVE_NO_SYSV
+# ifdef SYSV
+# undef SYSV
+# endif
+# undef PRESERVE_NO_SYSV
+#endif
+
+#include "xwindow.h" /* Window widget declarations */
+
+#include "hack.h"
+#include "winX.h"
+
+static struct line_element *FDECL(get_previous, (struct line_element *));
+static void FDECL(set_circle_buf, (struct mesg_info_t *,int));
+static char *FDECL(split, (char *,XFontStruct *,DIMENSION_P));
+static void FDECL(add_line, (struct mesg_info_t *,const char *));
+static void FDECL(redraw_message_window, (struct xwindow *));
+static void FDECL(mesg_check_size_change, (struct xwindow *));
+static void FDECL(mesg_exposed, (Widget,XtPointer,XtPointer));
+static void FDECL(get_gc, (Widget,struct mesg_info_t *));
+static void FDECL(mesg_resized, (Widget,XtPointer,XtPointer));
+
+static char mesg_translations[] =
+"#override\n\
+ <Key>: input() \
+";
+
+/* Move the message window's vertical scrollbar's slider to the bottom. */
+void
+set_message_slider(wp)
+ struct xwindow *wp;
+{
+ Widget scrollbar;
+ float top;
+
+ scrollbar = XtNameToWidget(XtParent(wp->w), "vertical");
+
+ if (scrollbar) {
+ top = 1.0;
+ XtCallCallbacks(scrollbar, XtNjumpProc, &top);
+ }
+}
+
+
+void
+create_message_window(wp, create_popup, parent)
+ struct xwindow *wp; /* window pointer */
+ boolean create_popup;
+ Widget parent;
+{
+ Arg args[8];
+ Cardinal num_args;
+ Widget viewport;
+ struct mesg_info_t *mesg_info;
+
+ wp->type = NHW_MESSAGE;
+
+ wp->mesg_information = mesg_info =
+ (struct mesg_info_t *) alloc(sizeof(struct mesg_info_t));
+
+ mesg_info->fs = 0;
+ mesg_info->num_lines = 0;
+ mesg_info->head = mesg_info->line_here = mesg_info->last_pause =
+ mesg_info->last_pause_head = (struct line_element *) 0;
+ mesg_info->dirty = False;
+ mesg_info->viewport_width = mesg_info->viewport_height = 0;
+
+ if (iflags.msg_history < appResources.message_lines)
+ iflags.msg_history = appResources.message_lines;
+ if (iflags.msg_history > MAX_HISTORY) /* a sanity check */
+ iflags.msg_history = MAX_HISTORY;
+
+ set_circle_buf(mesg_info, (int) iflags.msg_history);
+
+ /* Create a popup that becomes the parent. */
+ if (create_popup) {
+ num_args = 0;
+ XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
+
+ wp->popup = parent = XtCreatePopupShell("message_popup",
+ topLevelShellWidgetClass,
+ toplevel, args, num_args);
+ /*
+ * If we're here, then this is an auxiliary message window. If we're
+ * cancelled via a delete window message, we should just pop down.
+ */
+ }
+
+ /*
+ * Create the viewport. We only want the vertical scroll bar ever to be
+ * visible. If we allow the horizontal scrollbar to be visible it will
+ * always be visible, due to the stupid way the Athena viewport operates.
+ */
+ num_args = 0;
+ XtSetArg(args[num_args], XtNallowVert, True); num_args++;
+ viewport = XtCreateManagedWidget(
+ "mesg_viewport", /* name */
+ viewportWidgetClass, /* widget class from Window.h */
+ parent, /* parent widget */
+ args, /* set some values */
+ num_args); /* number of values to set */
+
+ /*
+ * Create a message window. We will change the width and height once
+ * we know what font we are using.
+ */
+ num_args = 0;
+ if (!create_popup) {
+ XtSetArg(args[num_args], XtNtranslations,
+ XtParseTranslationTable(mesg_translations)); num_args++;
+ }
+ wp->w = XtCreateManagedWidget(
+ "message", /* name */
+ windowWidgetClass, /* widget class from Window.h */
+ viewport, /* parent widget */
+ args, /* set some values */
+ num_args); /* number of values to set */
+
+ XtAddCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer) 0);
+
+ /*
+ * Now adjust the height and width of the message window so that it
+ * is appResources.message_lines high and DEFAULT_MESSAGE_WIDTH wide.
+ */
+
+ /* Get the font information. */
+ num_args = 0;
+ XtSetArg(args[num_args], XtNfont, &mesg_info->fs); num_args++;
+ XtGetValues(wp->w, args, num_args);
+
+ /* Save character information for fast use later. */
+ mesg_info->char_width = mesg_info->fs->max_bounds.width;
+ mesg_info->char_height = mesg_info->fs->max_bounds.ascent +
+ mesg_info->fs->max_bounds.descent;
+ mesg_info->char_ascent = mesg_info->fs->max_bounds.ascent;
+ mesg_info->char_lbearing = -mesg_info->fs->min_bounds.lbearing;
+
+ get_gc(wp->w, mesg_info);
+
+ wp->pixel_height = ((int)iflags.msg_history) * mesg_info->char_height;
+
+ /* If a variable spaced font, only use 2/3 of the default size */
+ if (mesg_info->fs->min_bounds.width != mesg_info->fs->max_bounds.width) {
+ wp->pixel_width = ((2*DEFAULT_MESSAGE_WIDTH)/3) *
+ mesg_info->fs->max_bounds.width;
+ } else
+ wp->pixel_width = (DEFAULT_MESSAGE_WIDTH *
+ mesg_info->fs->max_bounds.width);
+
+ /* Set the new width and height. */
+ num_args = 0;
+ XtSetArg(args[num_args], XtNwidth, wp->pixel_width); num_args++;
+ XtSetArg(args[num_args], XtNheight, wp->pixel_height); num_args++;
+ XtSetValues(wp->w, args, num_args);
+
+ /* make sure viewport height makes sense before realizing it */
+ num_args = 0;
+ mesg_info->viewport_height =
+ appResources.message_lines * mesg_info->char_height;
+ XtSetArg(args[num_args], XtNheight, mesg_info->viewport_height);num_args++;
+ XtSetValues(viewport, args, num_args);
+
+ XtAddCallback(wp->w, XtNresizeCallback, mesg_resized, (XtPointer) 0);
+
+ /*
+ * If we have created our own popup, then realize it so that the
+ * viewport is also realized.
+ */
+ if (create_popup) {
+ XtRealizeWidget(wp->popup);
+ XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
+ &wm_delete_window, 1);
+ }
+}
+
+
+void
+destroy_message_window(wp)
+ struct xwindow *wp;
+{
+ if (wp->popup) {
+ nh_XtPopdown(wp->popup);
+ if (!wp->keep_window)
+ XtDestroyWidget(wp->popup), wp->popup = (Widget)0;
+ }
+ if (wp->mesg_information) {
+ set_circle_buf(wp->mesg_information, 0); /* free buffer list */
+ free((genericptr_t)wp->mesg_information), wp->mesg_information = 0;
+ }
+ if (wp->keep_window)
+ XtRemoveCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer)0);
+ else
+ wp->type = NHW_NONE;
+}
+
+
+/* Redraw message window if new lines have been added. */
+void
+display_message_window(wp)
+ struct xwindow *wp;
+{
+ if (wp->mesg_information->dirty) redraw_message_window(wp);
+}
+
+
+/*
+ * Append a line of text to the message window. Split the line if the
+ * rendering of the text is too long for the window.
+ */
+void
+append_message(wp, str)
+ struct xwindow *wp;
+ const char *str;
+{
+ char *mark, *remainder, buf[BUFSZ];
+
+ if (!str) return;
+
+ Strcpy(buf, str); /* we might mark it up */
+
+ remainder = buf;
+ do {
+ mark = remainder;
+ remainder = split(mark, wp->mesg_information->fs, wp->pixel_width);
+ add_line(wp->mesg_information, mark);
+ } while (remainder);
+}
+
+/* private functions ======================================================= */
+
+/*
+ * Return the element in the circular linked list just before the given
+ * element.
+ */
+static struct line_element *
+get_previous(mark)
+ struct line_element *mark;
+{
+ struct line_element *curr;
+
+ if (!mark) return (struct line_element *) 0;
+
+ for (curr = mark; curr->next != mark; curr = curr->next)
+ ;
+ return curr;
+}
+
+
+/*
+ * Set the information buffer size to count lines. We do this by creating
+ * a circular linked list of elements, each of which represents a line of
+ * text. New buffers are created as needed, old ones are freed if they
+ * are no longer used.
+ */
+static void
+set_circle_buf(mesg_info, count)
+ struct mesg_info_t *mesg_info;
+ int count;
+{
+ int i;
+ struct line_element *tail, *curr, *head;
+
+ if (count < 0) panic("set_circle_buf: bad count [= %d]", count);
+ if (count == mesg_info->num_lines) return; /* no change in size */
+
+ if (count < mesg_info->num_lines) {
+ /*
+ * Toss num_lines - count line entries from our circular list.
+ *
+ * We lose lines from the front (top) of the list. We _know_
+ * the list is non_empty.
+ */
+ tail = get_previous(mesg_info->head);
+ for (i = mesg_info->num_lines - count; i > 0; i--) {
+ curr = mesg_info->head;
+ mesg_info->head = curr->next;
+ if (curr->line) free((genericptr_t)curr->line);
+ free((genericptr_t)curr);
+ }
+ if (count == 0) {
+ /* make sure we don't have a dangling pointer */
+ mesg_info->head = (struct line_element *) 0;
+ } else {
+ tail->next = mesg_info->head; /* link the tail to the head */
+ }
+ } else {
+ /*
+ * Add count - num_lines blank lines to the head of the list.
+ *
+ * Create a separate list, keeping track of the tail.
+ */
+ for (head = tail = 0, i = 0; i < count - mesg_info->num_lines; i++) {
+ curr = (struct line_element *) alloc(sizeof(struct line_element));
+ curr->line = 0;
+ curr->buf_length = 0;
+ curr->str_length = 0;
+ if (tail) {
+ tail->next = curr;
+ tail = curr;
+ } else {
+ head = tail = curr;
+ }
+ }
+ /*
+ * Complete the circle by making the new tail point to the old head
+ * and the old tail point to the new head. If our line count was
+ * zero, then make the new list circular.
+ */
+ if (mesg_info->num_lines) {
+ curr = get_previous(mesg_info->head);/* get end of old list */
+
+ tail->next = mesg_info->head; /* new tail -> old head */
+ curr->next = head; /* old tail -> new head */
+ } else {
+ tail->next = head;
+ }
+ mesg_info->head = head;
+ }
+
+ mesg_info->num_lines = count;
+ /* Erase the line on a resize. */
+ mesg_info->last_pause = (struct line_element *) 0;
+}
+
+
+/*
+ * Make sure the given string is shorter than the given pixel width. If
+ * not, back up from the end by words until we find a place to split.
+ */
+static char *
+split(s, fs, pixel_width)
+ char *s;
+ XFontStruct *fs; /* Font for the window. */
+ Dimension pixel_width;
+{
+ char save, *end, *remainder;
+
+ save = '\0';
+ remainder = 0;
+ end = eos(s); /* point to null at end of string */
+
+ /* assume that if end == s, XXXXXX returns 0) */
+ while ((Dimension) XTextWidth(fs, s, (int) strlen(s)) > pixel_width) {
+ *end-- = save;
+ while (*end != ' ') {
+ if (end == s) panic("split: eos!");
+ --end;
+ }
+ save = *end;
+ *end = '\0';
+ remainder = end + 1;
+ }
+ return remainder;
+}
+
+/*
+ * Add a line of text to the window. The first line in the curcular list
+ * becomes the last. So all we have to do is copy the new line over the
+ * old one. If the line buffer is too small, then allocate a new, larger
+ * one.
+ */
+static void
+add_line(mesg_info, s)
+ struct mesg_info_t *mesg_info;
+ const char *s;
+{
+ register struct line_element *curr = mesg_info->head;
+ register int new_line_length = strlen(s);
+
+ if (new_line_length + 1 > curr->buf_length) {
+ if (curr->line) free(curr->line); /* free old line */
+
+ curr->buf_length = new_line_length + 1;
+ curr->line = (char *) alloc((unsigned)curr->buf_length);
+ }
+
+ Strcpy(curr->line, s); /* copy info */
+ curr->str_length = new_line_length; /* save string length */
+
+ mesg_info->head = mesg_info->head->next; /* move head to next line */
+ mesg_info->dirty = True; /* we have undrawn lines */
+}
+
+
+/*
+ * Save a position in the text buffer so we can draw a line to seperate
+ * text from the last time this function was called.
+ *
+ * Save the head position, since it is the line "after" the last displayed
+ * line in the message window. The window redraw routine will draw a
+ * line above this saved pointer.
+ */
+void
+set_last_pause(wp)
+ struct xwindow *wp;
+{
+ register struct mesg_info_t *mesg_info = wp->mesg_information;
+
+#ifdef ERASE_LINE
+ /*
+ * If we've erased the pause line and haven't added any new lines,
+ * don't try to erase the line again.
+ */
+ if (!mesg_info->last_pause
+ && mesg_info->last_pause_head == mesg_info->head)
+ return;
+
+ if (mesg_info->last_pause == mesg_info->head) {
+ /* No new messages in last turn. Redraw window to erase line. */
+ mesg_info->last_pause = (struct line_element *) 0;
+ mesg_info->last_pause_head = mesg_info->head;
+ redraw_message_window(wp);
+ } else {
+#endif
+ mesg_info->last_pause = mesg_info->head;
+#ifdef ERASE_LINE
+ }
+#endif
+}
+
+
+static void
+redraw_message_window(wp)
+ struct xwindow *wp;
+{
+ struct mesg_info_t *mesg_info = wp->mesg_information;
+ register struct line_element *curr;
+ register int row, y_base;
+
+ /*
+ * Do this the cheap and easy way. Clear the window and just redraw
+ * the whole thing.
+ *
+ * This could be done more effecently with one call to XDrawText() instead
+ * of many calls to XDrawString(). Maybe later.
+ *
+ * Only need to clear if window has new text.
+ */
+ if (mesg_info->dirty) {
+ XClearWindow(XtDisplay(wp->w), XtWindow(wp->w));
+ mesg_info->line_here = mesg_info->last_pause;
+ }
+
+ /* For now, just update the whole shootn' match. */
+ for (y_base = row = 0, curr = mesg_info->head;
+ row < mesg_info->num_lines;
+ row++, y_base += mesg_info->char_height, curr = curr->next) {
+
+ XDrawString(XtDisplay(wp->w), XtWindow(wp->w),
+ mesg_info->gc,
+ mesg_info->char_lbearing,
+ mesg_info->char_ascent + y_base,
+ curr->line,
+ curr->str_length);
+ /*
+ * This draws a line at the _top_ of the line of text pointed to by
+ * mesg_info->last_pause.
+ */
+ if (appResources.message_line && curr == mesg_info->line_here) {
+ XDrawLine(XtDisplay(wp->w), XtWindow(wp->w),
+ mesg_info->gc,
+ 0, y_base, wp->pixel_width, y_base);
+ }
+ }
+
+ mesg_info->dirty = False;
+}
+
+
+/*
+ * Check the size of the viewport. If it has shrunk, then we want to
+ * move the vertical slider to the bottom.
+ */
+static void
+mesg_check_size_change(wp)
+ struct xwindow *wp;
+{
+ struct mesg_info_t *mesg_info = wp->mesg_information;
+ Arg arg[2];
+ Dimension new_width, new_height;
+ Widget viewport;
+
+ viewport = XtParent(wp->w);
+
+ XtSetArg(arg[0], XtNwidth, &new_width);
+ XtSetArg(arg[1], XtNheight, &new_height);
+ XtGetValues(viewport, arg, TWO);
+
+ /* Only move slider to bottom if new size is smaller. */
+ if (new_width < mesg_info->viewport_width
+ || new_height < mesg_info->viewport_height) {
+ set_message_slider(wp);
+ }
+
+ mesg_info->viewport_width = new_width;
+ mesg_info->viewport_height = new_height;
+}
+
+
+/* Event handler for message window expose events. */
+/*ARGSUSED*/
+static void
+mesg_exposed(w, client_data, widget_data)
+ Widget w;
+ XtPointer client_data; /* unused */
+ XtPointer widget_data; /* expose event from Window widget */
+{
+ XExposeEvent *event = (XExposeEvent *) widget_data;
+
+ if (XtIsRealized(w) && event->count == 0) {
+ struct xwindow *wp;
+ Display *dpy;
+ Window win;
+ XEvent evt;
+
+ /*
+ * Drain all pending expose events for the message window;
+ * we'll redraw the whole thing at once.
+ */
+ dpy = XtDisplay(w);
+ win = XtWindow(w);
+ while (XCheckTypedWindowEvent(dpy, win, Expose, &evt)) continue;
+
+ wp = find_widget(w);
+ if (wp->keep_window && !wp->mesg_information) return;
+ mesg_check_size_change(wp);
+ redraw_message_window(wp);
+ }
+}
+
+
+static void
+get_gc(w, mesg_info)
+ Widget w;
+ struct mesg_info_t *mesg_info;
+{
+ XGCValues values;
+ XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont;
+ Pixel fgpixel, bgpixel;
+ Arg arg[2];
+
+ XtSetArg(arg[0], XtNforeground, &fgpixel);
+ XtSetArg(arg[1], XtNbackground, &bgpixel);
+ XtGetValues(w, arg, TWO);
+
+ values.foreground = fgpixel;
+ values.background = bgpixel;
+ values.function = GXcopy;
+ values.font = WindowFont(w);
+ mesg_info->gc = XtGetGC(w, mask, &values);
+}
+
+/*
+ * Handle resizes on a message window. Correct saved pixel height and width.
+ * Adjust circle buffer to accomidate the new size.
+ *
+ * Problem: If the resize decreases the width of the window such that
+ * some lines are now longer than the window, they will be cut off by
+ * X itself. All new lines will be split to the new size, but the ends
+ * of the old ones will not be seen again unless the window is lengthened.
+ * I don't deal with this problem because it isn't worth the trouble.
+ */
+/* ARGSUSED */
+static void
+mesg_resized(w, client_data, call_data)
+ Widget w;
+ XtPointer call_data, client_data;
+{
+ Arg args[4];
+ Cardinal num_args;
+ Dimension pixel_width, pixel_height;
+ struct xwindow *wp;
+#ifdef VERBOSE
+ int old_lines;
+
+ old_lines = wp->mesg_information->num_lines;;
+#endif
+
+ num_args = 0;
+ XtSetArg(args[num_args], XtNwidth, &pixel_width); num_args++;
+ XtSetArg(args[num_args], XtNheight, &pixel_height); num_args++;
+ XtGetValues(w, args, num_args);
+
+ wp = find_widget(w);
+ wp->pixel_width = pixel_width;
+ wp->pixel_height = pixel_height;
+
+ set_circle_buf(wp->mesg_information,
+ (int) pixel_height / wp->mesg_information->char_height);
+
+#ifdef VERBOSE
+ printf("Message resize. Pixel: width = %d, height = %d; Lines: old = %d, new = %d\n",
+ pixel_width,
+ pixel_height,
+ old_lines,
+ wp->mesg_information->num_lines);
+#endif
+}
+
+/*winmesg.c*/