/*
 * Configurable ps-like program.
 * Display device which uses raw X11 calls to show text in a window.
 * The compilation of this display device is optional based on the
 * definition of the X11 symbol in the Makefile.
 *
 * Copyright (c) 2014 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#ifdef	X11

#include <memory.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/StringDefs.h>

#include "ips.h"


/*
 * Limits on the number of rows and columns for sanity.
 */
#define	MIN_ROWS	3
#define	MIN_COLS	20
#define	MAX_ROWS	500
#define	MAX_COLS	2000


/*
 * Extra reallocation slop size for lines which are being resized.
 */
#define	COLUMN_REALLOC_SLOP_SIZE	20


/*
 * Storage of a single cell of the text.
 * This consists of a character and a color id.
 */
typedef	short	CELL;


/*
 * Macros to manipulate the cell components
 */
#define	GETCHAR(cell)		((cell) >> 8)
#define	GETCOLOR(cell)		((cell) & 0xff)
#define	MAKECELL(ch, color)	(((ch) << 8) | (color))


/*
 * Storage for a line of text.
 * Note: This is a varying size structure.
 */
typedef struct	LINE	LINE;

struct LINE
{
	int	beginUpdate;	/* beginning column needing update */
	int	endUpdate;	/* ending column (plus one) needing update */
	int	clearCount;	/* columns needing clearing after used */
	int	usedCount;	/* number of columns currently in use */
	int	alloc;		/* number of columns allocated in data */
	CELL	data[1];	/* text for line (MUST BE LAST) */
};


/*
 * Allocation size for a LINE to hold a certain number of columns.
 */
#define	LINE_SIZE(columns)	(sizeof(LINE) + ((columns) * sizeof(CELL)))


/*
 * Pixels for offsetting text from edges.
 */
#define	TEXT_OFF	4

/*
 * The input buffer size.
 */
#define	INPUT_BUFFER_SIZE	128


static	BOOL	X11Open(DISPLAY *);
static	BOOL	X11DefineColor(DISPLAY *, int, const char *, const char *, int);
static	void	X11CreateWindow(DISPLAY *);
static	void	X11Close(DISPLAY *);
static	void	X11SetColor(DISPLAY *, int);
static	void	X11Refresh(DISPLAY *);
static	void	X11BeginPage(DISPLAY *);
static	void	X11PutChar(DISPLAY *, int);
static	void	X11PutString(DISPLAY *, const char *);
static	void	X11PutBuffer(DISPLAY *, const char *, int);
static	void	X11EndPage(DISPLAY *);
static	BOOL	X11EventWait(DISPLAY *, int);
static	BOOL	X11InputReady(DISPLAY *);
static	int	X11ReadChar(DISPLAY *);
static	void	X11RingBell(DISPLAY *);
static	int	X11GetRows(DISPLAY *);
static	int	X11GetCols(DISPLAY *);
static	BOOL	X11DoesScroll(DISPLAY *);


static DISPLAY	x11Display =
{
	X11Open, X11DefineColor, X11CreateWindow, X11Close, X11SetColor,
	X11Refresh, X11BeginPage, X11PutChar, X11PutString, X11PutBuffer,
	X11EndPage, X11EventWait, X11InputReady, X11ReadChar,
	X11RingBell, X11GetRows, X11GetCols, X11DoesScroll
};


/*
 * Local variables.
 */
static	Display *	display;	/* display for X */
static	int		screen;		/* screen number */
static	Window		rootWid;	/* top level window id */
static	Window		mainWid;	/* main window id */
static	GC		textGC;		/* graphics context for text */
static	GC		clearGC;	/* graphics context for clearing areas */
static	XFontStruct *	font;		/* font for text */
static	Colormap	colorMap;	/* default color map */
static	long		black;		/* black pixel value */
static	long		white;		/* white pixel value */
static	long		foreground;	/* default foreground color */
static	long		background;	/* default background color */
static	int		charWidth;	/* width of characters */
static	int		charHeight;	/* height of characters */
static	int		charAscent;	/* ascent of characters */
static	int		mainWidth;	/* width of main window */
static	int		mainHeight;	/* height of main window */
static	int		textRow;	/* row number for new text */
static	int		textCol;	/* column number for new text */
static	int		rows;		/* current number of rows of text */
static	int		cols;		/* current number of columns of text */
static	int		inputCount;	/* characters stored in input buffer */
static	BOOL		isMapped;	/* main window is mapped */
static	BOOL		isRedrawNeeded;		/* main window needs redrawing */
static	LINE *		lines[MAX_ROWS];	/* lines of text */
static	char		inputBuffer[INPUT_BUFFER_SIZE];	/* input buffer */


/*
 * Color information.
 * The color tables are indexed by the color id.
 */
static	int		currentColorId;
static	long		currentForegroundPixel;
static	long		currentBackgroundPixel;
static	int		currentColorFlags;
static	long		foregroundColorTable[MAX_COLORS];
static	long		backgroundColorTable[MAX_COLORS];
static	int		colorFlagsTable[MAX_COLORS];


/*
 * Local procedures.
 */
static	void	DoEvent(XEvent *);
static	void	DoKeyPress(XKeyEvent *);
static	void	DoConfigure(XConfigureEvent *);
static	void	StoreCharacter(int row, int col, int ch);
static	void	StoreEndOfLine(int row, int col);
static	void	SetColor(int colorId);
static	void	UpdateWindow(void);
static	void	UpdateRow(int row);
static	void	DrawText(int textX, int textY, const char * buffer, int count);
static	void	ClearWindow(void);
static	LINE *	GetLine(int row);


/*
 * Return the instance of the X11 display device.
 */
DISPLAY *
GetX11Display(void)
{
	return &x11Display;
}


/*
 * Open the display device.
 * This doesn't yet create the window.
 */
static BOOL
X11Open(DISPLAY * ipsDisplay)
{
	XColor		cellColor;
	XColor		nameColor;
	int		colorId;

	/*
	 * Open the display.
	 */
	display = XOpenDisplay(displayName);

	if (display == NULL)
		return FALSE;

	screen = DefaultScreen(display);
	rootWid = RootWindow(display, screen);

	/*
	 * Try to load the font to be used.
	 */
	font = XLoadQueryFont(display, fontName);

	/*
	 * If the font isn't found then try the default one.
	 */
	if ((font == NULL) && (strcmp(fontName, DEFAULT_FONT) != 0))
		font = XLoadQueryFont(display, DEFAULT_FONT);

	if (font == NULL)
	{
		XCloseDisplay(display);

		display = NULL;

		return FALSE;
	}

	/*
	 * Save the font attributes.
	 * This assumes that the font is mono-spaced.
	 */
	charAscent = font->ascent;
	charHeight = charAscent + font->descent;
	charWidth = font->max_bounds.width;

	/*
	 * Allocate colors.
	 */
	black = BlackPixel(display, screen);
	white = WhitePixel(display, screen);

	colorMap = DefaultColormap(display, screen);

	foreground = black;
	background = white;

	if (XAllocNamedColor(display, colorMap, foregroundName,
		&cellColor, &nameColor))
	{
		foreground = cellColor.pixel;
	}

	if (XAllocNamedColor(display, colorMap, backgroundName,
		&cellColor, &nameColor))
	{
		background = cellColor.pixel;
	}

	/*
	 * Set up the default color table information.
	 */
	currentColorId = DEFAULT_COLOR_ID;
	currentForegroundPixel = foreground;
	currentBackgroundPixel = background;
	currentColorFlags = COLOR_FLAG_NONE;

	for (colorId = 0; colorId < MAX_COLORS; colorId++)
	{
		foregroundColorTable[colorId] = foreground;
		backgroundColorTable[colorId] = background;
		colorFlagsTable[colorId] = COLOR_FLAG_NONE;
	}

	/*
	 * Set up the graphics contexts.
	 */
	textGC = XCreateGC(display, rootWid, 0, NULL);

	XSetForeground(display, textGC, foreground);
	XSetBackground(display, textGC, background);
	XSetFont(display, textGC, font->fid);

	clearGC = XCreateGC(display, rootWid, 0, NULL);

	XSetForeground(display, clearGC, background);

	/*
	 * Synchronize so we know we are ok.
	 */
	XSync(display, False);

	/*
	 * Ok, return success.
	 */
	return TRUE;
}


/*
 * Create the output window.
 */
static void
X11CreateWindow(DISPLAY * ipsDisplay)
{
	XSizeHints	sizeHints;
	XWMHints	wmHints;
	XEvent		event;
	int		displayWidth;
	int		displayHeight;
	int		parsedFlags;
	int		parsedX;
	int		parsedY;
	unsigned int	tryCols;
	unsigned int	tryRows;

	/*
	 * Parse the geometry string to get the preferred window size.
	 * Then constrain the result to reasonable values.
	 */
	parsedFlags = XParseGeometry(geometry, &parsedX, &parsedY,
		&tryCols, &tryRows);

	if ((parsedFlags & WidthValue) == 0)
		tryCols = DEFAULT_GEOMETRY_COLS;

	if ((parsedFlags & HeightValue) == 0)
		tryRows = DEFAULT_GEOMETRY_ROWS;

	if (tryCols < MIN_COLS)
		tryCols = MIN_COLS;

	if (tryCols > MAX_COLS)
		tryCols = MAX_COLS;

	if (tryRows < MIN_ROWS)
		tryRows = MIN_ROWS;

	if (tryRows > MAX_ROWS)
		tryRows = MAX_ROWS;

	/*
	 * Calculate a preferred window size based on holding text holding
	 * the desired number of rows and columns.
	 */
	displayWidth = DisplayWidth(display, screen);
	displayHeight = DisplayHeight(display, screen);

	mainWidth = charWidth * tryCols + TEXT_OFF * 2;
	mainHeight = charHeight * tryRows + TEXT_OFF * 2;

	if (mainWidth > displayWidth)
		mainWidth = displayWidth;

	if (mainHeight > displayHeight)
		mainHeight = displayHeight;

	sizeHints.flags = USSize | PMinSize | PResizeInc | PBaseSize;
	sizeHints.width = mainWidth;
	sizeHints.height = mainHeight;
	sizeHints.width_inc = charWidth;
	sizeHints.height_inc = charHeight;
	sizeHints.min_width = TEXT_OFF * 2;
	sizeHints.min_height = TEXT_OFF * 2;
	sizeHints.base_width = TEXT_OFF * 2;
	sizeHints.base_height = TEXT_OFF * 2;

	/*
	 * If a position was specified then set it.
	 * Note: This doesn't seem to work right but I don't have enough
	 * documentation to understand it right now.
	 */
	if (((parsedFlags & XValue) != 0) && ((parsedFlags & YValue) != 0))
	{
		sizeHints.flags |= USPosition;
		sizeHints.x = parsedX;
		sizeHints.y = parsedY;
	}

	mainWid = XCreateSimpleWindow(display, rootWid,
		0, 0, mainWidth, mainHeight, 0, 0, background);

	XSelectInput(display, mainWid,
		KeyPressMask | ExposureMask | StructureNotifyMask);

	XSetStandardProperties(display, mainWid, "ips", "ips", None,
		(char **) "", 0, &sizeHints);

	wmHints.flags = InputHint;
	wmHints.input = True;

	XSetWMHints(display, mainWid, &wmHints);

	/*
	 * Map the window and wait for it to appear.
	 */
	XMapWindow(display, mainWid);

	isMapped = FALSE;

	while (!isMapped)
	{
		XNextEvent(display, &event);
		DoEvent(&event);
	}
}


/*
 * Close the display device.
 */
static void
X11Close(DISPLAY * ipsDisplay)
{
	if (display)
		XCloseDisplay(display);

	display = NULL;
}


/*
 * Define a foreground and background color pair to be associated
 * with the specified color id, along with flags which modify the
 * look of the characters.  Empty names indicate that the default
 * colors are to be used.  Returns TRUE if the definition was
 * successful.
 */
static BOOL
X11DefineColor(DISPLAY * ipsDisplay, int colorId,
	const char * foregroundName, const char * backgroundName,
	int colorFlags)
{
	long	foregroundPixel = foreground;
	long	backgroundPixel = background;
	XColor	cellColor;
	XColor	nameColor;

	if ((colorId < 0) || (colorId >= MAX_COLORS))
		return FALSE;

	/*
	 * Validate that the flags are known.
	 */
	if (colorFlags & ~(COLOR_FLAG_UNDERLINE|COLOR_FLAG_BOLD))
		return FALSE;

	/*
	 * Allocate the foreground and background colors if they
	 * are not empty strings.
	 */
	if (*foregroundName)
	{
		if (!XAllocNamedColor(display, colorMap, foregroundName,
			&cellColor, &nameColor))
		{
			return FALSE;
		}

		foregroundPixel = cellColor.pixel;
	}

	if (*backgroundName)
	{
		if (!XAllocNamedColor(display, colorMap, backgroundName,
			&cellColor, &nameColor))
		{
			return FALSE;
		}

		backgroundPixel = cellColor.pixel;
	}

	/*
	 * Store the colors in the table and return success.
	 */
	foregroundColorTable[colorId] = foregroundPixel;
	backgroundColorTable[colorId] = backgroundPixel;
	colorFlagsTable[colorId] = colorFlags;

	return TRUE;
}


/*
 * Set the color id to be used for new output in the window.
 */
static void
X11SetColor(DISPLAY * ipsDisplay, int colorId)
{
	if ((colorId < 0) || (colorId > MAX_COLORS))
		colorId = DEFAULT_COLOR_ID;

	currentColorId = colorId;
}


static void
X11Refresh(DISPLAY * ipsDisplay)
{
	isRedrawNeeded = TRUE;
}


static void
X11BeginPage(DISPLAY * ipsDisplay)
{
	/*
	 * Clear the window if we need to redraw it.
	 */
	if (isRedrawNeeded)
		ClearWindow();

	/*
	 * Set our writing position to the top left.
	 */
	textRow = 0;
	textCol = 0;
}


/*
 * Write one character to the screen handling newlines and tabs.
 * This does not yet do any actual X11 calls, only the LINE structures
 * are manipulated.
 */
static void
X11PutChar(DISPLAY * ipsDisplay, int ch)
{
	int	spaceCount;

	/*
	 * Switch on the character to handle it.
	 */
	switch (ch)
	{
		case '\0':
			break;

		case '\n':
			StoreEndOfLine(textRow, textCol);
			textCol = 0;
			textRow++;

			break;

		case '\t':
			spaceCount = 8 - (textCol + 1) % 8;

			while (spaceCount-- > 0)
				X11PutChar(ipsDisplay, ' ');

			break;

		default:
			StoreCharacter(textRow, textCol, ch);
			textCol++;

			break;
	}
}


static void
X11PutString(DISPLAY * ipsDisplay, const char * str)
{
	while (*str)
		X11PutChar(ipsDisplay, *str++);
}


static void
X11PutBuffer(DISPLAY * ipsDisplay, const char * str, int len)
{
	while (len-- > 0)
		X11PutChar(ipsDisplay, *str++);
}


/*
 * End the page by writing the changed text into the window.
 */
static void
X11EndPage(DISPLAY * ipsDisplay)
{
	/*
	 * Clear all of the rest of the window.
	 */
	while (textRow <= rows)
	{
		StoreEndOfLine(textRow, textCol);
		textCol = 0;
		textRow++;
	}

	/*
	 * Now update the window and flush it.
	 */
	UpdateWindow();
	XFlush(display);
}


/*
 * Handle events while waiting for the specified amount of time.
 * Returns early if there are input characters to be processed.
 * Returns TRUE if the window is exposed or resized and needs redrawing.
 */
static BOOL
X11EventWait(DISPLAY * ipsDisplay, int milliSeconds)
{
	XEvent		event;
	struct	timeval	timeOut;
	fd_set		readFds;
	int		fd;

	/*
	 * First handle any already queued events.
	 */
	while (XEventsQueued(display, QueuedAfterFlush) > 0)
	{
		XNextEvent(display, &event);
		DoEvent(&event);
	}

	/*
	 * If there is no delay then do nothing.
	 */
	if (milliSeconds <= 0)
		return isRedrawNeeded;

	/*
	 * Wait for some more events to arrive or until the timeout expires.
	 */
	fd = ConnectionNumber(display);

	FD_ZERO(&readFds);
	FD_SET(fd, &readFds);

	timeOut.tv_sec = milliSeconds / 1000;
	timeOut.tv_usec = (milliSeconds % 1000) * 1000;

	(void) select(fd + 1, &readFds, NULL, NULL, &timeOut);

	/*
	 * Now handle any events that arrived during our wait.
	 */
	while (XEventsQueued(display, QueuedAfterFlush) > 0)
	{
		XNextEvent(display, &event);
		DoEvent(&event);
	}

	/*
	 * Return TRUE if the window needs redrawing.
	 */
	return isRedrawNeeded;
}


/*
 * See if keyboard input is ready to read from the window.
 * This does not block.
 */
static BOOL
X11InputReady(DISPLAY * ipsDisplay)
{
	XEvent	event;

	if (inputCount > 0)
		return TRUE;

	while (TRUE)
	{
		if (XEventsQueued(display, QueuedAfterFlush) <= 0)
			return FALSE;

		XPeekEvent(display, &event);

		if (event.type == KeyPress)
			return TRUE;

		XNextEvent(display, &event);

		DoEvent(&event);
	}

	return FALSE;
}


/*
 * Read the next character from the window.
 * If no characters are available this will block.
 */
static int
X11ReadChar(DISPLAY * ipsDisplay)
{
	XEvent	event;
	int	ch;

	/*
	 * Loop reading events until we have an input character and
	 * until there are no more events to process.
	 */
	while ((inputCount == 0) ||
		(XEventsQueued(display, QueuedAfterFlush) > 0))
	{
		XNextEvent(display, &event);

		DoEvent(&event);
	}

	/*
	 * There is now at least one character to read, so get it.
	 */
	ch = inputBuffer[0];
	inputCount--;

	if (inputCount > 0)
		memmove(inputBuffer, inputBuffer + 1, inputCount);

	return ch;
}


static int
X11GetRows(DISPLAY * ipsDisplay)
{
	return rows;
}


static int
X11GetCols(DISPLAY * ipsDisplay)
{
	return cols;
}


static void
X11RingBell(DISPLAY * ipsDisplay)
{
	XBell(display, 0);
	XFlush(display);
}


static BOOL
X11DoesScroll(DISPLAY * ipsDisplay)
{
	return FALSE;
}


/*
 * Handle the specified event.
 */
static void
DoEvent(XEvent * event)
{
	switch (event->type)
	{
		case Expose:
			isMapped = TRUE;
			isRedrawNeeded = TRUE;

			break;

		case MapNotify:
			isMapped = TRUE;

			break;

		case UnmapNotify:
			isMapped = FALSE;

			break;

		case KeyPress:
			DoKeyPress(&event->xkey);

			break;

		case ConfigureNotify:
			DoConfigure(&event->xconfigure);

			break;
	}
}


/*
 * Here for a keypress event.
 * We just store the character into an internal buffer until it can be
 * collected by the caller.
 */
static void
DoKeyPress(XKeyEvent * kp)
{
	KeySym	keySym;
	int	len;
	char	buf[12];

	/*
	 * Look up the key symbol.
	 */
	len = XLookupString(kp, buf, sizeof(buf), &keySym, NULL);

	/*
	 * Check for a normal key.
	 */
	if ((len != 1) || IsModifierKey(keySym))
		return;

	/*
	 * Try to store the character into our buffer.
	 */
	if (inputCount < sizeof(inputBuffer))
		inputBuffer[inputCount++] = buf[0];
	else
		XBell(display, 0);
}


/*
 * Here for a configuration change to our window (e.g., resize).
 * We just recalculate the number of rows and columns.
 * If their value changes, we set the redraw flag.
 */
static void
DoConfigure(XConfigureEvent * cp)
{
	int	newRows;
	int	newCols;

	mainHeight = cp->height;
	mainWidth = cp->width;

	newRows = (mainHeight - TEXT_OFF * 2) / charHeight;
	newCols = (mainWidth - TEXT_OFF * 2) / charWidth;

	if (newRows > MAX_ROWS)
		newRows = MAX_ROWS;

	if (newCols > MAX_COLS)
		newCols = MAX_COLS;

	if ((rows != newRows) || (cols != newCols))
	{
		rows = newRows;
		cols = newCols;
		isRedrawNeeded = TRUE;
	}
}


/*
 * Store a character at the indicated position of the window.
 * This only manipulates the LINE structures so no X11 calls are done yet.
 * No control character handling is performed here.
 */
static void
StoreCharacter(int row, int col, int ch)
{
	LINE *	line;
	CELL	cell;

	/*
	 * Make sure the position is within the window.
	 * If not, then ignore it.
	 */
	if ((row < 0) || (row >= rows) || (col < 0) || (col >= cols))
		return;

	/*
	 * Get the LINE for this row which is large enough for the column.
	 */
	line = GetLine(row);

	/*
	 * Build the cell value from the character and the current color.
	 */
	cell = MAKECELL(ch, currentColorId);

	/*
	 * If the used part of the line is being extended than handle that.
	 */
	if (col >= line->usedCount)
	{
		line->usedCount = col + 1;

		if (line->endUpdate == 0)
			line->beginUpdate = col;

		line->endUpdate = col + 1;
		line->data[col] = cell;

		return;
	}

	/*
	 * If a old column's character is being changed then handle that.
	 */
	if (line->data[col] != cell)
	{
		if (line->endUpdate == 0)
			line->beginUpdate = col;

		if (col >= line->endUpdate)
			line->endUpdate = col + 1;

		line->data[col] = cell;
	}
}


/*
 * Store an end of line for the specified row.
 * The specified column number is the first unused column of the line.
 * This basically just marks the line as needing clearing.
 */
static void
StoreEndOfLine(int row, int col)
{
	/*
	 * Make sure the position is within the window.
	 * If not, then ignore it.
	 */
	if ((row < 0) || (row >= rows) || (col < 0) || (col >= cols))
		return;

	/*
	 * Store end of lines in the rest of the row.
	 */
	while (col < cols)
		StoreCharacter(row, col++, ' ');

	/*
	 * Get the LINE for this row which is large enough for the column.
	 */
#if 0
	line = GetLine(row);

	/*
	 * If the unused column value is less then before then mark
	 * the fact that the end of line needs clearing and set the
	 * new used value to the smaller value.
	 */
	if (col < line->usedCount)
	{
		line->clearCount = line->usedCount - col;
		line->usedCount = col;
	}
#endif
}


/*
 * Update all of the rows in the window.
 */
static void
UpdateWindow(void)
{
	int	row;

	for (row = 0; row < rows; row++)
		UpdateRow(row);
}


/*
 * Update the specified row by writing its changed text to the window.
 */
static void
UpdateRow(int row)
{
	LINE *	line;
	const CELL *	cell;
	int	drawCount;
	int	textX;
	int	textY;
	int	clearX;
	int	clearY;
	int	colorId;
	int	runCount;
	char	buffer[512];

	line = GetLine(row);

	/*
	 * Get the number of characters to be updated.
	 */
	drawCount = line->endUpdate - line->beginUpdate;

	/*
	 * Calculate the window position of the change.
	 */
	textX = TEXT_OFF + charWidth * line->beginUpdate;
	textY = TEXT_OFF + charAscent + charHeight * row;
	cell = &line->data[line->beginUpdate];

	/*
	 * Loop drawing the characters until they are all done.
	 */
	while (drawCount > 0)
	{
		/*
		 * Get the color of the first cell of the run
		 * and make that color effective.
		 */
		colorId = GETCOLOR(*cell);

		SetColor(colorId);

		/*
		 * Count the number of characters in the run having
		 * the same color and copy the characters themselves
		 * into a local buffer while they fit.
		 */
		runCount = 0;

		while ((runCount < drawCount) && (runCount < sizeof(buffer)) &&
			(GETCOLOR(*cell) == colorId))
		{
			buffer[runCount] = GETCHAR(*cell);
			cell++;
			runCount++;
		}

		/*
		 * Draw the text at the specified location.
		 */
		DrawText(textX, textY, buffer, runCount);

		/*
		 * Adjust the values for the characters used.
		 */
		textX += (charWidth * runCount);
		drawCount -= runCount;
	}

	/*
	 * If the end of the line needs clearing then do so.
	 */
	if (line->clearCount > 0)
	{
		clearX = TEXT_OFF + charWidth * line->usedCount;
		clearY = TEXT_OFF + charHeight * row;

		XClearArea(display, mainWid, clearX, clearY,
			charWidth * line->clearCount, charHeight, False);
	}

	/*
	 * Update the LINE values.
	 */
	line->beginUpdate = 0;
	line->endUpdate = 0;
	line->clearCount = 0;
}


/*
 * Draw some text at the specified location.
 */
static void
DrawText(int textX, int textY, const char * buffer, int count)
{
	/*
	 * Clear or fill the area where the new text goes.
	 */
	if (currentBackgroundPixel == background)
	{
		XClearArea(display, mainWid, textX, textY - charAscent,
			charWidth * count, charHeight, False);
	}
	else
	{
		XFillRectangle(display, mainWid, clearGC, textX, textY - charAscent,
			charWidth * count, charHeight);
	}

	/*
	 * Draw the new text.
	 */
	XDrawString(display, mainWid, textGC, textX, textY, buffer, count);

	/*
	 * If the text is bold then draw it again shifted by a pixel.
	 * This is a bit of a hack but is easier than using multiple fonts.
	 */
	if (currentColorFlags & COLOR_FLAG_BOLD)
	{
		XDrawString(display, mainWid, textGC, textX + 1, textY, buffer, count);
	}

	/*
	 * If the text is being underlined then do that.
	 */
	if (currentColorFlags & COLOR_FLAG_UNDERLINE)
	{
		XDrawLine(display, mainWid, textGC, textX, textY,
			textX + charWidth * count - 1, textY);
	}
}


/*
 * Set the color being used for drawing text.
 */
void
SetColor(int colorId)
{
	long foregroundPixel;
	long backgroundPixel;

	/*
	 * Get the foreground and background pixel values
	 * for the color id.
	 */
	if ((colorId < 0) || (colorId >= MAX_COLORS))
		colorId = DEFAULT_COLOR_ID;

	foregroundPixel = foregroundColorTable[colorId];
	backgroundPixel = backgroundColorTable[colorId];

	/*
	 * Set the colors in the graphics contexts if necessary
	 * and save the new values.
	 */
	if (foregroundPixel != currentForegroundPixel)
		XSetForeground(display, textGC, foregroundPixel);

	if (backgroundPixel != currentBackgroundPixel)
	{
		XSetBackground(display, textGC, backgroundPixel);
		XSetForeground(display, clearGC, backgroundPixel);
	}

	/*
	 * Save the values for checking elsewhere.
	 */
	currentForegroundPixel = foregroundPixel;
	currentBackgroundPixel = backgroundPixel;
	currentColorFlags = colorFlagsTable[colorId];
}


/*
 * Clear the window and reinitialise the LINE structures to be empty.
 * This is called after exposure or configuration events when we are
 * beginning a new page of data.
 */
static void
ClearWindow(void)
{
	LINE *	line;
	int	row;

	/*
	 * Remove all of the information from the rows.
	 */
	for (row = 0; row < rows; row++)
	{
		line = GetLine(row);

		line->beginUpdate = 0;
		line->endUpdate = 0;
		line->usedCount = 0;
		line->clearCount = 0;
	}

	/*
	 * Clear the window for real.
	 */
	XClearWindow(display, mainWid);

	/*
	 * We now don't need to redraw anymore.
	 */
	isRedrawNeeded = FALSE;
}


/*
 * Get the LINE structure for the specified row.
 * This allocates or resizes the structure if required to hold the
 * current number of columns in the window.
 */
static LINE *
GetLine(int row)
{
	LINE *	line;

	/*
	 * Make sure the row number is valid.
	 */
	if ((row < 0) || (row >= rows))
	{
		fprintf(stderr, "Illegal row number\n");

		exit(1);
	}

	/*
	 * Get the indicated line structure.
	 */
	line = lines[row];

	/*
	 * If the line structure is NULL then allocate it large enough
	 * to hold the current number of columns in the window.
	 */
	if (line == NULL)
	{
		line = AllocMemory(LINE_SIZE(cols));

		lines[row] = line;

		line->beginUpdate = 0;
		line->endUpdate = 0;
		line->clearCount = 0;
		line->usedCount = 0;
		line->alloc = cols;

		return line;
	}

	/*
	 * If the line is large enough for the current number of columns
	 * then return it.
	 */
	if (line->alloc >= cols)
		return line;

	/*
	 * The line structure is too small so we need to reallocate it.
	 * This only happens if the window has been resized, so in this
	 * case we allocate a larger than needed line to try to prevent
	 * some further reallocations if the resizing is continued.
	 */
	line = (LINE *) ReallocMemory(line,
		LINE_SIZE(cols + COLUMN_REALLOC_SLOP_SIZE));

	lines[row] = line;

	line->alloc = cols + COLUMN_REALLOC_SLOP_SIZE;

	return line;
}

#endif

/* END CODE */
