]> granicus.if.org Git - nethack/commitdiff
*** empty log message ***
authorjwalz <jwalz>
Sat, 5 Jan 2002 21:06:03 +0000 (21:06 +0000)
committerjwalz <jwalz>
Sat, 5 Jan 2002 21:06:03 +0000 (21:06 +0000)
win/X11/winmesg.c [new file with mode: 0644]

diff --git a/win/X11/winmesg.c b/win/X11/winmesg.c
new file mode 100644 (file)
index 0000000..da5e359
--- /dev/null
@@ -0,0 +1,625 @@
+/*     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*/