/*	SCCS Id: @(#)wintext.c	3.1	92/3/7
/* Copyright (c) Dean Luick, 1992				  */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * File for dealing with text windows.
 * 
 * 	+ No global functions.
 */
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xos.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Cardinals.h>

#include "hack.h"
#include "winX.h"


#define TRANSIENT_TEXT	/* text window is a transient window (no positioning) */

static const char text_translations[] =
    "#override\n\
     <BtnDown>: dismiss_text()\n\
     <Key>: key_dismiss_text()";

/*
 * Callback used for all text windows.  The window is poped down on any key
 * or button down event.  It is destroyed if the main nethack code is done
 * with it.
 */
/*ARGSUSED*/
void
dismiss_text(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    struct xwindow *wp;
    struct text_info_t *text_info;
    Widget popup = XtParent(w);

    wp = find_widget(w);
    text_info = wp->text_information;

    nh_XtPopdown(popup);

    if (text_info->blocked) {
	exit_x_event = TRUE;
    } else if (text_info->destroy_on_ack) {
	destroy_text_window(wp);
    }
}

/* Dismiss when a non-modifier key pressed. */
void
key_dismiss_text(w, event, params, num_params)
    Widget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    char ch = key_event_to_char((XKeyEvent *) event);
    if (ch) dismiss_text(w, event, params, num_params);
}

/* ARGSUSED */
void
add_to_text_window(wp, attr, str)
    struct xwindow *wp;
    int attr;	/* currently unused */
    const char *str;
{
    struct text_info_t *text_info = wp->text_information;
    int width;

    append_text_buffer(&text_info->text, str, FALSE);

    /* Calculate text width and save longest line */
    width = XTextWidth(text_info->fs, str, (int) strlen(str));
    if (width > text_info->max_width)
	text_info->max_width = width;
}

void
display_text_window(wp, blocking)
    struct xwindow *wp;
    boolean blocking;
{
    struct text_info_t *text_info;
    Arg args[8];
    Cardinal num_args;
    Dimension width, height;
    int nlines;

    text_info = wp->text_information;
    width  = text_info->max_width + text_info->extra_width;
    text_info->blocked = blocking;
    text_info->destroy_on_ack = FALSE;

    /*
     * Calculate the number of lines to use.  First, find the number of
     * lines that would fit on the screen.  Next, remove four of these
     * lines to give room for a possible window manager titlebar (some
     * wm's put a titlebar on transient windows).  Make sure we have
     * _some_ lines.  Finally, use the number of lines in the text if
     * there are fewer than the max.
     */
    nlines = (XtScreen(wp->w)->height - text_info->extra_height) /
			(text_info->fs->ascent + text_info->fs->descent);
    nlines -= 4;
    if (nlines <= 0) nlines = 1;

    if (nlines > text_info->text.num_lines)
	nlines = text_info->text.num_lines;

    /* Font height is ascent + descent. */
    height = (nlines * (text_info->fs->ascent + text_info->fs->descent))
						    + text_info->extra_height;

    num_args = 0;

    if (nlines < text_info->text.num_lines) {
	/* add on width of scrollbar.  Really should look this up,
	 * but can't until the window is realized.  Chicken-and-egg problem.
	 */
	width += 20;
    }

    if (width > (Dimension) XtScreen(wp->w)->width) { /* too wide for screen */
	/* Back off some amount - we really need to back off the scrollbar */
	/* width plus some extra.					   */
	width = XtScreen(wp->w)->width - 20;
    }
    XtSetArg(args[num_args], XtNstring, text_info->text.text);	num_args++;
    XtSetArg(args[num_args], XtNwidth,  width);			num_args++;
    XtSetArg(args[num_args], XtNheight, height);		num_args++;
    XtSetValues(wp->w, args, num_args);

#ifdef TRANSIENT_TEXT
    XtRealizeWidget(wp->popup);
    positionpopup(wp->popup);
#endif

    nh_XtPopup(wp->popup, XtGrabNone, wp->w);

    /* Kludge alert.  Scrollbars are not sized correctly by the Text widget */
    /* if added before the window is displayed, so do it afterward. */
    num_args = 0;
    if (nlines < text_info->text.num_lines) {	/* add vert scrollbar */
	XtSetArg(args[num_args], XtNscrollVertical, XawtextScrollAlways);
								num_args++;
    }
    if (width >= (Dimension) (XtScreen(wp->w)->width-20)) {	/* too wide */
	XtSetArg(args[num_args], XtNscrollHorizontal, XawtextScrollAlways);
								num_args++;
    }
    if (num_args) XtSetValues(wp->w, args, num_args);

    /* We want the user to acknowlege. */
    if (blocking) {
	(void) x_event(EXIT_ON_EXIT);
	nh_XtPopdown(wp->popup);
    }
}


void
create_text_window(wp)
    struct xwindow *wp;
{
    struct text_info_t *text_info;
    Arg args[8];
    Cardinal num_args;
    Position top_margin, bottom_margin, left_margin, right_margin;

    wp->type = NHW_TEXT;

    wp->text_information = text_info = 
		    (struct text_info_t *) alloc(sizeof(struct text_info_t));

    init_text_buffer(&text_info->text);
    text_info->max_width      = 0;
    text_info->extra_width    = 0;
    text_info->extra_height   = 0;
    text_info->blocked	      = FALSE;
    text_info->destroy_on_ack = TRUE;	/* Ok to destroy before display */

    num_args = 0;
    XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;

#ifdef TRANSIENT_TEXT
    wp->popup = XtCreatePopupShell("text", transientShellWidgetClass,
				   toplevel, args, num_args);
#else
    wp->popup = XtCreatePopupShell("text", topLevelShellWidgetClass,
				   toplevel, args, num_args);
#endif

    num_args = 0;
    XtSetArg(args[num_args], XtNdisplayCaret, False);		num_args++;
    XtSetArg(args[num_args], XtNresize, XawtextResizeBoth);	num_args++;
    XtSetArg(args[num_args], XtNtranslations,
		XtParseTranslationTable(text_translations));	num_args++;

    wp->w = XtCreateManagedWidget(
		killer && WIN_MAP == WIN_ERR ?
				  "tombstone" : "text_text", /* name */
		asciiTextWidgetClass,
		wp->popup,		/* parent widget */
		args,			/* set some values */
		num_args);		/* number of values to set */

    /* Get the font and margin information. */
    num_args = 0;
    XtSetArg(args[num_args], XtNfont,	      &text_info->fs); num_args++;
    XtSetArg(args[num_args], XtNtopMargin,    &top_margin);    num_args++;
    XtSetArg(args[num_args], XtNbottomMargin, &bottom_margin); num_args++;
    XtSetArg(args[num_args], XtNleftMargin,   &left_margin);   num_args++;
    XtSetArg(args[num_args], XtNrightMargin,  &right_margin);  num_args++;
    XtGetValues(wp->w, args, num_args);

    text_info->extra_width  = left_margin + right_margin;
    text_info->extra_height = top_margin + bottom_margin;
}

void
destroy_text_window(wp)
    struct xwindow *wp;
{
    /* Don't need to pop down, this only called from dismiss_text(). */

    struct text_info_t *text_info = wp->text_information;

    /*
     * If the text window was blocked, then the user has already ACK'ed
     * it and we are free to really destroy the window.  Otherwise, don't
     * destroy until the user dismisses the window via a key or button
     * press.
     */
    if (text_info->blocked || text_info->destroy_on_ack) {
	XtDestroyWidget(wp->popup);
	free_text_buffer(&text_info->text);
	free((char *) text_info);
	wp->type = NHW_NONE;	/* allow reuse */
    } else {
	text_info->destroy_on_ack = TRUE;	/* destroy on next ACK */
    }
}


/* text buffer routines ---------------------------------------------------- */

/* Append a line to the text buffer. */
void
append_text_buffer(tb, str, concat)
    struct text_buffer *tb;
    const char *str;
    boolean concat;
{
    char *copy;
    int length;

    if (!tb->text) panic("append_text_buffer:  null text buffer");

    if (str) {
    	length = strlen(str);
    } else {
	length = 0;
    }

    if (length + tb->text_last + 1 >= tb->text_size) {
	/* we need to go to a bigger buffer! */
#ifdef VERBOSE
	printf("append_text_buffer: text buffer growing from %d to %d bytes\n",
				tb->text_size, 2*tb->text_size);
#endif
	copy = (char *) alloc(tb->text_size*2);
	(void) memcpy(copy, tb->text, tb->text_last);
	free(tb->text);
	tb->text = copy;
	tb->text_size *= 2;
    }

    if (tb->num_lines) {	/* not first --- append a newline */
	char appchar = '\n';

	if(concat && !index("!.?'\")", tb->text[tb->text_last-1])) {
	    appchar = ' ';
	    tb->num_lines--; /* offset increment at end of function */
	}

	*(tb->text + tb->text_last) = appchar;
	tb->text_last++;
    }

    if (str) {
	(void) memcpy((tb->text+tb->text_last), str, length+1);
	if(length) {
	    /* Remove all newlines. Otherwise we have a confused line count. */
	    copy = (tb->text+tb->text_last);
	    while (copy = index(copy, '\n'))
		*copy = ' ';
	}

	tb->text_last += length;
    }
    tb->text[tb->text_last] = '\0';
    tb->num_lines++;
}

/* Initialize text buffer. */
void
init_text_buffer(tb)
    struct text_buffer *tb;
{
    tb->text	  = (char *) alloc(START_SIZE);
    tb->text[0]   = '\0';
    tb->text_size = START_SIZE;
    tb->text_last = 0;
    tb->num_lines = 0;
}

/* Empty the text buffer */
void
clear_text_buffer(tb)
    struct text_buffer *tb;
{
    tb->text_last = 0;
    tb->text[0]   = '\0';
    tb->num_lines = 0;
}

/* Free up allocated memory. */
void
free_text_buffer(tb)
    struct text_buffer *tb;
{
    free(tb->text);
    tb->text = (char *) 0;
    tb->text_size = 0;
    tb->text_last = 0;
    tb->num_lines = 0;
}
