/* File: main-xaw.c */

/* Purpose: Support for X Athena Widget based Angband */
/* Most code written by Torbjn Lindgren (tl@cd.chalmers.se) */

#ifdef USE_XAW

#include "angband.h"


#ifndef __MAKEDEPEND__
#include <X11/Xlib.h>
#include <X11/StringDefs.h>
#include <X11/Xutil.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/IntrinsicP.h>
#include <X11/CoreP.h>
#include <X11/ShellP.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/SimpleP.h>
#include <X11/Xaw/Simple.h>
#include <X11/Xaw/XawInit.h>
#endif /* __MAKEDEPEND__ */



/*****************************************************
 *
 * Resource description
 *
 *****************************************************/

/* Resources:

Name                Class              RepType         Default Value
----                -----              -------         -------------
background          Background         Pixel           XtDefaultBackground
border              BorderColor        Pixel           XtDefaultForeground
borderWidth         BorderWidth        Dimension       1
cursor              Cursor             Cursor          None
cursorName          Cursor             String          NULL
destroyCallback     Callback           Pointer         NULL
height              Height             Dimension       0
insensitiveBorder   Insensitive        Pixmap          Gray
mappedWhenManaged   MappedWhenManaged  Boolean         True
pointerColor        Foreground         Pixel           XtDefaultForeground
pointerColorBackground Background      Pixel           XtDefaultBackground
sensitive           Sensitive          Boolean         True
width               Width              Dimension       0
x                   Position           Position        0
y                   Position           Position        0

 */

/*

My own X Resources look like this (on a 1152x900 screen):

angband*angband*font:                   12x24
angband*angband*geometry:               +0+-20
angband*recall*font:                    7x13
angband*recall*geometry:                80x10+0+586
angband*choice*font:                    7x13
angband*choice*geometry:                -0-0

It's also possible to change the colors using X Resources, the
standard colors would look like:

angband*color0:                         #000000
angband*color1:                         #ffffff
angband*color2:                         #a6a6a6
angband*color3:                         #ff6302
angband*color4:                         #ca0808
angband*color5:                         #008e18
angband*color6:                         #0000e3
angband*color7:                         #814007
angband*color8:                         #6b6b6b
angband*color9:                         #d6d6d6
angband*color10:                        #5100c2
angband*color11:                        #fdf105
angband*color12:                        #ff9259
angband*color13:                        #26cf17
angband*color14:                        #02b2f2
angband*color15:                        #b28b48

And the newer colors look like:

angband*color0:                         #000000
angband*color1:                         #ffffff
angband*color2:                         #d7d7d7
angband*color3:                         #ff9200
angband*color4:                         #ff0000
angband*color5:                         #00cd00
angband*color6:                         #0000fe
angband*color7:                         #c86400
angband*color8:                         #a3a3a3
angband*color9:                         #ebebeb
angband*color10:                        #a500ff
angband*color11:                        #fffd00
angband*color12:                        #ff00bc
angband*color13:                        #00ff00
angband*color14:                        #00c8ff
angband*color15:                        #ffcc80

Some older monochrome monitors have problem with white text on black
background. The new code can handle the reverse situation if the user
wants/needs this.

The following X Resources gives black text on white background using
Angband/Xaw. The other colors (2-15) isn't changed, since they're not
used on a monochrome monitor.

angband*color0: #ffffff
angband*color1: #000000

 */

/* New resource names */
#define XtNstartRows        "startRows"
#define XtNstartColumns     "startColumns"
#define XtNminRows          "minRows"
#define XtNminColumns       "minColumns"
#define XtNmaxRows          "maxRows"
#define XtNmaxColumns       "maxColumns"
#define XtNinternalBorder   "internalBorder"
#define XtNcolor0           "color0"
#define XtNcolor1           "color1"
#define XtNcolor2           "color2"
#define XtNcolor3           "color3"
#define XtNcolor4           "color4"
#define XtNcolor5           "color5"
#define XtNcolor6           "color6"
#define XtNcolor7           "color7"
#define XtNcolor8           "color8"
#define XtNcolor9           "color9"
#define XtNcolor10          "color10"
#define XtNcolor11          "color11"
#define XtNcolor12          "color12"
#define XtNcolor13          "color13"
#define XtNcolor14          "color14"
#define XtNcolor15          "color15"
#define XtNredrawCallback   "redrawCallback"

/* External definitions */
#define COLOR_XOR 16
#define NUM_COLORS 16

/* C Widget type definition */

typedef struct AngbandRec *AngbandWidget;

/* C Widget class type definition */

typedef struct AngbandClassRec *AngbandWidgetClass;


/*
 * New fields for the Angband widget record
 */

typedef struct
{
	/* Settable resources */
	int               start_rows;
	int               start_columns;
	int               min_rows;
	int               min_columns;
	int               max_rows;
	int               max_columns;
	int               internal_border;
	String            font;
	Pixel             color[NUM_COLORS];
	XtCallbackList    redraw_callbacks;

	/* Private state */
	XFontStruct       *fnt;
	Dimension         fontheight;
	Dimension         fontwidth;
	Dimension         fontascent;
	GC                gc[NUM_COLORS+1];  /* Includes a special 'xor' color */

} AngbandPart;


/*
 * Full instance record declaration
 */

typedef struct AngbandRec AngbandRec;

struct AngbandRec
{
	CorePart          core;
	SimplePart        simple;
	AngbandPart       angband;
};


/*
 * New fields for the Angband widget class record
 */

typedef struct AngbandClassPart AngbandClassPart;

struct AngbandClassPart
{
	int               dummy;
};


/*
 * Full class record declaration
 */

typedef struct AngbandClassRec AngbandClassRec;

struct AngbandClassRec
{
	CoreClassPart     core_class;
	SimpleClassPart   simple_class;
	AngbandClassPart  angband_class;
};



/* Angband widget, Created by Torbjn Lindgren (tl@cd.chalmers.se) */

/*
 * Note that it isn't as self-contained as it really should be,
 * originally everything was output to a Pixmap which was later copied
 * to the screen when necessary. I had to abandon that idea since
 * Pixmaps creates big performance problems for some really old X
 * terminals (such as 3/50's running Xkernel).
 */


/*
 * The default colors used is based on the ones used in main-mac.c.
 * The main difference is that they are gamma corrected for a gamma of
 * 1.6.  MacOS do gamma correction afterwards, but X uses raw
 * colors. The Gamma of most color screens are about 1.5 - 1.7.
 * Color 12 was later changed a bit so that it didn't look as similar
 * to color 3/4.
 */

#define offset(field) XtOffsetOf(AngbandRec, angband.field)

/*
 * Fallback resources for Angband widget
 */
static XtResource resources[] =
{
	{ XtNstartRows, XtCValue, XtRInt, sizeof(int),
	offset(start_rows), XtRImmediate, (XtPointer) 24 },
	{ XtNstartColumns, XtCValue, XtRInt, sizeof(int),
	offset(start_columns), XtRImmediate, (XtPointer) 80 },
	{ XtNminRows, XtCValue, XtRInt, sizeof(int),
	offset(min_rows), XtRImmediate, (XtPointer) 1 },
	{ XtNminColumns, XtCValue, XtRInt, sizeof(int),
	offset(min_columns), XtRImmediate, (XtPointer) 1 },
	{ XtNmaxRows, XtCValue, XtRInt, sizeof(int),
	offset(max_rows), XtRImmediate, (XtPointer) 24 },
	{ XtNmaxColumns, XtCValue, XtRInt, sizeof(int),
	offset(max_columns), XtRImmediate, (XtPointer) 80 },
	{ XtNinternalBorder, XtCValue, XtRInt, sizeof(int),
	offset(internal_border), XtRImmediate, (XtPointer) 2 },
	{ XtNfont, XtCFont, XtRString, sizeof(char *),
	offset(font), XtRString, "9x15" },
	{ XtNcolor0, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[0]), XtRString, "black" },
	{ XtNcolor1, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[1]), XtRString, "white" },
	{ XtNcolor2, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[2]), XtRString, "#d7d7d7" },
	{ XtNcolor3, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[3]), XtRString, "#ff9200" },
	{ XtNcolor4, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[4]), XtRString, "#ff0000" },
	{ XtNcolor5, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[5]), XtRString, "#00cd00" },
	{ XtNcolor6, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[6]), XtRString, "#0000fe" },
	{ XtNcolor7, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[7]), XtRString, "#c86400" },
	{ XtNcolor8, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[8]), XtRString, "#a3a3a3" },
	{ XtNcolor9, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[9]), XtRString, "#ebebeb" },
	{ XtNcolor10, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[10]), XtRString, "#a500ff" },
	{ XtNcolor11, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[11]), XtRString, "#fffd00" },
	{ XtNcolor12, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[12]), XtRString, "#ff00bc" },
	{ XtNcolor13, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[13]), XtRString, "#00ff00" },
	{ XtNcolor14, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[14]), XtRString, "#00c8ff" },
	{ XtNcolor15, XtCColor, XtRPixel, sizeof(Pixel),
	offset(color[15]), XtRString, "#ffcc80" },

	{ XtNredrawCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(redraw_callbacks), XtRCallback, (XtPointer)NULL }
};

#undef offset

/* Forward declarations for Widget functions */
static void Initialize(AngbandWidget request, AngbandWidget new);
static void Redisplay(AngbandWidget w, XEvent *event, Region region);
static Boolean SetValues(AngbandWidget current, AngbandWidget request,
			 AngbandWidget new, ArgList args, Cardinal *num_args);
static void Destroy(AngbandWidget widget);

/* Forward declaration for internal functions */
static void calculateSizeHints(AngbandWidget new);
static XFontStruct *getFont(AngbandWidget widget,
			    String font, Boolean fallback);


/* Class record constanst */
AngbandClassRec angbandClassRec =
{
	{
		/* Core class fields initialization */
#define superclass              (&simpleClassRec)
		/* superclass           */      (WidgetClass) superclass,
		/* class_name           */      "Angband",
		/* widget_size          */      sizeof(AngbandRec),
		/* class_initialize     */      NULL,
		/* class_part_initialize*/      NULL,
		/* class_inited         */      FALSE,
		/* initialize           */      (XtInitProc) Initialize,
		/* initialize_hook      */      NULL,
		/* realize              */      XtInheritRealize,
		/* actions              */      NULL,
		/* num_actions          */      0,
		/* resources            */      resources,
		/* num_resources        */      XtNumber(resources),
		/* xrm_class            */      NULLQUARK,
		/* compress_motion      */      TRUE,
		/* compress_exposure    */      XtExposeCompressMultiple,
		/* compress_enterleave  */      TRUE,
		/* visible_interest     */      FALSE,
		/* destroy              */      (XtWidgetProc) Destroy,
		/* resize               */      NULL,
		/* expose               */      (XtExposeProc) Redisplay,
		/* set_values           */      (XtSetValuesFunc) SetValues,
		/* set_values_hook      */      NULL,
		/* set_values_almost    */      XtInheritSetValuesAlmost,
		/* get_values_hook      */      NULL,
		/* accept_focus         */      NULL,
		/* version              */      XtVersion,
		/* callback_private     */      NULL,
		/* tm_table             */      NULL,
		/* query_geometry       */      NULL,
		/* display_accelerator  */      XtInheritDisplayAccelerator,
		/* extension            */      NULL
	},
	/* Simple class fields initialization */
	{
		/* change_sensitive     */      XtInheritChangeSensitive
	},
	/* Angband class fields initialization */
	{
		/* nothing              */      0
	}
};

/* Class record pointer */
WidgetClass angbandWidgetClass = (WidgetClass) &angbandClassRec;


/*
 * Public procedures
 */
static void AngbandOutputText(AngbandWidget widget, int x, int y,
			      String txt, int len, int color)
{
	/* Do nothing if the string is null */
	if (!txt || !*txt)
		return;

	/* Check the lenght, and fix it if it's below zero */
	if (len < 0)
		len = strlen(txt);

	/* Figure out where to place the text */
	y = y * widget->angband.fontheight + widget->angband.fontascent +
	widget->angband.internal_border;
	x = x * widget->angband.fontwidth + widget->angband.internal_border;

	/* Place the string */
	XDrawImageString (XtDisplay(widget), XtWindow(widget),
			  widget->angband.gc[color], x, y, txt, len);
}

static void AngbandClearArea(AngbandWidget widget,
			     int x, int y, int w, int h, int color)
{
	/* Figure out which area to clear */
	y = y * widget->angband.fontheight + widget->angband.internal_border;
	x = x * widget->angband.fontwidth + widget->angband.internal_border;

	/* Clear the area */
	XFillRectangle(XtDisplay(widget), XtWindow(widget),
		       widget->angband.gc[color],
		       x, y, widget->angband.fontwidth*w,
		       widget->angband.fontheight*h);
}

/*
 * Private procedures
 */

/*
 * Procedure Initialize() is called during the widget creation
 * process.  Initialize() load fonts and calculates window geometry.
 * The request parameter is filled in by parents to this widget.  The
 * new parameter is the request parameter plus data filled in by this
 * widget. All changes should be done to the new parameter.
 */
static void Initialize(AngbandWidget request, AngbandWidget new)
{
	XGCValues gcv;
	int depth = DefaultDepthOfScreen(XtScreen((Widget) new));
	TopLevelShellWidget parent =
	(TopLevelShellWidget)XtParent((Widget) new);
	int n;

	/* Fix the background color */
	new->core.background_pixel = new->angband.color[0];

	/* Get some information about the font */
	new->angband.fnt = getFont(new, new->angband.font, TRUE);
	new->angband.fontheight = new->angband.fnt->ascent +
	new->angband.fnt->descent;
	new->angband.fontwidth = new->angband.fnt->max_bounds.width;
	new->angband.fontascent = new->angband.fnt->ascent;

	/* Create and initialize the graphics contexts */ /* GXset? */
	gcv.font = new->angband.fnt->fid;
	gcv.graphics_exposures = FALSE;
	gcv.background = new->angband.color[0];
	for (n = 0; n < NUM_COLORS; n++)
	{
		if (depth == 1 && n >= 1)
			gcv.foreground = new->angband.color[1];
		else
			gcv.foreground = new->angband.color[n];
		new->angband.gc[n] = XtGetGC((Widget)new, GCFont | GCForeground |
					     GCBackground | GCGraphicsExposures,
					     &gcv);
	}

	/* Create a special GC for highlighting */
	gcv.foreground = BlackPixelOfScreen(XtScreen((Widget)new)) ^
	WhitePixelOfScreen(XtScreen((Widget)new));
	gcv.function = GXxor;
	new->angband.gc[NUM_COLORS] = XtGetGC((Widget)new, GCFunction |
					      GCGraphicsExposures |
					      GCForeground, &gcv);

	/* Calculate window geometry */
	new->core.height = new->angband.start_rows * new->angband.fontheight +
	2 * new->angband.internal_border;
	new->core.width = new->angband.start_columns * new->angband.fontwidth +
	2 * new->angband.internal_border;

	/* We need to be able to resize the Widget if the user want's to
	change font on the fly! */
	parent->shell.allow_shell_resize = TRUE;

	/* Calculates all the size hints */
	calculateSizeHints(new);
}

/*
 * Procedure Destroy() is called during the destruction of the widget.
 * Destroy() releases and frees GCs, frees the pixmaps and frees the
 * fonts.
 */
static void Destroy(AngbandWidget widget)
{
	int n;

	/* Free all GC's */
	for (n = 0; n < NUM_COLORS+1; n++)
		XtReleaseGC((Widget)widget, widget->angband.gc[n]);

	/* Free the font */
	XFreeFont(XtDisplay((Widget)widget), widget->angband.fnt);
}

/*
 * Procedure Redisplay() is called as the result of an Expose event.
 * Use the redraw callback to do a full redraw
 */
static void Redisplay(AngbandWidget widget, XEvent *event, Region region)
{
	if (XtHasCallbacks((Widget)widget, XtNredrawCallback) == XtCallbackHasSome)
		XtCallCallbacks((Widget)widget, XtNredrawCallback, NULL);
}

/*
 * Font, colors and internal_border can be changed on the fly.
 * The entire widget is redrawn if any of those parameters change (all
 * can potentially have effects that spans the whole widget).
 */
static Boolean SetValues(AngbandWidget current, AngbandWidget request,
			 AngbandWidget new, ArgList args,
			 Cardinal *num_args)
{
	int depth = DefaultDepthOfScreen(XtScreen((Widget) new));
	Boolean font_changed = FALSE;
	Boolean border_changed = FALSE;
	Boolean color_changed = FALSE;
	XGCValues gcv;
	int height, width;
	int n;

	/* Changed font? */
	if (current->angband.font != new->angband.font)
	{
		/* Check if the font exists */
		new->angband.fnt = getFont(new, new->angband.font, FALSE);

		/* The font didn't exist */
		if (new->angband.fnt == NULL)
		{
			new->angband.fnt = current->angband.fnt;
			new->angband.font = current->angband.font;
			XtWarning("Couldn't find the request font!");
		}
		else
		{
			font_changed = TRUE;
			/* Free the old font */
			XFreeFont(XtDisplay((Widget)new), current->angband.fnt);
			/* Update font information */
			new->angband.fontheight = new->angband.fnt->ascent +
			new->angband.fnt->descent;
			new->angband.fontwidth = new->angband.fnt->max_bounds.width;
			new->angband.fontascent = new->angband.fnt->ascent;
		}
	}

	/* Check all colors, if one or more has changed the redo all GC's */
	for (n = 0; n < NUM_COLORS; n++)
		if (current->angband.color[n] != new->angband.color[n])
			color_changed = TRUE;

	/* Change all GC's if color or font has changed */
	if (color_changed || font_changed)
	{
		gcv.font = new->angband.fnt->fid;
		gcv.graphics_exposures = FALSE;
		gcv.background = new->angband.color[0];

		/* Do all GC's */
		for (n = 0; n < NUM_COLORS; n++)
		{
			if (depth == 1 && n >= 1)
				gcv.foreground = new->angband.color[1];
			else
				gcv.foreground = new->angband.color[n];
			/* Release the old GC */
			XtReleaseGC((Widget)current, current->angband.gc[n]);
			/* Get the new GC */
			new->angband.gc[n] = XtGetGC((Widget)new, GCFont | GCForeground |
						     GCBackground | GCGraphicsExposures,
						     &gcv);
		}

		/* Replace the old XOR/highlighting GC */
		gcv.foreground = BlackPixelOfScreen(XtScreen((Widget)new)) ^
		WhitePixelOfScreen(XtScreen((Widget)new));
		gcv.function = GXxor;
		XtReleaseGC((Widget)current, current->angband.gc[NUM_COLORS]);
		new->angband.gc[NUM_COLORS] = XtGetGC((Widget)new, GCFunction |
						      GCGraphicsExposures |
						      GCForeground, &gcv);
		/* Fix the background color */
		new->core.background_pixel = new->angband.color[0];
	}

	/* Check if internal border width has changed, used later */
	if (current->angband.internal_border != new->angband.internal_border)
		border_changed = TRUE;


	/* If the font or the internal border has changed, all geometry
	has to be recalculated */
	if (font_changed || border_changed)
	{
		/* Change window size */
		height = (current->core.height - 2 * current->angband.internal_border) /
		current->angband.fontheight * new->angband.fontheight +
		2 * current->angband.internal_border;
		width = (current->core.width -  2 * current->angband.internal_border) /
		current->angband.fontwidth * new->angband.fontwidth +
		2 * new->angband.internal_border;

		/* Get the new width */
		if (XtMakeResizeRequest((Widget)new, width, height, NULL, NULL) ==
		    XtGeometryNo)
		{
			/* Not allowed */
			XtWarning("Size change denied!");
		}
		else
		{
			/* Recalculate size hints */
			calculateSizeHints(new);
		}
	}

	/* Tell it to redraw the widget if anything has changed */
	return (font_changed || color_changed || border_changed);
}

/*
 * Calculate size hints
 */
static void calculateSizeHints(AngbandWidget new)
{
	TopLevelShellWidget parent =
	(TopLevelShellWidget)XtParent((Widget) new);

	/* Calculate minimum size */
	parent->wm.size_hints.min_height =
	new->angband.min_rows * new->angband.fontheight +
	2 * new->angband.internal_border;
	parent->wm.size_hints.min_width =
	new->angband.min_columns * new->angband.fontwidth +
	2 * new->angband.internal_border;
	parent->wm.size_hints.flags |= PMinSize;

	/* Calculate maximum size */
	parent->wm.size_hints.max_height =
	new->angband.max_rows * new->angband.fontheight +
	2 * new->angband.internal_border;
	parent->wm.size_hints.max_width =
	new->angband.max_columns * new->angband.fontwidth +
	2 * new->angband.internal_border;
	parent->wm.size_hints.flags |= PMaxSize;

	/* Calculate increment size */
	parent->wm.size_hints.height_inc = new->angband.fontheight;
	parent->wm.size_hints.width_inc = new->angband.fontwidth;
	parent->wm.size_hints.flags |= PResizeInc;

	/* Calculate base size */
	parent->wm.base_height = 2 * new->angband.internal_border;
	parent->wm.base_width = 2 * new->angband.internal_border;
	parent->wm.size_hints.flags |= PBaseSize;
}

/*
 * Load a font
 */
static XFontStruct *getFont(AngbandWidget widget,
			    String font, Boolean fallback)
{
	Display *dpy = XtDisplay((Widget) widget);
	char buf[256];
	XFontStruct *fnt = NULL;

	if (!(fnt = XLoadQueryFont(dpy, font)) && fallback)
	{
		sprintf(buf, "Can't find the font \"%s\", trying fixed\n", font);
		XtWarning(buf);
		if (!(fnt = XLoadQueryFont(dpy, "fixed")))
			XtError("Can't fint the font \"fixed\"!, bailing out\n");
	}

	return fnt;
}



/*** The non-widget code ****/

#ifndef IsModifierKey

/*
 * Keysym macros, used on Keysyms to test for classes of symbols
 * These were stolen from one of the X11 header files
 */

#define IsKeypadKey(keysym) \
  (((unsigned)(keysym) >= XK_KP_Space) && ((unsigned)(keysym) <= XK_KP_Equal))

#define IsCursorKey(keysym) \
  (((unsigned)(keysym) >= XK_Home)     && ((unsigned)(keysym) <  XK_Select))

#define IsPFKey(keysym) \
  (((unsigned)(keysym) >= XK_KP_F1)     && ((unsigned)(keysym) <= XK_KP_F4))

#define IsFunctionKey(keysym) \
  (((unsigned)(keysym) >= XK_F1)       && ((unsigned)(keysym) <= XK_F35))

#define IsMiscFunctionKey(keysym) \
  (((unsigned)(keysym) >= XK_Select)   && ((unsigned)(keysym) <  XK_KP_Space))

#define IsModifierKey(keysym) \
  (((unsigned)(keysym) >= XK_Shift_L)  && ((unsigned)(keysym) <= XK_Hyper_R))

#endif


/*
 * Checks if the keysym is a special key or a normal key
 * Assume that XK_MISCELLANY keysyms are special
 */
#define IsSpecialKey(keysym) \
  ((unsigned)(keysym) >= 0xFF00)



/*
 * Maximum number of windows XXX XXX XXX XXX
 */
#define MAX_TERM_DATA 8

/*
 * Number of fallback resources per window
 */
#define TERM_FALLBACKS 6

/*
 * Number of windows with special fallback resources
 */
#define SPECIAL_FALLBACKS 3

/*
 * Forward declare
 */
typedef struct term_data term_data;

/*
 * A structure for each "term"
 */
struct term_data
{
	term t;

	AngbandWidget widget;
};

/*
 * An array of term_data's
 */
static term_data data[MAX_TERM_DATA];

char *termNames[MAX_TERM_DATA] =
{
    "angband",
    "mirror",
    "recall",
    "choice",
    "term-4",
    "term-5",
    "term-6",
    "term-7"
};

Arg specialArgs[TERM_FALLBACKS][SPECIAL_FALLBACKS] =
{
    /* The main screen */
    {
	{ XtNstartRows,    24},
	{ XtNstartColumns, 80},
	{ XtNminRows,      24},
	{ XtNminColumns,   80},
	{ XtNmaxRows,      24},
	{ XtNmaxColumns,   80}
    },

    /* The "mirror" window */
    {
	{ XtNstartRows,    24},
	{ XtNstartColumns, 80},
	{ XtNminRows,      1},
	{ XtNminColumns,   1},
	{ XtNmaxRows,      24},
	{ XtNmaxColumns,   80}
    },

    /* The "recall" window */
    {
	{ XtNstartRows,    8},
	{ XtNstartColumns, 80},
	{ XtNminRows,      1},
	{ XtNminColumns,   1},
	{ XtNmaxRows,      24},
	{ XtNmaxColumns,   80}
    }
};

Arg defaultArgs[TERM_FALLBACKS] =
{
	{ XtNstartRows,    24},
	{ XtNstartColumns, 80},
	{ XtNminRows,      1},
	{ XtNminColumns,   1},
	{ XtNmaxRows,      24},
	{ XtNmaxColumns,   80}
};


/*
 * The application context
 */
XtAppContext appcon;

/*
 * User changable information about widgets
 */
static String fallback[] =
{
	"Angband.angband.iconName:            Angband",
	"Angband.angband.title:               Angband",
	"Angband.mirror.iconName:             Mirror",
	"Angband.mirror.title:                Mirror",
	"Angband.recall.iconName:             Recall",
	"Angband.recall.title:                Recall",
	"Angband.choice.iconName:             Choice",
	"Angband.choice.title:                Choice",
	"Angband.term-4.iconName:	      Term 4",
	"Angband.term-4.title:		      Term 4",
	"Angband.term-5.iconName:	      Term 5",
	"Angband.term-5.title:		      Term 5",
	"Angband.term-6.iconName:	      Term 6",
	"Angband.term-6.title:		      Term 6",
	"Angband.term-7.iconName:	      Term 7",
	"Angband.term-7.title:		      Term 7",
	NULL
};



/*
 * Do a redraw
 */
static void react_redraw(Widget widget,
			 XtPointer client_data, XtPointer call_data)
{
	term_data *old_td = (term_data*)(Term->data);
	term_data *td = (term_data*)client_data;

	/* Activate the proper Term */
	Term_activate(&td->t);

	/* Request a redraw */
	Term_redraw();

	/* Activate the old Term */
	Term_activate(&old_td->t);
}

/*
 * Process a keypress event
 */
static void react_keypress(XKeyEvent *ev)
{
	int i, n, mc, ms, mo, mx;

	KeySym ks;

	char buf[128];
	char msg[128];


	/* Check for "normal" keypresses */
	n = XLookupString(ev, buf, 125, &ks, NULL);

	/* Terminate */
	buf[n] = '\0';

	/* Extract four "modifier flags" */
	mc = (ev->state & ControlMask) ? TRUE : FALSE;
	ms = (ev->state & ShiftMask) ? TRUE : FALSE;
	mo = (ev->state & Mod1Mask) ? TRUE : FALSE;
	mx = (ev->state & Mod2Mask) ? TRUE : FALSE;

	/* Hack -- Ignore "modifier keys" */
	if (IsModifierKey(ks)) return;

	/* Normal keys with no modifiers */
	if (n && !mo && !mx && !IsSpecialKey(ks))
	{
		/* Enqueue the normal key(s) */
		for (i = 0; buf[i]; i++) Term_keypress(buf[i]);

		/* All done */
		return;
	}

	/* Handle a few standard keys */
	switch (ks)
	{
		case XK_Escape:
		Term_keypress(ESCAPE); return;

		case XK_Return:
		Term_keypress('\r'); return;

		case XK_Tab:
		Term_keypress('\t'); return;

		case XK_Delete:
		case XK_BackSpace:
		Term_keypress('\010'); return;
	}

	/* Hack -- Use the KeySym */
	if (ks)
	{
		sprintf(msg, "%c%s%s%s%s_%lX%c", 31,
			mc ? "N" : "", ms ? "S" : "",
			mo ? "O" : "", mx ? "M" : "",
			(unsigned long)(ks), 13);
	}

	/* Hack -- Use the Keycode */
	else
	{
		sprintf(msg, "%c%s%s%s%sK_%X%c", 31,
			mc ? "N" : "", ms ? "S" : "",
			mo ? "O" : "", mx ? "M" : "",
			ev->keycode, 13);
	}

	/* Enqueue the "fake" string */
	for (i = 0; msg[i]; i++)
		Term_keypress(msg[i]);

	/* Hack -- dump an "extra" string */
	if (n)
	{
		/* Start the "extra" string */
		Term_keypress(28);

		/* Enqueue the "real" string */
		for (i = 0; buf[i]; i++)
			Term_keypress(buf[i]);

		/* End the "extra" string */
		Term_keypress(28);
	}
}


static void handle_event (Widget widget, XtPointer client_data, XEvent *event,
			  Boolean *continue_to_dispatch)
{
	term_data *old_td = (term_data*)(Term->data);
	term_data *td = (term_data *)client_data;

	/* Continue to process the event by default */
	*continue_to_dispatch = TRUE;

	/* Activate the Term */
	Term_activate(&td->t);

	switch (event->type)
	{
		case KeyPress:
		react_keypress(&(event->xkey));
		*continue_to_dispatch = FALSE; /* We took care of the event */
		break;

		default:
		break;  /* Huh? Shouldn't happen! */
	}

	/* Activate the old term */
	Term_activate(&old_td->t);

	return;
}


/*
 * Process an event (or just check for one)
 */
errr CheckEvent(bool wait)
{
	XEvent event;

	/* No events ready, and told to just check */
	if (!wait && !XtAppPending(appcon)) return 1;

	/* Process */
	while (1)
	{
		XtAppNextEvent(appcon, &event);
		XtDispatchEvent(&event);
		if (!XtAppPending(appcon)) break;
	}

	return (0);
}


/*
 * Handle a "special request"
 */
static errr Term_xtra_xaw(int n, int v)
{
	int i;

	/* Handle a subset of the legal requests */
	switch (n)
	{
		/* Make a noise */
		case TERM_XTRA_NOISE:
		XBell(XtDisplay((Widget)data[0].widget), 100);
		return (0);

		/* Flush the output */
		case TERM_XTRA_FRESH:
		XFlush(XtDisplay((Widget)data[0].widget));
		/* Nonblock event-check so the flushed events can be showed */
		CheckEvent(FALSE);
		return (0);

		/* Process random events */
		case TERM_XTRA_BORED:
		return (CheckEvent(0));

		/* Process events */
		case TERM_XTRA_EVENT:
		return (CheckEvent(v));

		/* Flush events */
		case TERM_XTRA_FLUSH:
		while (!CheckEvent(FALSE));
		return (0);

		/* Delay */
		case TERM_XTRA_DELAY:
		usleep(1000 * v);
		return (0);

		/* Clear the window */
		case TERM_XTRA_CLEAR:
		for (i=0; i<MAX_TERM_DATA; i++) {
		    if (Term == &data[i].t)
			XClearWindow(XtDisplay((Widget)data[i].widget),
				     XtWindow((Widget)data[i].widget));
		}
		return (0);
	}

	/* Unknown */
	return (1);
}



/*
 * Erase a number of characters
 */
static errr Term_wipe_xaw(int x, int y, int n)
{
	term_data *td = (term_data*)(Term->data);

	/* Erase using color 0 */
	AngbandClearArea(td->widget, x, y, n, 1, 0);

	/* Success */
	return (0);
}



/*
 * Draw the cursor (XXX by hiliting)
 */
static errr Term_curs_xaw(int x, int y)
{
	term_data *td = (term_data*)(Term->data);

	/* Hilite the cursor character */
	AngbandClearArea(td->widget, x, y, 1, 1, COLOR_XOR);

	/* Success */
	return (0);
}


/*
 * Draw a number of characters
 */
static errr Term_text_xaw(int x, int y, int n, byte a, concptr s)
{
	term_data *td = (term_data*)(Term->data);

	/* Draw the text */
	AngbandOutputText(td->widget, x, y, (String)s, n, (a & 0x0F));

	/* Success */
	return (0);
}


/*
 * Raise a term
 */
static void term_raise(term_data win)
{
	Widget widget = (Widget)win.widget;

	XRaiseWindow(XtDisplay(XtParent(widget)), XtWindow(XtParent(widget)));
}


/*
 * Initialize a term_data
 */
static errr term_data_init(term_data *td, Widget topLevel,
			   int key_buf, String name,
			   ArgList widget_arg, Cardinal widget_arg_no)
{
	Widget parent;
	term *t = &td->t;

	/* Create the shell widget */
	parent = XtCreatePopupShell(name, topLevelShellWidgetClass, topLevel,
				    NULL, 0);

	/* Create the interior widget */
	td->widget = (AngbandWidget)
	XtCreateManagedWidget (name, angbandWidgetClass,
			       parent, widget_arg, widget_arg_no);

	/* Initialize the term (full size) */
	term_init(t, 80, 24, key_buf);

	/* Use a "soft" cursor */
	t->soft_cursor = TRUE;

	/* Erase with "white space" */
	t->attr_blank = TERM_WHITE;
	t->char_blank = ' ';

	/* Hooks */
	t->xtra_hook = Term_xtra_xaw;
	t->curs_hook = Term_curs_xaw;
	t->wipe_hook = Term_wipe_xaw;
	t->text_hook = Term_text_xaw;

	/* Save the data */
	t->data = td;

	/* Register the keypress event handler */
	XtAddEventHandler((Widget)td->widget, KeyPressMask,
			  False, (XtEventHandler) handle_event, td);

	/* Redraw callback */
	XtAddCallback((Widget)td->widget, XtNredrawCallback,
		      react_redraw, td);

	/* Realize the widget */
	XtRealizeWidget(parent);

	/* Make it visible */
	XtPopup(parent, XtGrabNone);

	/* Activate (important) */
	Term_activate(t);

	return 0;
}


/*
 * Initialization function for an X Athena Widget module to Angband
 *
 * Mega-Hack -- we are not given the actual "argc" and "argv" from
 * the main program, so we fake one.  Thus, we are unable to parse
 * any "display" requests for external devices.  This is okay, since
 * we need to verify the display anyway, to work with "main.c".
 */
errr init_xaw(void)
{
	int argc, i;
	char *argv[2];
	Widget topLevel;
	Display *dpy;


	/* One fake argument */
	argc = 1;

	/* Save the program name */
	argv[0] = argv0;

	/* Terminate */
	argv[1] = NULL;


	/* Attempt to open the local display */
	dpy = XOpenDisplay("");

	/* Failure -- assume no X11 available */
	if (!dpy) return (-1);

	/* Close the local display */
	XCloseDisplay(dpy);


#ifdef USE_XAW_LANG
	/* Support locale processing */
	XtSetLanguageProc(NULL, NULL, NULL);
#endif

	/* Initialize the toolkit */
	topLevel = XtAppInitialize (&appcon, "Angband", NULL, 0, &argc, argv,
				    fallback, NULL, 0);

	/* Initialize the windows */
	for (i=0; i<MAX_TERM_DATA; i++) {
	    term_data_init (&data[i], topLevel, 1024, termNames[i],
			    i<SPECIAL_FALLBACKS ? specialArgs[i] : defaultArgs,
			    TERM_FALLBACKS);
	    angband_term[i] = Term;
	}

	/* Activate the "Angband" window screen */
	Term_activate(&data[0].t);

	/* Raise the "Angband" window */
	term_raise(data[0]);

	/* Success */
	return (0);
}

#endif

