OSDN Git Service

upgrade to 3.6.1
[jnethack/source.git] / win / X11 / winmesg.c
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. */
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 #ifndef XI18N
41 static char *FDECL(split, (char *, XFontStruct *, DIMENSION_P));
42 #endif
43 static void FDECL(add_line, (struct mesg_info_t *, const char *));
44 static void FDECL(redraw_message_window, (struct xwindow *));
45 static void FDECL(mesg_check_size_change, (struct xwindow *));
46 static void FDECL(mesg_exposed, (Widget, XtPointer, XtPointer));
47 static void FDECL(get_gc, (Widget, struct mesg_info_t *));
48 static void FDECL(mesg_resized, (Widget, XtPointer, XtPointer));
49
50 static char mesg_translations[] = "#override\n\
51  <Key>Left:     scroll(4)\n\
52  <Key>Right:    scroll(6)\n\
53  <Key>Up:       scroll(8)\n\
54  <Key>Down:     scroll(2)\n\
55  <Key>:         input()";
56
57 /* Move the message window's vertical scrollbar's slider to the bottom. */
58 void
59 set_message_slider(wp)
60 struct xwindow *wp;
61 {
62     Widget scrollbar;
63     float top;
64
65     scrollbar = XtNameToWidget(XtParent(wp->w), "vertical");
66
67     if (scrollbar) {
68         top = 1.0;
69         XtCallCallbacks(scrollbar, XtNjumpProc, &top);
70     }
71 }
72
73 void
74 create_message_window(wp, create_popup, parent)
75 struct xwindow *wp; /* window pointer */
76 boolean create_popup;
77 Widget parent;
78 {
79     Arg args[8];
80     Cardinal num_args;
81     Widget viewport;
82     struct mesg_info_t *mesg_info;
83 #ifdef XI18N
84     XFontSetExtents *extent;
85 #endif
86
87     wp->type = NHW_MESSAGE;
88
89     wp->mesg_information = mesg_info =
90         (struct mesg_info_t *) alloc(sizeof (struct mesg_info_t));
91
92     mesg_info->fs = 0;
93     mesg_info->num_lines = 0;
94     mesg_info->head = mesg_info->line_here = mesg_info->last_pause =
95         mesg_info->last_pause_head = (struct line_element *) 0;
96     mesg_info->dirty = False;
97     mesg_info->viewport_width = mesg_info->viewport_height = 0;
98
99     if (iflags.msg_history < (unsigned) appResources.message_lines)
100         iflags.msg_history = (unsigned) appResources.message_lines;
101     if (iflags.msg_history > MAX_HISTORY) /* a sanity check */
102         iflags.msg_history = MAX_HISTORY;
103
104     set_circle_buf(mesg_info, (int) iflags.msg_history);
105
106     /* Create a popup that becomes the parent. */
107     if (create_popup) {
108         num_args = 0;
109         XtSetArg(args[num_args], XtNallowShellResize, True);
110         num_args++;
111
112         wp->popup = parent =
113             XtCreatePopupShell("message_popup", topLevelShellWidgetClass,
114                                toplevel, args, num_args);
115         /*
116          * If we're here, then this is an auxiliary message window.  If we're
117          * cancelled via a delete window message, we should just pop down.
118          */
119     }
120
121     /*
122      * Create the viewport.  We only want the vertical scroll bar ever to be
123      * visible.  If we allow the horizontal scrollbar to be visible it will
124      * always be visible, due to the stupid way the Athena viewport operates.
125      */
126     num_args = 0;
127     XtSetArg(args[num_args], XtNallowVert, True);
128     num_args++;
129     viewport = XtCreateManagedWidget(
130         "mesg_viewport",     /* name */
131         viewportWidgetClass, /* widget class from Window.h */
132         parent,              /* parent widget */
133         args,                /* set some values */
134         num_args);           /* number of values to set */
135
136     /*
137      * Create a message window.  We will change the width and height once
138      * we know what font we are using.
139      */
140     num_args = 0;
141     if (!create_popup) {
142         XtSetArg(args[num_args], XtNtranslations,
143                  XtParseTranslationTable(mesg_translations));
144         num_args++;
145     }
146     wp->w = XtCreateManagedWidget(
147         "message",         /* name */
148         windowWidgetClass, /* widget class from Window.h */
149         viewport,          /* parent widget */
150         args,              /* set some values */
151         num_args);         /* number of values to set */
152
153     XtAddCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer) 0);
154
155     /*
156      * Now adjust the height and width of the message window so that it
157      * is appResources.message_lines high and DEFAULT_MESSAGE_WIDTH wide.
158      */
159
160     /* Get the font information. */
161     num_args = 0;
162     XtSetArg(args[num_args], XtNfont, &mesg_info->fs);
163     num_args++;
164 #ifdef XI18N
165     XtSetArg(args[num_args], XtNfontSet, &mesg_info->fontset);
166     num_args++;
167 #endif
168     XtGetValues(wp->w, args, num_args);
169 #ifdef XI18N
170     extent = XExtentsOfFontSet(mesg_info->fontset);
171 #endif
172
173     /* Save character information for fast use later. */
174 #ifndef XI18N
175     mesg_info->char_width = mesg_info->fs->max_bounds.width;
176     mesg_info->char_height =
177         mesg_info->fs->max_bounds.ascent + mesg_info->fs->max_bounds.descent;
178     mesg_info->char_ascent = mesg_info->fs->max_bounds.ascent;
179     mesg_info->char_lbearing = -mesg_info->fs->min_bounds.lbearing;
180 #else
181     mesg_info->char_width    = extent->max_logical_extent.width;
182     mesg_info->char_height   = extent->max_logical_extent.height;
183     mesg_info->char_ascent   = -extent->max_logical_extent.y;
184     mesg_info->char_lbearing = extent->max_logical_extent.x;
185 #endif
186
187     get_gc(wp->w, mesg_info);
188
189     wp->pixel_height = ((int) iflags.msg_history) * mesg_info->char_height;
190
191     /* If a variable spaced font, only use 2/3 of the default size */
192     if (mesg_info->fs->min_bounds.width != mesg_info->fs->max_bounds.width) {
193         wp->pixel_width = ((2 * DEFAULT_MESSAGE_WIDTH) / 3)
194                           * mesg_info->fs->max_bounds.width;
195     } else
196         wp->pixel_width =
197             (DEFAULT_MESSAGE_WIDTH * mesg_info->fs->max_bounds.width);
198
199     /* Set the new width and height. */
200     num_args = 0;
201     XtSetArg(args[num_args], XtNwidth, wp->pixel_width);
202     num_args++;
203     XtSetArg(args[num_args], XtNheight, wp->pixel_height);
204     num_args++;
205     XtSetValues(wp->w, args, num_args);
206
207     /* make sure viewport height makes sense before realizing it */
208     num_args = 0;
209     mesg_info->viewport_height =
210         appResources.message_lines * mesg_info->char_height;
211     XtSetArg(args[num_args], XtNheight, mesg_info->viewport_height);
212     num_args++;
213     XtSetValues(viewport, args, num_args);
214
215     XtAddCallback(wp->w, XtNresizeCallback, mesg_resized, (XtPointer) 0);
216
217     /*
218      * If we have created our own popup, then realize it so that the
219      * viewport is also realized.
220      */
221     if (create_popup) {
222         XtRealizeWidget(wp->popup);
223         XSetWMProtocols(XtDisplay(wp->popup), XtWindow(wp->popup),
224                         &wm_delete_window, 1);
225     }
226 }
227
228 void
229 destroy_message_window(wp)
230 struct xwindow *wp;
231 {
232     if (wp->popup) {
233         nh_XtPopdown(wp->popup);
234         if (!wp->keep_window)
235             XtDestroyWidget(wp->popup), wp->popup = (Widget) 0;
236     }
237     if (wp->mesg_information) {
238         set_circle_buf(wp->mesg_information, 0); /* free buffer list */
239         free((genericptr_t) wp->mesg_information), wp->mesg_information = 0;
240     }
241     if (wp->keep_window)
242         XtRemoveCallback(wp->w, XtNexposeCallback, mesg_exposed,
243                          (XtPointer) 0);
244     else
245         wp->type = NHW_NONE;
246 }
247
248 /* Redraw message window if new lines have been added. */
249 void
250 display_message_window(wp)
251 struct xwindow *wp;
252 {
253     set_message_slider(wp);
254     if (wp->mesg_information->dirty)
255         redraw_message_window(wp);
256 }
257
258 /*
259  * Append a line of text to the message window.  Split the line if the
260  * rendering of the text is too long for the window.
261  */
262 void
263 append_message(wp, str)
264 struct xwindow *wp;
265 const char *str;
266 {
267 #if defined(XI18N)
268     int len;
269     XRectangle ink_ext, lgc_ext;
270     char ss[1024],s1[1024],s2[1024]; /* may be enough */
271
272     Strcpy(ss, str);
273     while(1){
274       len = strlen(ss);
275       while(1){
276         XmbTextExtents(wp->mesg_information->fontset, ss, len,
277                        &ink_ext,&lgc_ext);
278         if(lgc_ext.width < wp->pixel_width)
279           break;
280         --len;
281       }
282       if( len >= strlen(ss)){
283         add_line(wp->mesg_information, ss);
284         break;
285       }
286       split_japanese(ss, s1, s2, len);
287       add_line(wp->mesg_information, s1);
288       if(!*s2)
289         break;
290       Strcpy(ss,s2);
291     }
292 #else
293     char *mark, *remainder, buf[BUFSZ];
294
295     if (!str)
296         return;
297
298     Strcpy(buf, str); /* we might mark it up */
299
300     remainder = buf;
301     do {
302         mark = remainder;
303         remainder = split(mark, wp->mesg_information->fs, wp->pixel_width);
304         add_line(wp->mesg_information, mark);
305     } while (remainder);
306 # if 1 /*JP*/
307     add_line(wp->mesg_information, buf);
308 # endif
309 #endif /*XI18N*/
310 }
311
312 /* private functions =======================================================
313  */
314
315 /*
316  * Return the element in the circular linked list just before the given
317  * element.
318  */
319 static struct line_element *
320 get_previous(mark)
321 struct line_element *mark;
322 {
323     struct line_element *curr;
324
325     if (!mark)
326         return (struct line_element *) 0;
327
328     for (curr = mark; curr->next != mark; curr = curr->next)
329         ;
330     return curr;
331 }
332
333 /*
334  * Set the information buffer size to count lines.  We do this by creating
335  * a circular linked list of elements, each of which represents a line of
336  * text.  New buffers are created as needed, old ones are freed if they
337  * are no longer used.
338  */
339 static void
340 set_circle_buf(mesg_info, count)
341 struct mesg_info_t *mesg_info;
342 int count;
343 {
344     int i;
345     struct line_element *tail, *curr, *head;
346
347     if (count < 0)
348         panic("set_circle_buf: bad count [= %d]", count);
349     if (count == mesg_info->num_lines)
350         return; /* no change in size */
351
352     if (count < mesg_info->num_lines) {
353         /*
354          * Toss num_lines - count line entries from our circular list.
355          *
356          * We lose lines from the front (top) of the list.  We _know_
357          * the list is non_empty.
358          */
359         tail = get_previous(mesg_info->head);
360         for (i = mesg_info->num_lines - count; i > 0; i--) {
361             curr = mesg_info->head;
362             mesg_info->head = curr->next;
363             if (curr->line)
364                 free((genericptr_t) curr->line);
365             free((genericptr_t) curr);
366         }
367         if (count == 0) {
368             /* make sure we don't have a dangling pointer */
369             mesg_info->head = (struct line_element *) 0;
370         } else {
371             tail->next = mesg_info->head; /* link the tail to the head */
372         }
373     } else {
374         /*
375          * Add count - num_lines blank lines to the head of the list.
376          *
377          * Create a separate list, keeping track of the tail.
378          */
379         for (head = tail = 0, i = 0; i < count - mesg_info->num_lines; i++) {
380             curr = (struct line_element *) alloc(sizeof(struct line_element));
381             curr->line = 0;
382             curr->buf_length = 0;
383             curr->str_length = 0;
384             if (tail) {
385                 tail->next = curr;
386                 tail = curr;
387             } else {
388                 head = tail = curr;
389             }
390         }
391         /*
392          * Complete the circle by making the new tail point to the old head
393          * and the old tail point to the new head.  If our line count was
394          * zero, then make the new list circular.
395          */
396         if (mesg_info->num_lines) {
397             curr = get_previous(mesg_info->head); /* get end of old list */
398
399             tail->next = mesg_info->head; /* new tail -> old head */
400             curr->next = head;            /* old tail -> new head */
401         } else {
402             tail->next = head;
403         }
404         mesg_info->head = head;
405     }
406
407     mesg_info->num_lines = count;
408     /* Erase the line on a resize. */
409     mesg_info->last_pause = (struct line_element *) 0;
410 }
411
412 /*
413  * Make sure the given string is shorter than the given pixel width.  If
414  * not, back up from the end by words until we find a place to split.
415  */
416 #ifndef XI18N
417 static char *
418 split(s, fs, pixel_width)
419 char *s;
420 XFontStruct *fs; /* Font for the window. */
421 Dimension pixel_width;
422 {
423     char save, *end, *remainder;
424
425     save = '\0';
426     remainder = 0;
427     end = eos(s); /* point to null at end of string */
428
429     /* assume that if end == s, XXXXXX returns 0) */
430     while ((Dimension) XTextWidth(fs, s, (int) strlen(s)) > pixel_width) {
431         *end-- = save;
432         while (*end != ' ') {
433             if (end == s)
434                 panic("split: eos!");
435             --end;
436         }
437         save = *end;
438         *end = '\0';
439         remainder = end + 1;
440     }
441     return remainder;
442 }
443 #endif
444
445 /*
446  * Add a line of text to the window.  The first line in the curcular list
447  * becomes the last.  So all we have to do is copy the new line over the
448  * old one.  If the line buffer is too small, then allocate a new, larger
449  * one.
450  */
451 static void
452 add_line(mesg_info, s)
453 struct mesg_info_t *mesg_info;
454 const char *s;
455 {
456     register struct line_element *curr = mesg_info->head;
457     register int new_line_length = strlen(s);
458
459     if (new_line_length + 1 > curr->buf_length) {
460         if (curr->line)
461             free(curr->line); /* free old line */
462
463         curr->buf_length = new_line_length + 1;
464         curr->line = (char *) alloc((unsigned) curr->buf_length);
465     }
466
467     Strcpy(curr->line, s);              /* copy info */
468     curr->str_length = new_line_length; /* save string length */
469
470     mesg_info->head = mesg_info->head->next; /* move head to next line */
471     mesg_info->dirty = True;                 /* we have undrawn lines */
472 }
473
474 /*
475  * Save a position in the text buffer so we can draw a line to seperate
476  * text from the last time this function was called.
477  *
478  * Save the head position, since it is the line "after" the last displayed
479  * line in the message window.  The window redraw routine will draw a
480  * line above this saved pointer.
481  */
482 void
483 set_last_pause(wp)
484 struct xwindow *wp;
485 {
486     register struct mesg_info_t *mesg_info = wp->mesg_information;
487
488 #ifdef ERASE_LINE
489     /*
490      * If we've erased the pause line and haven't added any new lines,
491      * don't try to erase the line again.
492      */
493     if (!mesg_info->last_pause
494         && mesg_info->last_pause_head == mesg_info->head)
495         return;
496
497     if (mesg_info->last_pause == mesg_info->head) {
498         /* No new messages in last turn.  Redraw window to erase line. */
499         mesg_info->last_pause = (struct line_element *) 0;
500         mesg_info->last_pause_head = mesg_info->head;
501         redraw_message_window(wp);
502     } else {
503 #endif
504         mesg_info->last_pause = mesg_info->head;
505 #ifdef ERASE_LINE
506     }
507 #endif
508 }
509
510 static void
511 redraw_message_window(wp)
512 struct xwindow *wp;
513 {
514     struct mesg_info_t *mesg_info = wp->mesg_information;
515     register struct line_element *curr;
516     register int row, y_base;
517
518     /*
519      * Do this the cheap and easy way.  Clear the window and just redraw
520      * the whole thing.
521      *
522      * This could be done more effecently with one call to XDrawText() instead
523      * of many calls to XDrawString().  Maybe later.
524      *
525      * Only need to clear if window has new text.
526      */
527     if (mesg_info->dirty) {
528         XClearWindow(XtDisplay(wp->w), XtWindow(wp->w));
529         mesg_info->line_here = mesg_info->last_pause;
530     }
531
532     /* For now, just update the whole shootn' match. */
533     for (y_base = row = 0, curr = mesg_info->head; row < mesg_info->num_lines;
534          row++, y_base += mesg_info->char_height, curr = curr->next) {
535 #ifndef XI18N
536         XDrawString(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc,
537                     mesg_info->char_lbearing, mesg_info->char_ascent + y_base,
538                     curr->line, curr->str_length);
539 #else
540         XmbDrawString(XtDisplay(wp->w), XtWindow(wp->w),
541                       mesg_info->fontset,
542                       mesg_info->gc,
543                       mesg_info->char_lbearing,
544                       mesg_info->char_ascent + y_base,
545                       curr->line,
546                       curr->str_length);
547 #endif
548         /*
549          * This draws a line at the _top_ of the line of text pointed to by
550          * mesg_info->last_pause.
551          */
552         if (appResources.message_line && curr == mesg_info->line_here) {
553             XDrawLine(XtDisplay(wp->w), XtWindow(wp->w), mesg_info->gc, 0,
554                       y_base, wp->pixel_width, y_base);
555         }
556     }
557
558     mesg_info->dirty = False;
559 }
560
561 /*
562  * Check the size of the viewport.  If it has shrunk, then we want to
563  * move the vertical slider to the bottom.
564  */
565 static void
566 mesg_check_size_change(wp)
567 struct xwindow *wp;
568 {
569     struct mesg_info_t *mesg_info = wp->mesg_information;
570     Arg arg[2];
571     Dimension new_width, new_height;
572     Widget viewport;
573
574     viewport = XtParent(wp->w);
575
576     XtSetArg(arg[0], XtNwidth, &new_width);
577     XtSetArg(arg[1], XtNheight, &new_height);
578     XtGetValues(viewport, arg, TWO);
579
580     /* Only move slider to bottom if new size is smaller. */
581     if (new_width < mesg_info->viewport_width
582         || new_height < mesg_info->viewport_height) {
583         set_message_slider(wp);
584     }
585
586     mesg_info->viewport_width = new_width;
587     mesg_info->viewport_height = new_height;
588 }
589
590 /* Event handler for message window expose events. */
591 /*ARGSUSED*/
592 static void
593 mesg_exposed(w, client_data, widget_data)
594 Widget w;
595 XtPointer client_data; /* unused */
596 XtPointer widget_data; /* expose event from Window widget */
597 {
598     XExposeEvent *event = (XExposeEvent *) widget_data;
599
600     nhUse(client_data);
601
602     if (XtIsRealized(w) && event->count == 0) {
603         struct xwindow *wp;
604         Display *dpy;
605         Window win;
606         XEvent evt;
607
608         /*
609          * Drain all pending expose events for the message window;
610          * we'll redraw the whole thing at once.
611          */
612         dpy = XtDisplay(w);
613         win = XtWindow(w);
614         while (XCheckTypedWindowEvent(dpy, win, Expose, &evt))
615             continue;
616
617         wp = find_widget(w);
618         if (wp->keep_window && !wp->mesg_information)
619             return;
620         mesg_check_size_change(wp);
621         redraw_message_window(wp);
622     }
623 }
624
625 static void
626 get_gc(w, mesg_info)
627 Widget w;
628 struct mesg_info_t *mesg_info;
629 {
630     XGCValues values;
631     XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont;
632     Pixel fgpixel, bgpixel;
633     Arg arg[2];
634
635     XtSetArg(arg[0], XtNforeground, &fgpixel);
636     XtSetArg(arg[1], XtNbackground, &bgpixel);
637     XtGetValues(w, arg, TWO);
638
639     values.foreground = fgpixel;
640     values.background = bgpixel;
641     values.function = GXcopy;
642     values.font = WindowFont(w);
643     mesg_info->gc = XtGetGC(w, mask, &values);
644 }
645
646 /*
647  * Handle resizes on a message window.  Correct saved pixel height and width.
648  * Adjust circle buffer to accomidate the new size.
649  *
650  * Problem:  If the resize decreases the width of the window such that
651  * some lines are now longer than the window, they will be cut off by
652  * X itself.  All new lines will be split to the new size, but the ends
653  * of the old ones will not be seen again unless the window is lengthened.
654  * I don't deal with this problem because it isn't worth the trouble.
655  */
656 /* ARGSUSED */
657 static void
658 mesg_resized(w, client_data, call_data)
659 Widget w;
660 XtPointer call_data, client_data;
661 {
662     Arg args[4];
663     Cardinal num_args;
664     Dimension pixel_width, pixel_height;
665     struct xwindow *wp;
666 #ifdef VERBOSE
667     int old_lines;
668
669     old_lines = wp->mesg_information->num_lines;
670     ;
671 #endif
672
673     nhUse(call_data);
674     nhUse(client_data);
675
676     num_args = 0;
677     XtSetArg(args[num_args], XtNwidth, &pixel_width);
678     num_args++;
679     XtSetArg(args[num_args], XtNheight, &pixel_height);
680     num_args++;
681     XtGetValues(w, args, num_args);
682
683     wp = find_widget(w);
684     wp->pixel_width = pixel_width;
685     wp->pixel_height = pixel_height;
686
687     set_circle_buf(wp->mesg_information,
688                    (int) pixel_height / wp->mesg_information->char_height);
689
690 #ifdef VERBOSE
691     printf("Message resize.  Pixel: width = %d, height = %d;  Lines: old = "
692            "%d, new = %d\n",
693            pixel_width, pixel_height, old_lines,
694            wp->mesg_information->num_lines);
695 #endif
696 }
697
698 /*winmesg.c*/