/* 
 * mxDisplay.c --
 *
 *	This file provides redisplay facilities for Mx windows.
 *
 * Copyright (C) 1986 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/mx/RCS/mxDisplay.c,v 1.12 90/04/19 11:31:22 ouster Exp $ SPRITE (Berkeley)";
#endif not lint


#include <X11/Xlib.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "mxInt.h"

/*
 * There are two overall things that complicate this code.  The first
 * is the possibility that one line of a file may not fit on one line
 * of the display, particularly if the window is narrow.  In this case,
 * the code here will wrap the line around onto the next line of the
 * display.  A line of the display is referred to as a "display line"
 * to distinguish it from a line of the file.  The possibility that one
 * file line can occupy multiple display lines wreaks havoc on all the
 * code that manages the screen layout.
 *
 * The second overall trouble-causer is variable-width characters.
 * First of all, I was stupid enough to decide to code this module
 * to handle variable-width fonts.  Second, even with fixed-width
 * fonts there's a problem with tabs:  wherever tab occurs, the next
 * character's x-position gets rounded to the next higher multiple
 * of TABSIZE space-widths.  Third, control characters get displayed
 * as a two-character sequence, "^X", which makes them wider than
 * other characters.  The result is big trouble for code that
 * computes where characters go on the screen.
 */

#define TABSIZE 8

/*
 * The structure below is used to keep track of a particular
 * point in a window, in terms of its x and y coordinates, and
 * also in terms of the line on the display and character within
 * the line.
 */

typedef struct {
    int x, y;		/* Screen coords of character's UL corner. */
    int dlIndex;	/* Display line in which character appears, or
			 * OFF_TOP or OFF_BOTTOM. */
    int charIndex;	/* Index of character.  0 means 1st character
    			 * of FILE line, not DISPLAY line. */
    char *line;		/* Pointer to line of text containing this character
    			 * and x-y position. */
} CharPos;

#define OFF_TOP		-1
#define OFF_BOTTOM	-2

/*
 * Pattern to use for areas off the end of the file.
 */

#include "eof.bits"

/*
 * Forward references to local procedures in this file:
 */

static int		CharToLine();
static int		CountLines();
static void		DoShift();
static void		EventProc();
static void		FindTopAndBot();
static char *		MeasureLength();
static void		PointToChar();
static void		PositionView();
static void		SetupView();
static void		ShiftScreen();
static void		SpyProc();

/*
 *----------------------------------------------------------------------
 *
 * MxSetupRedisplay --
 *
 *	Initialize variables and create handlers so that a file
 *	gets redisplayed in an Mx window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff in mxwPtr gets modified, and Sx_EventHandlers get
 *	created.
 *
 *----------------------------------------------------------------------
 */

void
MxSetupRedisplay(mxwPtr)
    register MxWindow *mxwPtr;		/* Window to be redisplayed. */
{
    register int i;
    register XFontStruct *fontPtr = mxwPtr->fontPtr;
    char string[16];
    XGCValues gcValues;

    /*
     * Calculate character widths that will be easier to deal with
     * than the ones in *fontPtr.
     */

    mxwPtr->fontHeight = mxwPtr->fontPtr->ascent + mxwPtr->fontPtr->descent;
    mxwPtr->charWidths = (unsigned char *) malloc(128);
    if ((fontPtr->min_byte1 != 0) || (fontPtr->max_byte1 != 0)) {
	Sx_Panic(mxwPtr->display,
		"MxSetupRedisplay got font with too many characters.");
    }
    if ((fontPtr->min_char_or_byte2 > 040)
	    || (fontPtr->max_char_or_byte2 < 126)) {
	Sx_Panic(mxwPtr->display,
		"MxSetupRedisplay got font that doesn't contain all the \
ASCII characters.");
    }
    for (i = 0; i < 128; i++) {
	char string[3];
	if (iscntrl(i)) {
	    string[0] = '^';
	    if (i == 127) {
		string[1] = '?';
	    } else {
		string[1] = i+64;
	    }
	    mxwPtr->charWidths[i] = XTextWidth(fontPtr, string, 2);
	} else {
	    string[0] = i;
	    mxwPtr->charWidths[i] = XTextWidth(fontPtr, string, 1);
	}
    }
    mxwPtr->charWidths['\n'] = mxwPtr->charWidths['\t'] = -1;

    sprintf(string, "%d",
	    mxwPtr->width/mxwPtr->fontPtr->max_bounds.width);
    Tcl_SetVar(mxwPtr->interp, "width", string, 1);
    sprintf(string, "%d",
	    mxwPtr->height/mxwPtr->fontHeight);
    Tcl_SetVar(mxwPtr->interp, "height", string, 1);
    mxwPtr->heightLines = mxwPtr->height/mxwPtr->fontHeight;
    if (mxwPtr->heightLines < 1) {
	mxwPtr->heightLines = 1;
    }
    mxwPtr->linePtr = (MxDisplayLine *)
	    malloc((unsigned) (mxwPtr->heightLines * sizeof(MxDisplayLine)));
    mxwPtr->lineArraySize = mxwPtr->heightLines;
    mxwPtr->tabWidth = TABSIZE * mxwPtr->charWidths[' '];
    mxwPtr->keyPosition.lineIndex = mxwPtr->keyPosition.charIndex = 0;
    mxwPtr->spy = Mx_SpyCreate(mxwPtr->fileInfoPtr->file,
	MX_AFTER, SpyProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(mxwPtr->display, mxwPtr->fileWindow,
	    ExposureMask|StructureNotifyMask, EventProc,
	    (ClientData) mxwPtr);
    mxwPtr->eofPixmap = XCreateBitmapFromData(mxwPtr->display,
	    DefaultRootWindow(mxwPtr->display), eof_bits, eof_width,
	    eof_height);
    if (mxwPtr->eofPixmap == None) {
	Sx_Panic(mxwPtr->display,
		"MxSetupRedisplay couldn't create eof pixmap.");
    }
    mxwPtr->shiftFirst = mxwPtr->shiftDistance = mxwPtr->shiftNumLines = 0;
    gcValues.stipple = XCreateBitmapFromData(mxwPtr->display,
	    DefaultRootWindow(mxwPtr->display), eof_bits,
	    eof_width, eof_height);
    gcValues.foreground = mxwPtr->foreground;
    gcValues.background = mxwPtr->background;
    gcValues.fill_style = FillOpaqueStippled;
    mxwPtr->eofGc = XCreateGC(mxwPtr->display, mxwPtr->fileWindow,
	    GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
    mxwPtr->eofYOrigin = 0;
    XFreePixmap(mxwPtr->display, gcValues.stipple);
    SetupView(mxwPtr, Mx_ZeroPosition, 0, mxwPtr->heightLines);
}

/*
 *----------------------------------------------------------------------
 *
 * MxCleanupRedisplay --
 *
 *	This procedure is invoked just before a window is deleted.
 *	It cleans up the redisplay-related part of the MxWindow
 *	structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory space gets freed up and handlers get deleted.
 *
 *----------------------------------------------------------------------
 */

void
MxCleanupRedisplay(mxwPtr)
    register MxWindow *mxwPtr;		/* Window that's about to go away. */
{
    free((char *) mxwPtr->linePtr);
    free((char *) mxwPtr->charWidths);
    Mx_SpyDelete(mxwPtr->spy);
    XFreeGC(mxwPtr->display, mxwPtr->eofGc);
}

/*
 *----------------------------------------------------------------------
 *
 * MxScroll --
 *
 *	Change the portion of a file that is displayed in a window.
 *	The parameters of this procedure are designed for it to be
 *	used directly as a scrollbar callback function.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The view will change to reflect the scrolling request.  The
 *	scrollbar will be updated to reflect the new view.
 *
 *----------------------------------------------------------------------
 */

void
MxScroll(mxwPtr, amount, type)
    register MxWindow *mxwPtr;	/* Window to be scrolled. */
    float amount;		/* Amount to scroll, used in conjunction
				 * with type. */
    int type;			/* How to scroll:  SX_SCROLL_ABSOLUTE
				 * or SX_SCROLL_PAGES.  See man page for
				 * Sx_ScrollbarMake for details. */
{
    MxCaretRedisplayText(mxwPtr->fileInfoPtr);
    if (type == SX_SCROLL_ABSOLUTE) {
	Mx_Position pos;
	
	pos = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
	pos.lineIndex = amount*pos.lineIndex + .5;
	pos.charIndex = 0;
	PositionView(mxwPtr, pos, (mxwPtr->heightLines-1)/2);
    } else if (type == SX_SCROLL_PAGES) {
	int delta;
	
	if (amount > 0) {
	    delta = amount*mxwPtr->heightLines + .5;
	    if (delta >= mxwPtr->heightLines) {
		delta = mxwPtr->heightLines - 1;
	    }
	    PositionView(mxwPtr,
		    mxwPtr->linePtr[mxwPtr->heightLines-1].position,
		    (mxwPtr->heightLines - 1) - delta);
	} else {
	    delta = -(amount*mxwPtr->heightLines - .5);
	    if (delta >= mxwPtr->heightLines) {
		delta = mxwPtr->heightLines - 1;
	    }
	    PositionView(mxwPtr, mxwPtr->linePtr[0].position, delta);
	}
    }
    mxwPtr->keyPosition = mxwPtr->linePtr[(mxwPtr->heightLines-1)/2].position;
}

/*
 *----------------------------------------------------------------------
 *
 * MxGetInWindow --
 *
 *	Ensures that a particular position in a file is visible on-screen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The display in mxwPtr will be adjusted so that the given
 *	position is visible in the window at the given lineIndex.
 *	If the position is already visible in the window and force
 *	is 0, then the view isn't adjusted after all.
 *
 *----------------------------------------------------------------------
 */

void
MxGetInWindow(mxwPtr, position, lineIndex, force)
    register MxWindow *mxwPtr;	/* Window whose view should be adjusted. */
    Mx_Position position;	/* Position in file associated with mxwPtr. */
    int lineIndex;		/* Index of line where position should 
				 * appear. */
    int force;		/* 1 means force position to appear at
				 * lineIndex, regardless of where it is now.
				 * 0 means only reposition if position
				 * is currently off-screen. */
{
    register MxDisplayLine *linePtr;

    mxwPtr->keyPosition = position;
    linePtr = mxwPtr->linePtr;
    if (!force && MX_POS_LEQ(linePtr->position, position)) {
	linePtr = &mxwPtr->linePtr[mxwPtr->heightLines-1];
	if ((linePtr->position.lineIndex > position.lineIndex)
		|| ((linePtr->position.lineIndex == position.lineIndex)
		&& ((linePtr->position.charIndex + linePtr->length)
		> position.charIndex))) {
	    return;
	}
    }
    MxCaretRedisplayText(mxwPtr->fileInfoPtr);
    PositionView(mxwPtr, position, lineIndex);
}

/*
 *----------------------------------------------------------------------
 *
 * MxFindPosition --
 *
 *	Given a pixel location in a window, this procedure locates
 *	the position in the file corresponding to that location.
 *
 * Results:
 *	*positionPtr is filled in with the closest character to
 *	the given (x,y) location in the window.  The return value
 *	is 1 if (x,y) is on the right side of the position'th
 *	character, and 0 if it is on the left.  If the line
 *	ends before x, then the position of the newline character
 *	at the end of the line is returned and the (x,y) location
 *	is considered to be on the left side of the character.  If
 *	the coordinate is off the end of the file, then the file's
 *	eof is returned along with a 1 result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
MxFindPosition(mxwPtr, x, y, positionPtr)
    register MxWindow *mxwPtr;	/* Window to which x and y pertain. */
    int x, y;			/* Pixel location within mxwPtr's
				 * fileWindow. */
    Mx_Position *positionPtr;	/* Character position to fill in. */
{
    CharPos info;
    int width;

    PointToChar(mxwPtr, x, y, &info);
    positionPtr->lineIndex = mxwPtr->linePtr[info.dlIndex].position.lineIndex;
    positionPtr->charIndex = info.charIndex;
    if (info.line[info.charIndex] == '\n') {
	return 0;
    }
    width = MxMeasureChars(mxwPtr, &info.line[info.charIndex], 1, info.x);
    if ((x - info.x) < (width/2)) {
	return 0;
    }
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * EventProc --
 *
 *	This procedure is invoked by the Sx package when a display-related
 *	event occurs on the window for a view.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The display gets updated.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
static void
EventProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;		/* Information about window. */
    register XEvent *eventPtr;		/* What happened. */
{
    switch (eventPtr->type) {

	/*
	 * If the window changed size then recompute the MxDisplayLine
	 * data structure describing the window.  Set a flag so that
	 * the window won't actually get redisplayed until the next
	 * Exposure event arrives.
	 */

	case ConfigureNotify: {
	    char string[20];
	    Mx_Position top;

	    if ((eventPtr->xconfigure.width == mxwPtr->width)
		    && (eventPtr->xconfigure.height == mxwPtr->height)) {
		break;
	    }
	    mxwPtr->width = eventPtr->xconfigure.width;
	    mxwPtr->height = eventPtr->xconfigure.height;
	    sprintf(string, "%d",
		    mxwPtr->width/mxwPtr->fontPtr->max_bounds.width);
	    Tcl_SetVar(mxwPtr->interp, "width", string, 1);
	    sprintf(string, "%d", mxwPtr->height/mxwPtr->fontHeight);
	    Tcl_SetVar(mxwPtr->interp, "height", string, 1);
	    mxwPtr->heightLines = mxwPtr->height/mxwPtr->fontHeight;
	    if (mxwPtr->heightLines < 1) {
		mxwPtr->heightLines = 1;
	    }
	    top = mxwPtr->linePtr[0].position;
	    if (mxwPtr->lineArraySize < mxwPtr->heightLines) {
		free((char *) mxwPtr->linePtr);
		mxwPtr->linePtr = (MxDisplayLine *)
			malloc((unsigned) (mxwPtr->heightLines
			* sizeof(MxDisplayLine)));
		mxwPtr->lineArraySize = mxwPtr->heightLines;
	    }

	    /*
	     * First try a view that has the same top line as the previous
	     * view.  Then make sure that the key position is still visible.
	     */
	     
	    SetupView(mxwPtr, top, 0, mxwPtr->heightLines);
	    MxGetInWindow(mxwPtr, mxwPtr->keyPosition,
		    mxwPtr->heightLines-1, 0);
	    mxwPtr->flags |= DELAY_REDISPLAY;
	    break;
	}

	/*
	 * A portion of the window became visible.  Compute what the
	 * portion is, and display it.
	 */

	case Expose: {
	    mxwPtr->flags &= ~DELAY_REDISPLAY;
	    MxRedisplayRegion(mxwPtr, eventPtr->xexpose.x,
		    eventPtr->xexpose.y, eventPtr->xexpose.width,
		    eventPtr->xexpose.height);
	    break;
	}

	/*
	 * The window has become mapped or unmapped;  set/clear a flag
	 * to indicate this.
	 */

	case MapNotify: {
	    mxwPtr->flags |= MAPPED;
	    break;
	}

	case UnmapNotify: {
	    mxwPtr->flags &= ~MAPPED;
	    break;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SpyProc --
 *
 *	This procedure is invoked by the Mx_File procedures just after
 *	any modifications in the database representing the file displayed
 *	in a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The screen gets updated to reflect the modifications to
 *	the file.
 *
 *----------------------------------------------------------------------
 */
	/* ARGSUSED */
static void
SpyProc(mxwPtr, file, type, first, oldLast, newLast, string)
    register MxWindow *mxwPtr;	/* Window to update. */
    Mx_File *file;		/* File that changed. */
    int type;			/* MX_AFTER. */
    Mx_Position first;		/* Location in file of first character
				 * inserted or deleted. */
    Mx_Position oldLast;	/* Position of character just after last
    				 * one deleted. */
    Mx_Position newLast;	/* Position of character just after last
    				 * one inserted. */
    char *string;		/* String inserted (not used here). */
{
    register MxDisplayLine *linePtr;
    int redisplayFirst;		/* First character to redisplay. */
    int firstIndex;		/* Index of display line corresponding to
				 * first. */
    int oldLastIndex;		/* Index of display line corresponding to
				 * oldLast after rounding (see below). */
    int newLastIndex;		/* Index of display line corresponding to
				 * newLAst after rounding (see below). */
    
    /*
     * Round first back to the beginning of its line.  If first was
     * offscreen to the top, then find the position that will be the
     * new upper-left character in the window.  This is needed to
     * handle properly cases where a range of bytes gets deleted that
     * spans the top of the window.  While doing this, remember which
     * portion of first's line will have to redisplayed (everything
     * from first to the end of the line, plus whatever was already
     * waiting to be redisplayed).
     */

    firstIndex = CharToLine(mxwPtr, first);
    if (firstIndex == OFF_TOP) {
	firstIndex = redisplayFirst = 0;
	FindTopAndBot(mxwPtr, newLast, 0, &first, (Mx_Position *) NULL);
    } else if (firstIndex == OFF_BOTTOM) {
	return;
    } else {
	linePtr = &mxwPtr->linePtr[firstIndex];
	redisplayFirst = first.charIndex;
	if ((linePtr->redisplayFirst != -1)
		&& (linePtr->redisplayFirst < redisplayFirst)) {
	    redisplayFirst = linePtr->redisplayFirst;
	}
	first = linePtr->position;
    }

    MxCaretRedisplayText(mxwPtr->fileInfoPtr);

    /*
     * Once a piece of a line is redisplayed, everything up to the end
     * of that file line has to be redisplayed (since characters could
     * have shifted around almost arbitrarily).  So, round newLast and
     * oldLast up to the beginning of the next line.
     */

    oldLast.charIndex = 0;
    oldLast.lineIndex++;
    newLast.charIndex = 0;
    newLast.lineIndex++;
    oldLastIndex = CharToLine(mxwPtr, oldLast);
    newLastIndex = firstIndex + CountLines(mxwPtr, first, newLast) - 1;
    if (newLastIndex > mxwPtr->heightLines) {
	newLastIndex = mxwPtr->heightLines;
    }

    /*
     * Because of the modification, some of the line numbers stored
     * in our display line information may have to be adjusted.
     * WARNING:  make all CharToLine calls before updating the line
     * info;  calls may fail if made when the line info is partially
     * updated.
     */

    if (oldLastIndex != OFF_BOTTOM) {
	int lineMotion, i;
	
	lineMotion = newLast.lineIndex - oldLast.lineIndex;
	if (oldLastIndex == OFF_TOP) {
	    i = 0;
	} else {
	    i = oldLastIndex;
	}
	for (linePtr = &mxwPtr->linePtr[i]; i < mxwPtr->heightLines;
		i++, linePtr++) {
	    linePtr->position.lineIndex += lineMotion;
	}
    }

    /*
     * If the changed lines weren't visible, then just return.
     */
     
    if ((oldLastIndex == OFF_TOP) || (oldLastIndex == 0)) {
	return;
    }
    if (oldLastIndex == OFF_BOTTOM) {
	oldLastIndex = mxwPtr->heightLines;
    }

    /*
     * There are three ranges of lines that are of interest now:
     * 1. The stuff that was on the screen just below what changed;
     *    we may be able to shift this up or down to avoid redisplaying
     *	  it.
     * 2. The stuff at the bottom of the new window:  if we deleted
     *    a bunch of stuff, we could shift some old stuff up, but there
     *    will be stuff at the bottom of the window that has to be
     *	  redisplayed.
     * 3. The stuff that changed, which we'll have to redisplay.
     */

    if (newLastIndex < mxwPtr->heightLines) {
	int shift;

	shift = newLastIndex - oldLastIndex;
	if (shift > 0) {
	    ShiftScreen(mxwPtr, oldLastIndex,
		    mxwPtr->heightLines - newLastIndex, shift);
	} else if (shift < 0) {
	    int i, tmp1, tmp2;

	    if (oldLastIndex < mxwPtr->heightLines) {
		ShiftScreen(mxwPtr, oldLastIndex,
			mxwPtr->heightLines - oldLastIndex, shift);
		i = mxwPtr->heightLines + shift - 1;
		linePtr = &mxwPtr->linePtr[i];
		tmp1 = linePtr->redisplayFirst;
		tmp2 = linePtr->redisplayLast;
		SetupView(mxwPtr, mxwPtr->linePtr[i].position, i,
			mxwPtr->heightLines - i);
		linePtr->redisplayFirst = tmp1;
		linePtr->redisplayLast = tmp2;
	    } else {
		SetupView(mxwPtr, newLast, newLastIndex,
			mxwPtr->heightLines - newLastIndex);
	    }
	}
    }

    /*
     * One final optimization: SetupView arranged for all of the first
     * line to be redisplayed.  That isn't strictly necessary, so change
     * the redisplay information.
     */

    SetupView(mxwPtr, first, firstIndex, newLastIndex - firstIndex);
    mxwPtr->linePtr[firstIndex].redisplayFirst = redisplayFirst;
}

/*
 *----------------------------------------------------------------------
 *
 * MxMeasureChars --
 *
 *	Compute how many pixels wide a set of characters will be.
 *
 * Results:
 *	The return value is how many pixels wide the first numChars
 *	characters of string are, when displayed by Mx using mxwPtr's
 *	font.  Because of control characters and tabs, this procedure
 *	produces different results than XTextWidth.  If the end of
 *	the string is reached before numChars, then the counting stops.
 *	If a newline appears in the string, then the width will be
 *	the width of mxwPtr's window minus x.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
MxMeasureChars(mxwPtr, string, numChars, x)
    register MxWindow *mxwPtr;	/* Window whose font should be used for
    				 * measurement. */
    register char *string;	/* First character to measure. */
    register int numChars;	/* How many characters of string to use.
				 * If -1, then use whole string. */
    int x;			/* Location on line of first character
				 * in string.  Needed to compute widths
				 * of tabs and newlines correctly. */
{
    register int newX;
    
    for (newX = x; numChars != 0; numChars--, string++) {
	if (*string <= '\n') {
	    if (*string == '\t') {
		newX += mxwPtr->tabWidth;
		newX -= newX % mxwPtr->tabWidth;;
	    } else if (*string == '\n') {
		newX = mxwPtr->width;
	    } else if (*string == 0) {
		break;
	    } else {
		newX += mxwPtr->charWidths[*string];
	    }
	} else {
	    newX += mxwPtr->charWidths[*string];
	}
    }
    return newX - x;
}

/*
 *----------------------------------------------------------------------
 *
 * MeasureLength --
 *
 *	Count how many characters from a given string can fit in a
 *	given horizontal range.
 *
 * Results:
 *	Assuming that the left edge of the first character in string
 *	is at x-coordinate left, the return value is a pointer to the
 *	first character in string that will cover x-coordinate right,
 *	assuming that string is displayed with mxwPtr's font.  *lastXPtr
 *	is filled in with the x-coordinate of the left edge of the
 *	character returned. If all the characters in the string fit in
 *	the range, then the return value is a pointer to the string's
 *	terminating NULL character and *lastXPtr gets filled in with
 *	the right edge of the last non-null character.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
MeasureLength(mxwPtr, string, left, right, lastXPtr)
    register MxWindow *mxwPtr;	/* Window whose font should be used for
    				 * measurement. */
    register char *string;	/* First character to measure. */
    register int left;		/* Location on line of left edge of first
				 * character in string.  Needed to compute
				 * widths of tabs and newlines correctly. */
    int right;			/* X-coordinate of right edge of area of
				 * interest. */
    int *lastXPtr;		/* Gets filled in with x-coord of left edge
				 * of character that crossed right.  If NULL,
				 * nothing's stored. */
{
    int lastX;
    
    while (1) {
	lastX = left;
	if (*string <= '\n') {
	    if (*string == '\t') {
		left += mxwPtr->tabWidth;
		left -= left % mxwPtr->tabWidth;
	    } else if (*string == '\n') {
		left = mxwPtr->width;
		break;
	    } else if (*string == 0) {
		break;
	    } else  {
		left += mxwPtr->charWidths[*string];
	    }
	} else {
	    left += mxwPtr->charWidths[*string];
	}
	if (left > right) {
	    break;
	}
	string++;
    }
    if (lastXPtr != NULL) {
	*lastXPtr = lastX;
    }
    return string;
}

/*
 *----------------------------------------------------------------------
 *
 * PointToChar --
 *
 *	This procedure locates the character corresponding to a
 *	particular point in the window.
 *
 * Results:
 *	*infoPtr is modified to store information about the character
 *	corresponding to (x,y) in mxwPtr's window.  If there's no character
 *	at that location (e.g. the point is outside the window's boundary
 *	or off the end of the file), then the closest character (e.g.
 *	last char in file) is returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PointToChar(mxwPtr, x, y, infoPtr)
    register MxWindow *mxwPtr;	/* Window that this is all about. */
    int x, y;			/* Coordinates in mxwPtr->fileWindow. */
    register CharPos *infoPtr;	/* To be filled in with info about
    				 * character corresponding to x and y. */
{
    register MxDisplayLine *linePtr;
    Mx_Position eof;

    /*
     * Find the display line that contains y.  If y is off-window,
     * return the character at one end of the window or the other.
     * If the coordinate is off the end of the file, return info
     * abou the last character in the file.
     */

    if (y < 0) {
	y = 0;
	x = 0;
    } else if (y >= mxwPtr->height) {
	y = mxwPtr->height-1;
	x = mxwPtr->width-1;
    }
    infoPtr->dlIndex = y/mxwPtr->fontHeight;

    /*
     * Careful!  If the window isn't an even number of lines high, there'll
     * be an unused sliver at the bottom of the window.  Round positions in
     * here back to the bottom full line.
     */

    if (infoPtr->dlIndex >= mxwPtr->heightLines) {
	infoPtr->dlIndex = mxwPtr->heightLines-1;
    }
    infoPtr->y = infoPtr->dlIndex * mxwPtr->fontHeight;
    linePtr = &mxwPtr->linePtr[infoPtr->dlIndex];
    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    while (MX_POS_LESS(eof, linePtr->position)) {
	linePtr--;
	infoPtr->dlIndex--;
	x = mxwPtr->width-1;
    }

    /*
     * Find the character position within the line.
     */

    if (x < 0) {
	x = 0;
    } else if (x >= mxwPtr->width) {
	x = mxwPtr->width-1;
    }
    infoPtr->line = Mx_GetLine(mxwPtr->fileInfoPtr->file,
	    linePtr->position.lineIndex, (int *) NULL);
    infoPtr->charIndex = MeasureLength(mxwPtr,
	    infoPtr->line + linePtr->position.charIndex, 0, x, &infoPtr->x)
	    - infoPtr->line;
}

/*
 *----------------------------------------------------------------------
 *
 * CharToLine --
 *
 *	Given a character in a file, find the display line where
 *	that character appears (if any).
 *
 * Results:
 *	The return value is the display line on which the character
 *	at position will be displayed in mxwPtr's window.  If the
 *	character doesn't appear in the window, then a negative
 *	value is returned:  OFF_TOP means off-screen to the top of the
 *	window, and OFF_BOTTOM means off-screen to the bottom of
 *	the window.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CharToLine(mxwPtr, position)
    register MxWindow *mxwPtr;	/* Window in which caller wants to know
				 * where position is displayed. */
    Mx_Position position;	/* Position in file of character. */
{
    register MxDisplayLine *linePtr;
    int inc, index;

    /*
     * Can do indexing to make this relatively fast, but it may still
     * take several indexes, since some file lines may occupy several
     * display lines.
     */

    linePtr = mxwPtr->linePtr;
    index = 0;
    if (MX_POS_LESS(position, linePtr->position)) {
	return OFF_TOP;
    }
    while (1) {
	inc = position.lineIndex - linePtr->position.lineIndex;
	if (inc == 0) {
	    if (position.charIndex < (linePtr->position.charIndex +
		    linePtr->length)) {
		return index;
	    } else {
		inc = 1;
	    }
	}
	index += inc;
	linePtr += inc;
	if (index >= mxwPtr->heightLines) {
	    return OFF_BOTTOM;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxCharToPoint --
 *
 *	Locate the upper-left corner of a particular character, given
 *	its position in the file.
 *
 * Results:
 *	The return value is < 0 if the given character position is
 *	off-screen to the top and > 0 if the given position is off-screen
 *	to the bottom.  Otherwise the return value is 0 and *xPtr and
 *	*yPtr are filled in with the location of the character's upper
 *	left corner on the screen.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
MxCharToPoint(mxwPtr, position, xPtr, yPtr)
    register MxWindow *mxwPtr;	/* Window of interest. */
    Mx_Position position;	/* Position of character in mxwPtr's file. */
    int *xPtr, *yPtr;		/* Filled in with location of character's
    				 * UL corner (if position is on-screen). */
{
    register MxDisplayLine *linePtr;
    int dlIndex;
    char *line;

    dlIndex = CharToLine(mxwPtr, position);
    if (dlIndex < 0) {
	if (dlIndex == -1) {
	    return -1;
	} else {
	    return 1;
	}
    }
    linePtr = &mxwPtr->linePtr[dlIndex];
    
    /*
     * Find the location of the character in its line.
     */

    *yPtr = dlIndex * mxwPtr->fontHeight;
    line = Mx_GetLine(mxwPtr->fileInfoPtr->file,
	    linePtr->position.lineIndex, (int *) NULL);
    *xPtr = MxMeasureChars(mxwPtr, line + linePtr->position.charIndex,
	    position.charIndex - linePtr->position.charIndex, 0);
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * MxUpdateWindow --
 *
 *	Redisplay any out-of-date information pertaining to mxwPtr.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The screen is redisplayed where necessary in order to bring
 *	the window back into agreement with the database in memory.
 *	This is the only procedure that actually does redisplay (other
 *	procedures simply save around info about what to redraw;
 *	this avoids multiple redisplays when many small changes are
 *	made in a row).
 *
 * Design trick:
 *	X seems to be much faster using XDrawString than XDrawImageString
 *	(at least on Sun-2's and Sun-3's it is).  So, the code below checks
 *	for the special case of a range of lines being completely redisplayed.
 *	in this case, it clears the whole area once, then uses XDrawString.
 *	This handles scrolling efficiently.  In the other cases, the slower
 *	XDrawImageString operation gets used.
 *
 *----------------------------------------------------------------------
 */

void
MxUpdateWindow(mxwPtr)
    register MxWindow *mxwPtr;		/* Window to update. */
{
    int width, x, y, lineCount, charCount;
    Mx_Position position, position2;
    register MxDisplayLine *linePtr;	/* Current line. */
    register MxDisplayLine *clearPtr;	/* All lines down to (but not including
					 * this one) have already been
					 * cleared. */
    register char *line;
    char *line2;
    int eofY;			/* Y-coordinate of end-of-file. */

    if (mxwPtr->flags & DELAY_REDISPLAY) {
	return;
    }
    
    /*
     * If there's a screen shift waiting to be done, carry it out.
     */

    if (mxwPtr->shiftNumLines > 0) {
	DoShift(mxwPtr);
    }

    /*
     * Iterate over all of the lines in the window, updating any that
     * need it.
     */

    clearPtr = mxwPtr->linePtr;
    eofY = CharToLine(mxwPtr, Mx_EndOfFile(mxwPtr->fileInfoPtr->file));
    if (eofY < 0) {
	eofY = mxwPtr->height;
    } else {
	eofY = (eofY + 1) * mxwPtr->fontHeight;
    }
    for (linePtr = mxwPtr->linePtr, lineCount = mxwPtr->heightLines, y = 0;
	    lineCount > 0;
	    linePtr++, lineCount--, y += mxwPtr->fontHeight) {
	if (linePtr->redisplayFirst < 0) {
	    continue;
	}

	/*
	 * Before redisplaying this line, see if we can clear out a whole
	 * area of the screen and then use XDrawString operations.  Don't
	 * clear stuff past the end of the file:  this introduces extra
	 * blinking on the screen.
	 */

	if (clearPtr <= linePtr) {
	    int clearCount, clearHeight;
	    
	    for (clearPtr = linePtr, clearCount = 0; clearCount < lineCount;
		    clearCount++, clearPtr++) {
		if ((clearPtr->redisplayFirst != clearPtr->position.charIndex)
			|| (clearPtr->redisplayLast !=
			(clearPtr->position.charIndex + clearPtr->length
			- 1))) {
		    break;
		}
	    }
	    if (clearCount != 0) {
		clearHeight = mxwPtr->fontHeight*clearCount;
		if ((y + clearHeight) > eofY) {
		    clearHeight = eofY - y;
		}
		if (clearHeight > 0) {
		    XClearArea(mxwPtr->display, mxwPtr->fileWindow, 0, y,
			    mxwPtr->width, clearHeight, False);
		}
	    }
	}

	/*
	 * Divide the line up into pieces, where each piece is one of:
	 * (a) a string of text from the line,
	 * (b) a two-character string of the form "^X", fabricated
	 *     as a replacement for a control character,
	 * (c) an area corresponding to white space (tabs, newline, or
	 *     extra space left over at end of line when one file line
	 *     corresponds to multiple display lines, or
	 * (d) an area corresponding to empty space past the end of the
	 *     file.
	 */

	position.lineIndex = linePtr->position.lineIndex;
	line = Mx_GetLine(mxwPtr->fileInfoPtr->file,
		linePtr->position.lineIndex, (int *) NULL);
	if (line == NULL) {			/* Case (d): end of file. */
	    width = mxwPtr->width;
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->eofGc,
		    0, y, width, mxwPtr->fontHeight);
	    linePtr->redisplayFirst = -1;
	    continue;
	}

	x = MxMeasureChars(mxwPtr, line + linePtr->position.charIndex,
		(linePtr->redisplayFirst - linePtr->position.charIndex), 0);
	line += linePtr->redisplayFirst;
	position.charIndex = linePtr->redisplayFirst;
	while (position.charIndex <= linePtr->redisplayLast) {
	    if (iscntrl(*line)) {
		if (*line == '\n') {		/* Case (c): newline. */
		    break;
		} else if (*line == '\t') {	/* Case (c): tab. */
		    width = mxwPtr->tabWidth - x%mxwPtr->tabWidth;
		    if (linePtr >= clearPtr) {
			XClearArea(mxwPtr->display, mxwPtr->fileWindow,
				x, y, width, mxwPtr->fontHeight, False);
		    }
		    MxDisplayHighlights(mxwPtr, position, 1, x, y, line);
		    position.charIndex++;
		    line++;
		    x += width;
		    continue;
		} else {			/* Case (b): control char. */
		    char control[2];

		    control[0] = '^';
		    if (*line == 0177) {
			control[1] = '?';
		    } else {
			control[1] = *line + 64;
		    }
		    width = MxMeasureChars(mxwPtr, line, 1, x);
		    if (linePtr >= clearPtr) {
			XDrawImageString(mxwPtr->display, mxwPtr->fileWindow,
				mxwPtr->textGc, x, y + mxwPtr->fontPtr->ascent,
				control, 2);
		    } else {
			XDrawString(mxwPtr->display, mxwPtr->fileWindow,
				mxwPtr->textGc, x, y + mxwPtr->fontPtr->ascent,
				control, 2);
		    }
		    MxDisplayHighlights(mxwPtr, position, 1, x, y, line);
		    position.charIndex++;
		    line++;
		    x += width;
		    continue;
		}
	    }

	    /*
	     * Case (a): grab up as many characters as possible without
	     * including a control character or going past the redisplay
	     * range.
	     */

	    for (line2 = line, position2 = position;
		    (position.charIndex <= linePtr->redisplayLast)
		    && !iscntrl(*line);
		    line++, position.charIndex++) {
		/* Null loop body. */
	    }
	    charCount = line - line2;
	    width = MxMeasureChars(mxwPtr, line2, charCount, x);
	    if (linePtr >= clearPtr) {
		XDrawImageString(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->textGc, x, y + mxwPtr->fontPtr->ascent, line2,
			charCount);
	    } else {
		XDrawString(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->textGc, x, y + mxwPtr->fontPtr->ascent, line2,
			charCount);
	    }
	    MxDisplayHighlights(mxwPtr, position2, charCount, x, y, line2); 
	    x += width;
	}

	/*
	 * Case (d): leftover space at end of line:  only redisplay this
	 * if we're really at the end of the line (i.e. the last character
	 * being redisplayed is also the last on the line).
	 */

	width = (mxwPtr->width - x);
	if ((width > 0) &&
		(linePtr->redisplayLast == (linePtr->position.charIndex +
		linePtr->length - 1))) {
	    charCount = 1;
	    if (linePtr >= clearPtr) {
		XClearArea(mxwPtr->display, mxwPtr->fileWindow, x, y,
			width, mxwPtr->fontHeight, False);
	    }
	    MxDisplayHighlights(mxwPtr, position, 1, x, y, "\n");
	}
	linePtr->redisplayFirst = -1;
    }

    /*
     * See if there's a sliver at the bottom of the screen that needs
     * to be redrawn.  The sliver needs to be redrawn if requested
     * explicitly (through the REDRAW_SLIVER flag), or if its style
     * of display (eof vs. background) has changed.
     */
    
    if (y < mxwPtr->height) {
	int sliverFlag;

	sliverFlag = 0;
	if (y >= eofY) {
	    sliverFlag = SLIVER_EOF;
	}
	if ((mxwPtr->flags & REDRAW_SLIVER)
		|| ((mxwPtr->flags & SLIVER_EOF) != sliverFlag)) {
	    mxwPtr->flags &= ~REDRAW_SLIVER;
	    if (y >= eofY) {
		XFillRectangle(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->eofGc, 0, y, mxwPtr->width,
			mxwPtr->height - y);
		mxwPtr->flags |= SLIVER_EOF;
	    } else {
		XClearArea(mxwPtr->display, mxwPtr->fileWindow, 0, y,
			mxwPtr->width, mxwPtr->height - y, False);
		mxwPtr->flags &= ~SLIVER_EOF;
	    }
	}
    }

    /*
     * All the text is up-to-date.  Update the scrollbar.
     */

    if (mxwPtr->scrollbar != 0) {
	float top, bottom;
	Mx_Position eof;
	
	eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
	top = mxwPtr->linePtr[0].position.lineIndex;
	top /= (eof.lineIndex+1);
	if (eof.lineIndex == 0) {
	    bottom = 1.0;
	} else {
	    bottom = mxwPtr->linePtr[mxwPtr->heightLines-1].position.lineIndex;
	    bottom /= eof.lineIndex;
	}
	Sx_ScrollbarSetRange(mxwPtr->display, mxwPtr->scrollbar, top, bottom);
    }

    mxwPtr->flags &= ~NEEDS_UPDATE;
}

/*
 *----------------------------------------------------------------------
 *
 * MxRedisplayRange --
 *
 *	Arrange for a range within a file to be redisplayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information is saved around so that the range of characters
 *	between first and last, inclusive, will be redisplayed in
 *	mxwPtr (if any of the range is visible in the window).  No
 *	redisplay occurs here;  Mx_Update must be called to
 *	actually do the redisplay.
 *
 *----------------------------------------------------------------------
 */

void
MxRedisplayRange(mxwPtr, first, last)
    register MxWindow *mxwPtr;	/* Window in which redisplay is to happen. */
    Mx_Position first;		/* First byte to be redisplayed (in mxwPtr's
				 * file). */
    Mx_Position last;		/* Last byte to be redisplayed (in mxwPtr's
				 * file). */
{
    register MxDisplayLine *linePtr;
    int firstChar, lastChar, index;

    index = CharToLine(mxwPtr, first);
    if (index < 0) {
	if (index == OFF_BOTTOM) {
	    return;
	}
	linePtr = mxwPtr->linePtr;
    } else {
	linePtr = &mxwPtr->linePtr[index];
    }

    mxwPtr->flags |= NEEDS_UPDATE;
    while (MX_POS_LEQ(linePtr->position, last)
	    && (index < mxwPtr->heightLines)) {
	if ((linePtr->position.lineIndex == first.lineIndex) &&
		(first.charIndex >= linePtr->position.charIndex)) {
	    firstChar = first.charIndex;
	} else {
	    firstChar = linePtr->position.charIndex;
	}
	lastChar = linePtr->position.charIndex + linePtr->length - 1;
	if ((linePtr->position.lineIndex == last.lineIndex) &&
		(last.charIndex < lastChar)) {
	    lastChar = last.charIndex;
	}

	/*
	 * Must merge this redisplay request with any previous ones
	 * for the line.
	 */
	 
	if (linePtr->redisplayFirst >= 0) {
	    if (linePtr->redisplayFirst > firstChar) {
		linePtr->redisplayFirst = firstChar;
	    }
	    if (linePtr->redisplayLast < lastChar) {
		linePtr->redisplayLast = lastChar;
	    }
	} else {
	    linePtr->redisplayFirst = firstChar;
	    linePtr->redisplayLast = lastChar;
	}
	firstChar = 0;
	index++;
	linePtr++;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxRedisplayRegion --
 *
 *	Given an area of a window (in pixels), mark everything in that
 *	area to force it to be redisplayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information is retained so that area in mxwPtr given by
 *	x, y, width, and height will be redisplayed on the next
 *	call to Mx_Update.  Nothing is actually redisplayed now.
 *
 *----------------------------------------------------------------------
 */

void
MxRedisplayRegion(mxwPtr, x, y, width, height)
    register MxWindow *mxwPtr;		/* Window in which to redisplay. */
    int x, y, width, height;		/* Area to redisplay. */
{
    register MxDisplayLine *linePtr;
    int lastX, lastY, lastDisplayY, lineIndex;

    /*
     * Clip to window dimensions and see if there's a sliver at the
     * bottom of the window that must be redisplayed.
     */
     
    lastX = x + width;
    lastY = y + height;
    if (x < 0) {
	x = 0;
    }
    if (y < 0) {
	y = 0;
    }
    if (lastX > mxwPtr->width) {
	lastX = mxwPtr->width;
    }
    lastX--;
    if (x > lastX) {
	return;
    }
    lastDisplayY = mxwPtr->heightLines * mxwPtr->fontHeight;
    if (lastY > lastDisplayY) {
	if (mxwPtr->height > lastDisplayY) {
	    mxwPtr->flags |= REDRAW_SLIVER;
	}
	lastY = lastDisplayY;
    }
    lastY--;
    
    /*
     * Iterate over all lines in region.
     */

    mxwPtr->flags |= NEEDS_UPDATE;
    lineIndex = y/mxwPtr->fontHeight;
    y = lineIndex*mxwPtr->fontHeight;
    for (linePtr = &mxwPtr->linePtr[lineIndex]; y <= lastY;
	    y += mxwPtr->fontHeight, linePtr++) {
	int x2, firstChar, lastChar, numChars;
	char *line, *line2;

	/*
	 * Find the range of characters in this line that correspond
	 * to the desired x-range.
	 */
	 
	line = Mx_GetLine(mxwPtr->fileInfoPtr->file,
		linePtr->position.lineIndex, (int *) NULL);
	if (line == NULL) {
	    line = "\n";
	}
	line += linePtr->position.charIndex;
	line2 = MeasureLength(mxwPtr, line, 0, x, &x2);
	firstChar = linePtr->position.charIndex + line2 - line;
	numChars = MeasureLength(mxwPtr, line2, x2, lastX, &x2) - line;
	if (numChars >= linePtr->length) {
	    numChars = linePtr->length - 1;
	}
	lastChar = numChars + linePtr->position.charIndex;

	/*
	 * Merge this redisplay region with any previously-recorded
	 * redisplay areas for the same line.
	 */
	 
	if (linePtr->redisplayFirst >= 0) {
	    if (linePtr->redisplayFirst > firstChar) {
		linePtr->redisplayFirst = firstChar;
	    }
	    if (linePtr->redisplayLast < lastChar) {
		linePtr->redisplayLast = lastChar;
	    }
	} else {
	    linePtr->redisplayFirst = firstChar;
	    linePtr->redisplayLast = lastChar;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CountLines --
 *
 *	This procedure determines how many display lines will be required
 *	to represent one or more lines of a file (if a line is very
 *	long, it may wrap around and occupy several lines of the
 *	window).
 *
 * Results:
 *	The return value is the number of lines required in the window
 *	of mxwPtr to display characters first through last of mxwPtr's
 *	file, assuming that first appears at the beginning of a line.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CountLines(mxwPtr, first, last)
    register MxWindow *mxwPtr;	/* Information about window. */
    Mx_Position first, last;	/* Range of characters to analyze.  Last must
				 * be >= first. */
{
    int count = 0;
    char *line, *line2;

    while (MX_POS_LEQ(first, last)) {
	line = Mx_GetLine(mxwPtr->fileInfoPtr->file, first.lineIndex,
		(int *) NULL);
	if (line == NULL) {
	    line = "\n";
	}
	line2 = MeasureLength(mxwPtr, line + first.charIndex, 0,
		mxwPtr->width, (int *) NULL);
	if (*line2 == '\n') {
	    first.lineIndex++;
	    first.charIndex = 0;
	} else {
	    if (line2 == (line + first.charIndex)) {
		first.charIndex++;
	    } else {
		first.charIndex = line2 - line;
	    }
	}
	count++;
    }
    return count;
}

/*
 *----------------------------------------------------------------------
 *
 * PositionView --
 *
 *	Adjust the view in a window, scrolling and/or arranging for
 *	later redisplay, to ensure that a particular character will
 *	appear on a particular line of the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The linePtr structure of mxwPtr gets modified, and redisplay
 *	information is left around.
 *
 *----------------------------------------------------------------------
 */

static void
PositionView(mxwPtr, pos, index)
    register MxWindow *mxwPtr;
    Mx_Position pos;		/* Position within mxwPtr's file. */
    int index;			/* Display line number on which position
				 * should appear (must be between 0 and
				 * mxwPtr->heightLines-1, inclusive). */
{
    register MxDisplayLine *linePtr;
    Mx_Position newTop, newBot, tmp;
    int i, j, length;

    /*
     * See if the position is already displayed in the right place.
     */

    linePtr = &mxwPtr->linePtr[index];
    if ((linePtr->position.lineIndex == pos.lineIndex)
	    && (linePtr->position.charIndex <= pos.charIndex)
	    && ((linePtr->position.charIndex + linePtr->length)
		    > pos.charIndex)) {
	return;
    }
    
    /*
     * Find the characters that should appear at the left of the top
     * and bottom screen lines.  Then see if either of those positions
     * is already on-screen.  If so, we can save redisplay by scrolling
     * the existing bits.
     */

    FindTopAndBot(mxwPtr, pos, index, &newTop, &newBot);
    i = CharToLine(mxwPtr, newTop);
    if (i >= 0) {
	j = mxwPtr->heightLines - i;
	linePtr = &mxwPtr->linePtr[mxwPtr->heightLines-1];
	tmp.lineIndex = linePtr->position.lineIndex;
	tmp.charIndex = linePtr->position.charIndex + linePtr->length;
	if ((Mx_GetLine(mxwPtr->fileInfoPtr->file, tmp.lineIndex, &length)
		== NULL) || (length <= tmp.charIndex)) {
	    tmp.lineIndex += 1;
	    tmp.charIndex = 0;
	}
	ShiftScreen(mxwPtr, i, j, -i);
	SetupView(mxwPtr, tmp, j, i);
	return;
    }
    i = CharToLine(mxwPtr, newBot);
    if (i >= 0) {
	i += 1;
	j = mxwPtr->heightLines - i;
	ShiftScreen(mxwPtr, 0, i, j);
	SetupView(mxwPtr, newTop, 0, j);
	return;
    }
    SetupView(mxwPtr, newTop, 0, mxwPtr->heightLines);
}

/*
 *----------------------------------------------------------------------
 *
 * ShiftScreen --
 *
 *	This procedure is called to move information on the screen
 *	using a raster op, in order to avoid redrawing stuff that's
 *	just been moved.  This procedure doesn't actually do the
 *	raster op;  it just saves information about the raster op.
 *	the actual operation is done by DoShift.  The reason for
 *	this is that ShiftScreen tries to merge multiple shifts into
 *	a single large one, for example, when the screen gets scrolled
 *	up several times one-line-at-a-time.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Updates the linePtr structure to reflect the shift, and saves
 *	information about the shift in mxwPtr for use by DoShift later.
 *	In some cases, where multiple interacting shifts have been
 *	requested, the previous shift gets done here.
 *
 *----------------------------------------------------------------------
 */

static void
ShiftScreen(mxwPtr, firstLine, numLines, distance)
    register MxWindow *mxwPtr;	/* Window in which to shift. */
    int firstLine;		/* Index of first display line to be
				 * shifted. */
    int numLines;		/* Number of lines to be shifted. */
    int distance;		/* Distance to shift, in lines.  May be
				 * positive (for down) or negative (for up). */
{
    int i, top, bottom;
    register MxDisplayLine *srcPtr, *dstPtr;

    /*
     * Copy the information in the display line array.  Be careful about the
     * order of copying, or you may overwrite information before it can be
     * copied.  While copying, compute the topmost and bottommost source
     * lines that contain any useful information (there's no point in scrolling
     * a line if it has to be completely redisplayed anyway).
     */

    top = numLines;
    bottom = -1;
    if (distance > 0) {
	for (srcPtr = &mxwPtr->linePtr[firstLine + numLines - 1],
		dstPtr = srcPtr + distance, i = numLines - 1;
		i >= 0; i--, srcPtr--, dstPtr--) {
	    if ((srcPtr->redisplayFirst != srcPtr->position.charIndex) ||
		    (srcPtr->redisplayLast !=
		    (srcPtr->redisplayFirst + srcPtr->length - 1))) {
		if (i < top) {
		    top = i;
		}
		if (i > bottom) {
		    bottom = i;
		}
	    }
	    *dstPtr = *srcPtr;
	}
    } else {
	for (srcPtr = &mxwPtr->linePtr[firstLine],
		dstPtr = srcPtr + distance, i = 0;
		i < numLines; i++, srcPtr++, dstPtr++) {
	    if ((srcPtr->redisplayFirst != srcPtr->position.charIndex) ||
		    (srcPtr->redisplayLast !=
		    (srcPtr->redisplayFirst + srcPtr->length - 1))) {
		if (i < top) {
		    top = i;
		}
		if (i > bottom) {
		    bottom = i;
		}
	    }
	    *dstPtr = *srcPtr;
	}
    }
    firstLine += top;
    numLines = bottom + 1 - top;
    if (numLines <= 0) {
	return;
    }

    /*
     * Now see if this range is compatible with the previous shift range
     * (if any).  The two shifts can be combined into a single shift if:
     * (a) the new source range is a subset of the old destination range;
     * and (b) the new total range (source + dest) is a superset of the
     * old destination range.  In this case the old shift information
     * can just be replaced by (slightly-modified) new information.  If
     * the ranges aren't compatible, then just perform the old shift and
     * replace the saved information with info about the new shift.
     */

    
    if (mxwPtr->shiftNumLines > 0) {
	int rangeTop, rangeBottom, oldTargTop, oldTargBottom;

	rangeTop = firstLine;
	rangeBottom = firstLine + numLines;
	if (distance < 0) {
	    rangeTop += distance;
	} else {
	    rangeBottom += distance;
	}
	oldTargTop = mxwPtr->shiftFirst + mxwPtr->shiftDistance;
	oldTargBottom = mxwPtr->shiftFirst + mxwPtr->shiftNumLines
		+ mxwPtr->shiftDistance;
	if ((firstLine >= oldTargTop)
		&& ((firstLine + numLines) <= oldTargBottom)
		&& (rangeTop <= oldTargTop)
		&& (rangeBottom >= oldTargBottom)) {
	    mxwPtr->shiftFirst = firstLine -= mxwPtr->shiftDistance;
	    mxwPtr->shiftDistance = distance + mxwPtr->shiftDistance;
	    mxwPtr->shiftNumLines = numLines;
	    if (mxwPtr->shiftDistance == 0) {
		mxwPtr->shiftNumLines = 0;
	    }
	    return;
	}
	DoShift(mxwPtr);
    }

    /*
     * Either there was no previous shift information, or the previous
     * shift has now been carried out.  In either case, just store information
     * about this shift.
     */

    mxwPtr->shiftFirst = firstLine;
    mxwPtr->shiftDistance = distance;
    mxwPtr->shiftNumLines = numLines;
    mxwPtr->flags |= NEEDS_UPDATE;
}

/*
 *----------------------------------------------------------------------
 *
 * DoShift --
 *
 *	This procedure is the one that actually performs the raster
 *	op that shifts the screen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Shifts information on the screen.  Also makes sure that none of
 *	the source area is obscured.  If so, it adds to the redisplay
 *	information for the window.
 *
 *----------------------------------------------------------------------
 */

static void
DoShift(mxwPtr)
    register MxWindow *mxwPtr;	/* Window in which to shift. The information
				 * about what to shift is stored in mxwPtr. */
{
    int srcY, dstY, height;

    /*
     * Copy the information on the screen.
     */

    srcY = mxwPtr->shiftFirst*mxwPtr->fontHeight;
    dstY = srcY + mxwPtr->shiftDistance*mxwPtr->fontHeight;
    height = mxwPtr->shiftNumLines*mxwPtr->fontHeight;
    XCopyArea(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->fileWindow,
	    mxwPtr->textGc, 0, srcY, mxwPtr->width, height, 0, dstY);
    mxwPtr->shiftNumLines = 0;

    /*
     * Modify the graphics context used for displaying the end-of-file
     * stipple to change its origin.  Otherwise new stipples drawn after
     * the shift will not line up with old ones drawn before the shift.
     */

    mxwPtr->eofYOrigin = (mxwPtr->eofYOrigin + dstY - srcY)%eof_height;
    XSetTSOrigin(mxwPtr->display, mxwPtr->eofGc, 0, mxwPtr->eofYOrigin);

    /*
     * See if any areas were obscured.  If so, mark them for redisplay.
     * Must do this here rather than in the background event handler,
     * because intervening events could shift the exposed areas before
     * the background event handler could see the events.
     */

    while (1) {
	XEvent event;

	XWindowEvent(mxwPtr->display, mxwPtr->fileWindow, ExposureMask,
		&event);
	if (event.type == NoExpose) {
	    break;
	} else if (event.type == Expose) {

	    /*
	     * This is an expose event; it must already have been queued
	     * up before the CopyArea was issued.  Rather than trying to
	     * figure out whether this area overlapped the area that was
	     * shifted, just redisplay it twice:  once assuming it wasn't
	     * in the shifted area, once assuming it was.
	     */

	    MxRedisplayRegion(mxwPtr, event.xexpose.x, event.xexpose.y,
		    event.xexpose.width, event.xexpose.height);
	    MxRedisplayRegion(mxwPtr, event.xexpose.x,
		    event.xexpose.y + dstY - srcY,
		    event.xexpose.width, event.xexpose.height);
	} else if (event.type == GraphicsExpose) {
	    MxRedisplayRegion(mxwPtr, event.xgraphicsexpose.x,
		    event.xgraphicsexpose.y, event.xgraphicsexpose.width,
		    event.xgraphicsexpose.height);
	    if (event.xgraphicsexpose.count == 0) {
		break;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FindTopAndBot --
 *
 *	Given that a particular position in the file should appear
 *	at a particular place in the view, this procedure computes
 *	the positions of the characters that should be displayed at
 *	the top-left and bottom-left corners of the window.
 *
 * Results:
 *	*TopPtr is filled in with the position of the character that
 *	should be displayed at the left end of the top line in the
 *	window, in order for pos to appear on line index.  *BotPtr
 *	is filled in with the position of the character that should be
 *	displayed at the left end of the bottom line in the window.
 *	The positioning will be adjusted if necessary to guarantee that
 *	the top line of the view always contains part of the file.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
FindTopAndBot(mxwPtr, pos, index, topPtr, botPtr)
    register MxWindow *mxwPtr;	/* Information describing window. */
    Mx_Position pos;		/* Location in file. */
    int index;			/* Where pos should be displayed on screen.
				 * Must be between 0 and
				 * mxwPtr->heightLines-1. */
    Mx_Position *topPtr;	/* Filled in with location of leftmost
				 * character on top line of window. Ignored
				 * if NULL. */
    Mx_Position *botPtr;	/* Filled in with location of leftmost
				 * character on bottom line of window.
				 * Ignored if NULL. */
{
    char *line, *line2;
    Mx_Position first, tmp;
    int firstIndex;
    
    /*
     * First of all, back up both the position and the display line to
     * the beginning of a file line.
     */

    if (pos.charIndex != 0) {
	tmp.lineIndex = pos.lineIndex;
	tmp.charIndex = 0;
	index -= CountLines(mxwPtr, tmp, pos) - 1;
	pos.charIndex = 0;
    }

    /*
     * Next, advance backwards until getting to the first character of
     * the file that occupies display line 0 (this character could be
     * off the top of the screen).  At the same time, make sure that
     * the topmost line is actually a line in the file.
     */

    first = pos;
    firstIndex = index;
    while (firstIndex > 0) {
	tmp = first;
	first.lineIndex -= 1;
	firstIndex -= CountLines(mxwPtr, first, tmp) - 1;
    }
    
    if (first.lineIndex < 0) {
	first = pos = Mx_ZeroPosition;
	firstIndex = index = 0;
    } else {
	Mx_Position eof;

	eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
	if (MX_POS_LESS(eof, first)) {
	    first = pos = eof;
	    firstIndex = index = 0;
	}
    }
	
    /*
     * Now advance forwards (if necessary) until finding the first
     * character on display line 0.
     */

    if (firstIndex < 0) {
	line = line2 = Mx_GetLine(mxwPtr->fileInfoPtr->file, first.lineIndex,
		(int *) NULL);
	while (firstIndex < 0) {
	    line = MeasureLength(mxwPtr, line, 0, mxwPtr->width,
		    (int *) NULL);
	    first.charIndex = line - line2;
	    firstIndex++;
	}
    }

    /*
     * Lastly, step down from pos to the end of the window, in order to
     * find out what's in the first character of the last line.
     */

    while (index < (mxwPtr->heightLines - 1)) {
	line = Mx_GetLine(mxwPtr->fileInfoPtr->file, pos.lineIndex,
		(int *) NULL);
	if (line == NULL) {
	    line = "\n";
	} else {
	    line += pos.charIndex;
	}
	line2 = MeasureLength(mxwPtr, line, 0, mxwPtr->width,
		(int *) NULL);
	if (*line2 == '\n') {
	    pos.charIndex = 0;
	    pos.lineIndex++;
	} else {
	    pos.charIndex += line2 - line;
	}
	index++;
    }
    if (topPtr != NULL) {
	*topPtr = first;
    }
    if (botPtr != NULL) {
	*botPtr = pos;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SetupView --
 *
 *	Fill in the MxDisplayLine information in a range of display
 *	lines, and mark all of the lines for later redisplay.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	NumLines display lines, starting at first, are modified to hold
 *	correct displaying information so that position is the first.
 *
 *----------------------------------------------------------------------
 */

static void
SetupView(mxwPtr, position, first, numLines)
    register MxWindow *mxwPtr;	/* Window to be adjusted. */
    Mx_Position position;	/* Character in mxwPtr's file. */
    int first;			/* Index of display line on which position
				 * should be displayed in character position
				 * 0. */
    int numLines;		/* Number of display lines whose display
				 * information should be updated. */
{
    char *line, *lastChar;
    register MxDisplayLine *linePtr;
    int length;

    mxwPtr->flags |= NEEDS_UPDATE;
    for (linePtr = &mxwPtr->linePtr[first]; numLines > 0;
	    numLines--, linePtr++) {
	line = Mx_GetLine(mxwPtr->fileInfoPtr->file, position.lineIndex,
		&length);
	if (line == NULL) {
	    line = "\n";
	    length = 1;
	}
	lastChar = MeasureLength(mxwPtr, &line[position.charIndex],
		0, mxwPtr->width, (int *) NULL);
	if (*lastChar == '\n') {
	    lastChar++;
	}
	linePtr->position = position;
	linePtr->redisplayFirst = position.charIndex;
	linePtr->length = lastChar - &line[position.charIndex];
	linePtr->redisplayLast = linePtr->redisplayFirst + linePtr->length - 1;
	position.charIndex += linePtr->length;
	if (position.charIndex >= length) {
	    position.lineIndex++;
	    position.charIndex = 0;
	}
    }
}
