/* xwin.c -- X Window Terminal Interface */

/*			   NOTICE
 *
 * Copyright (c) 1990,1992,1993 Britt Yenne.  All rights reserved.
 * 
 * This software is provided AS-IS.  The author gives no warranty,
 * real or assumed, and takes no responsibility whatsoever for any 
 * use or misuse of this software, or any damage created by its use
 * or misuse.
 * 
 * This software may be freely copied and distributed provided that
 * no part of this NOTICE is deleted or edited in any manner.
 * 
 */

/* Mail comments or questions to ytalk@austin.eds.com */

#include "header.h"

#ifdef USE_X11

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>

static Display	       *display;	/* display */
static Window		rootwin;	/* root window */
static int		screen_num;	/* screen number */
static XrmDatabase	db;		/* resource database */
static XFontStruct     *text_font;	/* font */
static GC		textGC,		/* text graphic context */
			invertGC;	/* graphic context for inverts */
static ylong		whitepix,	/* white pixel */
			blackpix;	/* black pixel */
static int		font_width,	/* font width */
			font_height,	/* font height */
			font_ascent;	/* font ascent */

#define YPOS(p) ((p) * font_height)
#define XPOS(p) ((p) * font_width)

/* ----- local functions ----- */

static XTextProperty *
strToTP(s)
  char *s;
{
    XTextProperty *tp = (XTextProperty *)get_mem(sizeof(XTextProperty));
    XStringListToTextProperty(&s, 1, tp);
    return tp;
}

static char *
getOption(o)
  char *o;
{
    XrmValue value;
    char *type;

    if(XrmGetResource(db, o, o, &type, &value))
    {
	if(value.addr == NULL)
	    return NULL;
	if(strcmp(value.addr, "false") == 0)
	    return NULL;
	if(strcmp(value.addr, "False") == 0)
	    return NULL;
	return value.addr;
    }
    else
	return (char *)NULL;
}

static void
load_font(name, font)
  char *name;
  XFontStruct **font;
{
    if((*font = XLoadQueryFont(display, name)) == NULL)
    {
	sprintf(errstr, "Cannot load font %s", name);
	show_error(errstr);
	bail(YTE_ERROR);
    }
}

static void
make_GC(gc, font, fgpixel, bgpixel, l_width, l_style, l_cap, l_join, gcfunc)
  GC *gc;
  XFontStruct *font;
  ylong fgpixel, bgpixel;
  int l_width, l_style, l_cap, l_join, gcfunc;
{
    ylong mask = 0;
    XGCValues values;

    if(font != NULL)
    {
	values.font = font->fid;
	mask |= GCFont;
    }
    values.foreground = fgpixel;
    values.background = bgpixel;
    values.line_width = l_width;
    values.line_style = l_style;
    values.cap_style = l_cap;
    values.join_style = l_join;
    mask |= GCForeground | GCBackground | GCLineWidth | GCLineStyle | 
	    GCCapStyle | GCJoinStyle;
    if(gcfunc != -1)
    {
	values.function = gcfunc;
	mask |= GCFunction;
    }
    *gc = XCreateGC(display, rootwin, mask, &values);
}

/* Find the user who owns a given Window.
 */
static yuser *
win_user(win)
  Window win;
{
    register yuser *u;

    for(u = user_list; u; u = u->unext)
	if(u->win == win)
	    break;
    return u;
}

#define TWIN	report.xany.window

static void
process_event()
{
    register int n;
    register yuser *user;
    static XEvent report;
    static char buf[512];

    while(XPending(display))
    {
	XNextEvent(display, &report);
	switch(report.type)
	{
	    case Expose:
		if(report.xexpose.count)
		    break;
		if((user = win_user(TWIN)) != NULL)
		    redraw_term(user, 0);
		break;
	    case ConfigureNotify: /* RESIZED (or moved) */
		if((user = win_user(TWIN)) != NULL)
		{
		    int rows, cols;
		    rows = report.xconfigure.height / font_height;
		    cols = report.xconfigure.width / font_width;
		    resize_win(user, rows, cols);
		}
		break;
	    case KeyPress:
		n = XLookupString((XKeyEvent *) &report, buf, 512, NULL, NULL);
		my_input(win_user(report.xkeymap.window), buf, n);
		break;
	}
    }
}

static void
place_cursor(win, y, x)
  Window win;
  int y, x;
{
    XFillRectangle(display, win, invertGC,
	XPOS(x), YPOS(y),
	font_width, font_height);
}

/* ----- global functions ----- */

/* Initialize X Windows.
 */
void
init_xwin()
{
    char	*xrmstr;
    char	*displayName;
    char	*rfn, str[256];
    int		xfd;
    XGCValues   values;

    /* get and open the display */

    displayName = getOption("YTalk.display");
    if((display = XOpenDisplay(displayName)) == NULL)
    {
	show_error("Cannot open X display");
	bail(YTE_ERROR);
    }
    rootwin = DefaultRootWindow(display);
    screen_num = DefaultScreen(display);

    /* read all options */

    db = NULL;
    XrmInitialize();
    if((xrmstr = XResourceManagerString(display)) != NULL)
	db = XrmGetStringDatabase(xrmstr);
    else if((rfn = (char *)getenv("XENVIRONMENT")) != NULL
        && access(rfn, 0) == 0)
	db = XrmGetFileDatabase(rfn);
    else if((rfn = (char *)getenv("HOME")) != NULL)
    {
	sprintf(str, "%s/.Xdefaults", rfn);
	if(access(str, 0) == 0)
	    db = XrmGetFileDatabase(str);
    }
    if(db == NULL)
	db = XrmGetStringDatabase("");
    if(getOption("YTalk.reverse"))
    {
	whitepix = BlackPixel(display, screen_num);
	blackpix = WhitePixel(display, screen_num);
    }
    else
    {
	blackpix = BlackPixel(display, screen_num);
	whitepix = WhitePixel(display, screen_num);
    }

    /* load font and graphic context */

    if((rfn = getOption("YTalk.font")) == NULL)
	rfn = "9x15";
    load_font(rfn, &text_font);
    font_width = text_font->max_bounds.rbearing;
    font_height = text_font->max_bounds.ascent + text_font->max_bounds.descent;
    font_ascent = text_font->max_bounds.ascent;
    make_GC(&textGC, text_font, blackpix, whitepix,
	    2, LineSolid, CapRound, JoinRound, -1);
    make_GC(&invertGC, text_font, blackpix, whitepix,
	    2, LineSolid, CapRound, JoinRound, GXinvert);
    values.plane_mask = blackpix ^ whitepix;
    XChangeGC(display, invertGC, GCPlaneMask, &values);

    /* set up event processing */

    xfd = ConnectionNumber(display);
    add_fd(xfd, process_event);
}

/* End X Windows.
 */
void
end_xwin()
{
    XCloseDisplay(display);
}

/* Open a new window.
 */
int
open_xwin(user, title)
  yuser *user;
  char *title;
{
    XWMHints	WMhints;
    XClassHint	ClassHints;
    XSizeHints	size;
    XTextProperty *name;
    Window	win;
    int		rows, cols;

    size.x = 0;
    size.y = 0;
    size.width = 80;
    size.height = 24;
    size.min_width = 20;
    size.min_height = 2;
    size.width_inc = font_width;
    size.height_inc = font_height;
    size.flags = PSize | PMinSize | PResizeInc;
    if(getOption("YTalk.geometry"))
    {
	XParseGeometry(getOption("YTalk.geometry"),
	    &size.x, &size.y, (u_int *)&size.width, (u_int *)&size.height);

	/* don't set USPosition -- it confuses tvtwm */
    }
    rows = size.height;
    cols = size.width;
    size.width *= font_width;
    size.height *= font_height;
    size.min_width *= font_width;
    size.min_height *= font_height;
    win = XCreateSimpleWindow(display, rootwin, size.x, size.y,
	size.width, size.height, 4, blackpix, whitepix);
    if(win == (Window)0)
	return -1;

    WMhints.flags = InputHint;
    WMhints.input = 1;
    ClassHints.res_name = "ytalk";
    ClassHints.res_class = "YTalk";
    name = strToTP(title);
    XSetWMProperties(display, win, name, name,
	0, 0, &size, &WMhints, &ClassHints);

    XSelectInput(display, win, ExposureMask | KeyPressMask
	| StructureNotifyMask);
    XMapRaised(display, win);

    user->win = win;
    user->ty = user->tx = 0;
    place_cursor(win, 0, 0);
    resize_win(user, rows, cols);
    return 0;
}

void
close_xwin(user)
  yuser *user;
{
    XDestroyWindow(display, user->win);
    user->win = (Window)0;
}

void
addch_xwin(user, ch)
  yuser *user;
  ychar ch;
{
    XClearArea(display, user->win,
	XPOS(user->tx), YPOS(user->ty),
	font_width, font_height,
	False);
    XDrawString(display, user->win, textGC,
	XPOS(user->tx), YPOS(user->ty) + font_ascent,
	&ch, 1);
    user->tx++;
    if(user->tx >= user->t_cols)
	user->tx--;
    place_cursor(user->win, user->ty, user->tx);
}

void
move_xwin(user, y, x)
  yuser *user;
  int y, x;
{
    place_cursor(user->win, user->ty, user->tx);
    user->ty = y;
    user->tx = x;
    place_cursor(user->win, user->ty, user->tx);
}

void
clreol_xwin(user)
  yuser *user;
{
    XClearArea(display, user->win,
	XPOS(user->tx), YPOS(user->ty),
	0, font_height,
	False);
    place_cursor(user->win, user->ty, user->tx);
}

void
clreos_xwin(user)
  yuser *user;
{
    XClearArea(display, user->win,
	XPOS(user->tx), YPOS(user->ty),
	0, font_height,
	False);
    XClearArea(display, user->win,
	0, YPOS(user->ty + 1),
	0, 0,
	False);
    place_cursor(user->win, user->ty, user->tx);
}

void
scroll_xwin(user)
  yuser *user;
{
    place_cursor(user->win, user->ty, user->tx);
    XCopyArea(display, user->win, user->win, textGC,
	XPOS(0), YPOS(1),
	XPOS(user->t_cols), YPOS(user->t_rows - 1),
	XPOS(0), YPOS(0));
    XClearArea(display, user->win,
	0, YPOS(user->t_rows - 1),
	0, font_height,
	False);
    place_cursor(user->win, user->ty, user->tx);
}

void
rev_scroll_xwin(user)
  yuser *user;
{
    place_cursor(user->win, user->ty, user->tx);
    XCopyArea(display, user->win, user->win, textGC,
	XPOS(0), YPOS(0),
	XPOS(user->t_cols), YPOS(user->t_rows - 1),
	XPOS(0), YPOS(1));
    XClearArea(display, user->win,
	XPOS(0), YPOS(0),
	0, font_height,
	False);
    place_cursor(user->win, user->ty, user->tx);
}

void
flush_xwin(user)
  yuser *user;
{
    /* "user" is unused -- sorry, lint  :-) */
    XFlush(display);
}

#endif
