1 /* NetHack 3.7 wintext.c $NHDT-Date: 1597967808 2020/08/20 23:56:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.22 $ */
2 /* Copyright (c) Dean Luick, 1992 */
3 /* NetHack may be freely redistributed. See license for details. */
6 * File for dealing with text windows.
8 * + No global functions.
12 #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */
15 #include <X11/Intrinsic.h>
16 #include <X11/StringDefs.h>
17 #include <X11/Shell.h>
19 #include <X11/Xaw/Form.h>
20 #include <X11/Xaw/AsciiText.h>
21 #include <X11/Xaw/Cardinals.h>
22 #include <X11/Xatom.h>
24 #ifdef PRESERVE_NO_SYSV
28 #undef PRESERVE_NO_SYSV
38 #ifdef GRAPHIC_TOMBSTONE
42 #define TRANSIENT_TEXT /* text window is a transient window (no positioning) \
45 static const char text_translations
[] = "#override\n\
46 <BtnDown>: dismiss_text()\n\
47 <Key>: key_dismiss_text()";
49 #ifdef GRAPHIC_TOMBSTONE
50 static const char rip_translations
[] = "#override\n\
51 <BtnDown>: rip_dismiss_text()\n\
52 <Key>: rip_dismiss_text()";
54 static Widget
create_ripout_widget(Widget
);
59 delete_text(Widget w
, XEvent
*event
, String
*params
, Cardinal
*num_params
)
62 struct text_info_t
*text_info
;
69 text_info
= wp
->text_information
;
71 nh_XtPopdown(wp
->popup
);
73 if (text_info
->blocked
) {
75 } else if (text_info
->destroy_on_ack
) {
76 destroy_text_window(wp
);
81 * Callback used for all text windows. The window is popped down on any key
82 * or button down event. It is destroyed if the main nethack code is done
87 dismiss_text(Widget w
, XEvent
*event
, String
*params
, Cardinal
*num_params
)
90 struct text_info_t
*text_info
;
97 text_info
= wp
->text_information
;
99 nh_XtPopdown(wp
->popup
);
101 if (text_info
->blocked
) {
103 } else if (text_info
->destroy_on_ack
) {
104 destroy_text_window(wp
);
108 /* Dismiss when a non-modifier key pressed. */
110 key_dismiss_text(Widget w
, XEvent
*event
, String
*params
, Cardinal
*num_params
)
112 char ch
= key_event_to_char((XKeyEvent
*) event
);
114 dismiss_text(w
, event
, params
, num_params
);
117 #ifdef GRAPHIC_TOMBSTONE
118 /* Dismiss from clicking on rip image. */
120 rip_dismiss_text(Widget w
, XEvent
*event
, String
*params
, Cardinal
*num_params
)
122 dismiss_text(XtParent(w
), event
, params
, num_params
);
128 add_to_text_window(struct xwindow
*wp
, int attr
, /* currently unused */
131 struct text_info_t
*text_info
= wp
->text_information
;
136 append_text_buffer(&text_info
->text
, str
, FALSE
);
138 /* Calculate text width and save longest line */
139 width
= XTextWidth(text_info
->fs
, str
, (int) strlen(str
));
140 if (width
> text_info
->max_width
)
141 text_info
->max_width
= width
;
145 display_text_window(struct xwindow
*wp
, boolean blocking
)
147 struct text_info_t
*text_info
;
150 Dimension width
, height
, font_height
;
153 text_info
= wp
->text_information
;
154 width
= text_info
->max_width
+ text_info
->extra_width
;
155 text_info
->blocked
= blocking
;
156 text_info
->destroy_on_ack
= FALSE
;
157 font_height
= nhFontHeight(wp
->w
);
160 * Calculate the number of lines to use. First, find the number of
161 * lines that would fit on the screen. Next, remove four of these
162 * lines to give room for a possible window manager titlebar (some
163 * wm's put a titlebar on transient windows). Make sure we have
164 * _some_ lines. Finally, use the number of lines in the text if
165 * there are fewer than the max.
167 nlines
= (XtScreen(wp
->w
)->height
- text_info
->extra_height
) / font_height
;
170 if (nlines
> text_info
->text
.num_lines
)
171 nlines
= text_info
->text
.num_lines
;
175 height
= nlines
* font_height
+ text_info
->extra_height
;
179 if (nlines
< text_info
->text
.num_lines
) {
180 /* add on width of scrollbar. Really should look this up,
181 * but can't until the window is realized. Chicken-and-egg problem.
186 #ifdef GRAPHIC_TOMBSTONE
187 if (text_info
->is_rip
) {
188 Widget rip
= create_ripout_widget(XtParent(wp
->w
));
191 XtSetArg(args
[num_args
], nhStr(XtNfromVert
), rip
);
194 text_info
->is_rip
= FALSE
;
198 if (width
> (Dimension
) XtScreen(wp
->w
)->width
) { /* too wide for screen */
199 /* Back off some amount - we really need to back off the scrollbar */
200 /* width plus some extra. */
201 width
= XtScreen(wp
->w
)->width
- 20;
203 XtSetArg(args
[num_args
], XtNstring
, text_info
->text
.text
);
205 XtSetArg(args
[num_args
], XtNwidth
, width
);
207 XtSetArg(args
[num_args
], XtNheight
, height
);
209 XtSetValues(wp
->w
, args
, num_args
);
211 #ifdef TRANSIENT_TEXT
212 XtRealizeWidget(wp
->popup
);
213 XSetWMProtocols(XtDisplay(wp
->popup
), XtWindow(wp
->popup
),
214 &wm_delete_window
, 1);
215 positionpopup(wp
->popup
, FALSE
);
218 nh_XtPopup(wp
->popup
, (int) XtGrabNone
, wp
->w
);
220 /* Kludge alert. Scrollbars are not sized correctly by the Text widget */
221 /* if added before the window is displayed, so do it afterward. */
223 if (nlines
< text_info
->text
.num_lines
) { /* add vert scrollbar */
224 XtSetArg(args
[num_args
], nhStr(XtNscrollVertical
),
225 XawtextScrollAlways
);
228 if (width
>= (Dimension
)(XtScreen(wp
->w
)->width
- 20)) { /* too wide */
229 XtSetArg(args
[num_args
], nhStr(XtNscrollHorizontal
),
230 XawtextScrollAlways
);
234 XtSetValues(wp
->w
, args
, num_args
);
236 /* We want the user to acknowledge. */
238 (void) x_event(EXIT_ON_EXIT
);
239 nh_XtPopdown(wp
->popup
);
244 create_text_window(struct xwindow
*wp
)
246 struct text_info_t
*text_info
;
249 Position top_margin
, bottom_margin
, left_margin
, right_margin
;
254 wp
->text_information
= text_info
=
255 (struct text_info_t
*) alloc(sizeof(struct text_info_t
));
257 init_text_buffer(&text_info
->text
);
258 text_info
->max_width
= 0;
259 text_info
->extra_width
= 0;
260 text_info
->extra_height
= 0;
261 text_info
->blocked
= FALSE
;
262 text_info
->destroy_on_ack
= TRUE
; /* Ok to destroy before display */
263 #ifdef GRAPHIC_TOMBSTONE
264 text_info
->is_rip
= FALSE
;
268 XtSetArg(args
[num_args
], XtNallowShellResize
, True
), num_args
++;
269 XtSetArg(args
[num_args
], XtNtranslations
,
270 XtParseTranslationTable(text_translations
)), num_args
++;
272 #ifdef TRANSIENT_TEXT
273 wp
->popup
= XtCreatePopupShell("text", transientShellWidgetClass
,
274 toplevel
, args
, num_args
);
276 wp
->popup
= XtCreatePopupShell("text", topLevelShellWidgetClass
, toplevel
,
279 XtOverrideTranslations(
281 XtParseTranslationTable("<Message>WM_PROTOCOLS: delete_text()"));
284 XtSetArg(args
[num_args
], XtNallowShellResize
, True
), num_args
++;
285 XtSetArg(args
[num_args
], XtNtranslations
,
286 XtParseTranslationTable(text_translations
)), num_args
++;
287 form
= XtCreateManagedWidget("form", formWidgetClass
, wp
->popup
, args
,
291 XtSetArg(args
[num_args
], nhStr(XtNdisplayCaret
), False
);
293 XtSetArg(args
[num_args
], XtNresize
, XawtextResizeBoth
);
295 XtSetArg(args
[num_args
], XtNtranslations
,
296 XtParseTranslationTable(text_translations
));
299 wp
->w
= XtCreateManagedWidget(svk
.killer
.name
[0] && WIN_MAP
== WIN_ERR
301 : "text_text", /* name */
302 asciiTextWidgetClass
,
303 form
, /* parent widget */
304 args
, /* set some values */
305 num_args
); /* number of values to set */
307 /* Get the font and margin information. */
309 XtSetArg(args
[num_args
], XtNfont
, &text_info
->fs
);
311 XtSetArg(args
[num_args
], nhStr(XtNtopMargin
), &top_margin
);
313 XtSetArg(args
[num_args
], nhStr(XtNbottomMargin
), &bottom_margin
);
315 XtSetArg(args
[num_args
], nhStr(XtNleftMargin
), &left_margin
);
317 XtSetArg(args
[num_args
], nhStr(XtNrightMargin
), &right_margin
);
319 XtGetValues(wp
->w
, args
, num_args
);
321 text_info
->extra_width
= left_margin
+ right_margin
;
322 text_info
->extra_height
= top_margin
+ bottom_margin
;
326 destroy_text_window(struct xwindow
*wp
)
328 /* Don't need to pop down, this only called from dismiss_text(). */
330 struct text_info_t
*text_info
= wp
->text_information
;
333 * If the text window was blocked, then the user has already ACK'ed
334 * it and we are free to really destroy the window. Otherwise, don't
335 * destroy until the user dismisses the window via a key or button
338 if (text_info
->blocked
|| text_info
->destroy_on_ack
) {
339 XtDestroyWidget(wp
->popup
);
340 free_text_buffer(&text_info
->text
);
341 free((genericptr_t
) text_info
), wp
->text_information
= 0;
342 wp
->type
= NHW_NONE
; /* allow reuse */
344 text_info
->destroy_on_ack
= TRUE
; /* destroy on next ACK */
349 clear_text_window(struct xwindow
*wp
)
351 clear_text_buffer(&wp
->text_information
->text
);
354 /* text buffer routines ----------------------------------------------------
357 /* Append a line to the text buffer. */
359 append_text_buffer(struct text_buffer
*tb
, const char *str
, boolean concat
)
365 panic("append_text_buffer: null text buffer");
368 length
= strlen(str
);
373 if (length
+ tb
->text_last
+ 1 >= tb
->text_size
) {
374 /* we need to go to a bigger buffer! */
377 "append_text_buffer: text buffer growing from %d to %d bytes\n",
378 tb
->text_size
, 2 * tb
->text_size
);
380 copy
= (char *) alloc((unsigned) tb
->text_size
* 2);
381 (void) memcpy(copy
, tb
->text
, tb
->text_last
);
387 if (tb
->num_lines
) { /* not first --- append a newline */
390 if (concat
&& !strchr("!.?'\")", tb
->text
[tb
->text_last
- 1])) {
392 tb
->num_lines
--; /* offset increment at end of function */
395 *(tb
->text
+ tb
->text_last
) = appchar
;
400 (void) memcpy((tb
->text
+ tb
->text_last
), str
, length
+ 1);
402 /* Remove all newlines. Otherwise we have a confused line count. */
403 copy
= (tb
->text
+ tb
->text_last
);
404 while ((copy
= strchr(copy
, '\n')) != (char *) 0)
408 tb
->text_last
+= length
;
410 tb
->text
[tb
->text_last
] = '\0';
414 /* Initialize text buffer. */
416 init_text_buffer(struct text_buffer
*tb
)
418 tb
->text
= (char *) alloc(START_SIZE
);
420 tb
->text_size
= START_SIZE
;
425 /* Empty the text buffer */
427 clear_text_buffer(struct text_buffer
*tb
)
434 /* Free up allocated memory. */
436 free_text_buffer(struct text_buffer
*tb
)
439 tb
->text
= (char *) 0;
445 #ifdef GRAPHIC_TOMBSTONE
447 static void rip_exposed(Widget
, XtPointer
, XtPointer
);
449 static XImage
*rip_image
= 0;
451 #define STONE_LINE_LEN 16 /* # chars that fit on one line */
452 #define NAME_LINE 0 /* line # for player name */
453 #define GOLD_LINE 1 /* line # for amount of gold */
454 #define DEATH_LINE 2 /* line # for death description */
455 #define YEAR_LINE 6 /* line # for year */
457 static char rip_line
[YEAR_LINE
+ 1][STONE_LINE_LEN
+ 1];
460 calculate_rip_text(int how
, time_t when
)
462 /* Follows same algorithm as genl_outrip() */
469 /* Put name on stone */
470 Sprintf(rip_line
[NAME_LINE
], "%.16s", svp
.plname
); /* STONE_LINE_LEN */
473 cash
= max(gd
.done_money
, 0L);
474 /* arbitrary upper limit; practical upper limit is quite a bit less */
475 if (cash
> 999999999L)
477 Sprintf(buf
, "%ld Au", cash
);
478 Sprintf(rip_line
[GOLD_LINE
], "%ld Au", cash
);
480 /* Put together death description */
481 formatkiller(buf
, sizeof buf
, how
, FALSE
);
483 /* Put death type on stone */
484 for (line
= DEATH_LINE
, dpx
= buf
; line
< YEAR_LINE
; line
++) {
488 if ((i0
= strlen(dpx
)) > STONE_LINE_LEN
) {
489 for (i
= STONE_LINE_LEN
; ((i0
> STONE_LINE_LEN
) && i
); i
--)
497 strcpy(rip_line
[line
], dpx
);
498 if (tmpchar
!= ' ') {
505 /* Put year on stone */
506 year
= (int) ((yyyymmdd(when
) / 10000L) % 10000L);
507 Sprintf(rip_line
[YEAR_LINE
], "%4d", year
);
511 * RIP image expose callback.
515 rip_exposed(Widget w
, XtPointer client_data UNUSED
,
516 XtPointer widget_data
) /* expose event from Window widget */
518 XExposeEvent
*event
= (XExposeEvent
*) widget_data
;
519 Display
*dpy
= XtDisplay(w
);
524 static Pixmap rip_pixmap
= None
;
527 if (!XtIsRealized(w
) || event
->count
> 0)
530 if (rip_pixmap
== None
&& rip_image
) {
531 rip_pixmap
= XCreatePixmap(dpy
, XtWindow(w
), rip_image
->width
,
533 DefaultDepth(dpy
, DefaultScreen(dpy
)));
534 XPutImage(dpy
, rip_pixmap
, DefaultGC(dpy
, DefaultScreen(dpy
)),
535 rip_image
, 0, 0, 0, 0, /* src, dest top left */
536 rip_image
->width
, rip_image
->height
);
537 XDestroyImage(rip_image
); /* data bytes free'd also */
540 mask
= GCFunction
| GCForeground
| GCGraphicsExposures
| GCFont
;
541 values
.graphics_exposures
= False
;
542 XtSetArg(args
[0], XtNforeground
, &values
.foreground
);
543 XtGetValues(w
, args
, 1);
544 values
.function
= GXcopy
;
545 values
.font
= WindowFont(w
);
546 ggc
= XtGetGC(w
, mask
, &values
);
548 if (rip_pixmap
!= None
) {
549 XCopyArea(dpy
, rip_pixmap
, XtWindow(w
), ggc
, event
->x
, event
->y
,
550 event
->width
, event
->height
, event
->x
, event
->y
);
553 x
= appResources
.tombtext_x
;
554 y
= appResources
.tombtext_y
;
555 for (i
= 0; i
<= YEAR_LINE
; i
++) {
556 int len
= strlen(rip_line
[i
]);
557 XFontStruct
*font
= WindowFontStruct(w
);
558 int width
= XTextWidth(font
, rip_line
[i
], len
);
560 XDrawString(dpy
, XtWindow(w
), ggc
, x
- width
/ 2, y
, rip_line
[i
], len
);
561 x
+= appResources
.tombtext_dx
;
562 y
+= appResources
.tombtext_dy
;
569 * The ripout window creation routine.
572 create_ripout_widget(Widget parent
)
578 static int rip_width
, rip_height
;
581 XpmAttributes attributes
;
584 attributes
.valuemask
= XpmCloseness
;
585 attributes
.closeness
= 65535; /* Try anything */
586 errorcode
= XpmReadFileToImage(XtDisplay(parent
),
587 appResources
.tombstone
,
588 &rip_image
, 0, &attributes
);
589 if (errorcode
!= XpmSuccess
) {
592 Sprintf(buf
, "Failed to load %s: %s", appResources
.tombstone
,
593 XpmGetErrorString(errorcode
));
597 rip_width
= rip_image
->width
;
598 rip_height
= rip_image
->height
;
602 XtSetArg(args
[num_args
], XtNwidth
, rip_width
);
604 XtSetArg(args
[num_args
], XtNheight
, rip_height
);
606 XtSetArg(args
[num_args
], XtNtranslations
,
607 XtParseTranslationTable(rip_translations
));
610 imageport
= XtCreateManagedWidget("rip", windowWidgetClass
, parent
, args
,
613 XtAddCallback(imageport
, XtNexposeCallback
, rip_exposed
, (XtPointer
) 0);
618 #endif /* GRAPHIC_TOMBSTONE */