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. */
6 * Message window routines.
9 * create_message_window()
10 * destroy_message_window()
11 * display_message_window()
16 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
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
30 #undef PRESERVE_NO_SYSV
33 #include "xwindow.h" /* Window widget declarations */
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\
52 <Key>Down: scroll(2)\n\
55 /* Move the message window's vertical scrollbar's slider to the bottom. */
57 set_message_slider(wp
)
63 scrollbar
= XtNameToWidget(XtParent(wp
->w
), "vertical");
67 XtCallCallbacks(scrollbar
, XtNjumpProc
, &top
);
72 create_message_window(wp
, create_popup
, parent
)
73 struct xwindow
*wp
; /* window pointer */
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
));
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. */
104 XtSetArg(args
[num_args
], XtNallowShellResize
, True
);
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.
122 XtSetArg(args
[num_args
], XtNallowVert
, True
);
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.
137 XtSetArg(args
[num_args
], XtNtranslations
,
138 XtParseTranslationTable(mesg_translations
));
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. */
157 XtSetArg(args
[num_args
], XtNfont
, &mesg_info
->fs
);
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
;
178 (DEFAULT_MESSAGE_WIDTH
* mesg_info
->fs
->max_bounds
.width
);
180 /* Set the new width and height. */
182 XtSetArg(args
[num_args
], XtNwidth
, wp
->pixel_width
);
184 XtSetArg(args
[num_args
], XtNheight
, wp
->pixel_height
);
186 XtSetValues(wp
->w
, args
, num_args
);
188 /* make sure viewport height makes sense before realizing it */
190 mesg_info
->viewport_height
=
191 appResources
.message_lines
* mesg_info
->char_height
;
192 XtSetArg(args
[num_args
], XtNheight
, mesg_info
->viewport_height
);
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.
203 XtRealizeWidget(wp
->popup
);
204 XSetWMProtocols(XtDisplay(wp
->popup
), XtWindow(wp
->popup
),
205 &wm_delete_window
, 1);
210 destroy_message_window(wp
)
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;
223 XtRemoveCallback(wp
->w
, XtNexposeCallback
, mesg_exposed
,
229 /* Redraw message window if new lines have been added. */
231 display_message_window(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.
244 append_message(wp
, str
)
248 char *mark
, *remainder
, buf
[BUFSZ
];
253 Strcpy(buf
, str
); /* we might mark it up */
258 remainder
= split(mark
, wp
->mesg_information
->fs
, wp
->pixel_width
);
259 add_line(wp
->mesg_information
, mark
);
263 /* private functions =======================================================
267 * Return the element in the circular linked list just before the given
270 static struct line_element
*
272 struct line_element
*mark
;
274 struct line_element
*curr
;
277 return (struct line_element
*) 0;
279 for (curr
= mark
; curr
->next
!= mark
; curr
= curr
->next
)
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.
291 set_circle_buf(mesg_info
, count
)
292 struct mesg_info_t
*mesg_info
;
296 struct line_element
*tail
, *curr
, *head
;
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
;
315 free((genericptr_t
) curr
->line
);
316 free((genericptr_t
) curr
);
319 /* make sure we don't have a dangling pointer */
320 mesg_info
->head
= (struct line_element
*) 0;
322 tail
->next
= mesg_info
->head
; /* link the tail to the head */
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
));
333 curr
->buf_length
= 0;
334 curr
->str_length
= 0;
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 */
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.
368 split(s
, fs
, pixel_width
)
370 XFontStruct
*fs
; /* Font for the window. */
371 Dimension pixel_width
;
373 char save
, *end
, *remainder
;
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
) {
382 while (*end
!= ' ') {
384 panic("split: eos!");
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
401 add_line(mesg_info
, s
)
402 struct mesg_info_t
*mesg_info
;
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
) {
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.
435 register struct mesg_info_t
*mesg_info
= wp
->mesg_information
;
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
)
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
);
453 mesg_info
->last_pause
= mesg_info
->head
;
460 redraw_message_window(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
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.
505 mesg_check_size_change(wp
)
508 struct mesg_info_t
*mesg_info
= wp
->mesg_information
;
510 Dimension new_width
, new_height
;
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. */
532 mesg_exposed(w
, client_data
, widget_data
)
534 XtPointer client_data
; /* unused */
535 XtPointer widget_data
; /* expose event from Window widget */
537 XExposeEvent
*event
= (XExposeEvent
*) widget_data
;
541 if (XtIsRealized(w
) && event
->count
== 0) {
548 * Drain all pending expose events for the message window;
549 * we'll redraw the whole thing at once.
553 while (XCheckTypedWindowEvent(dpy
, win
, Expose
, &evt
))
557 if (wp
->keep_window
&& !wp
->mesg_information
)
559 mesg_check_size_change(wp
);
560 redraw_message_window(wp
);
567 struct mesg_info_t
*mesg_info
;
570 XtGCMask mask
= GCFunction
| GCForeground
| GCBackground
| GCFont
;
571 Pixel fgpixel
, bgpixel
;
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.
597 mesg_resized(w
, client_data
, call_data
)
599 XtPointer call_data
, client_data
;
603 Dimension pixel_width
, pixel_height
;
608 old_lines
= wp
->mesg_information
->num_lines
;
616 XtSetArg(args
[num_args
], XtNwidth
, &pixel_width
);
618 XtSetArg(args
[num_args
], XtNheight
, &pixel_height
);
620 XtGetValues(w
, args
, num_args
);
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
);
630 printf("Message resize. Pixel: width = %d, height = %d; Lines: old = "
632 pixel_width
, pixel_height
, old_lines
,
633 wp
->mesg_information
->num_lines
);