/* 
 * mxHighlight.c --
 *
 *	This file provides procedures for displaying highlighted
 *	areas in files, and also for displaying an insertion caret.
 *
 * 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/mxHighlight.c,v 1.9 89/10/16 17:28:16 shirriff Exp $ SPRITE (Berkeley)";
#endif not lint


#include <X11/Xlib.h>
#include <ctype.h>
#include <stdlib.h>

#include "mxInt.h"

extern void panic();

/*
 * Information used to generate gray pixmaps for MX_GRAY highlights:
 */

#include "grayHl.bits"

/*
 * Information used to display the caret:
 */

#include "caret.bits"
#include "caretMask.bits"
Pixmap caret;
Pixmap caretMask;

/*
 *----------------------------------------------------------------------
 *
 * MxHighlightCreate --
 *
 *	Arrange for a particular range of text to be highlighted.
 *
 * Results:
 *	The return value is a token that can be used to refer to
 *	the highlight in later calls to procedures like MxHighlightDelete.
 *
 * Side effects:
 *	After this call, the given range of characters in the file will
 *	displayed in highlighted fashion according to style.  Changes
 *	to file will automatically be reflected so that the highlight
 *	will remain over the same characters.  If all the characters in
 *	the highlighted range are deleted then the highlight will cease.
 *
 *----------------------------------------------------------------------
 */

MxHighlight *
MxHighlightCreate(mxwPtr, first, last, style)
    register MxWindow *mxwPtr;	/* Information about window in whose file
				 * the highlight is to be created.  The
				 * highlight will be displayed in ALL
				 * views on the file. */
    Mx_Position first;		/* First character to be highlighted. */
    Mx_Position last;		/* Last character to be highlighted. */
    int style;			/* How to highlight range:  must be one
				 * of MX_REVERSE, MX_GRAY, MX_UNDERLINE,
				 * or MX_BOX. */
{
    register MxHighlight *hlPtr;

    hlPtr = (MxHighlight *) malloc(sizeof(MxHighlight));
    hlPtr->fileInfoPtr = mxwPtr->fileInfoPtr;
    hlPtr->first = first;
    hlPtr->last = last;
    hlPtr->floater = Mx_FloaterCreate(mxwPtr->fileInfoPtr->file,
	    &hlPtr->first, &hlPtr->last);
    hlPtr->style = style;
    hlPtr->nextPtr = mxwPtr->fileInfoPtr->hlPtr;
    mxwPtr->fileInfoPtr->hlPtr = hlPtr;

    /*
     * Display the highlight.
     */
    
    for (mxwPtr = mxwPtr->fileInfoPtr->mxwPtr; mxwPtr != NULL;
	    mxwPtr = mxwPtr->nextPtr) {
	MxRedisplayRange(mxwPtr, first, last);
    }

    return hlPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * MxHighlightDelete --
 *
 *	Stop highlighting a range of characters.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All resources associated with the highlight are deleted, and
 *	the characters in the range of the highlight are displayed
 *	normally again.
 *
 *----------------------------------------------------------------------
 */

void
MxHighlightDelete(hlPtr)
    MxHighlight *hlPtr;		/* Token for highlight to be deleted. */
{
    register MxHighlight *hl2Ptr;
    register MxFileInfo *fileInfoPtr;
    register MxWindow *mxwPtr;

    /*
     * Unlink the highlight from the list for its file.
     */

    fileInfoPtr = hlPtr->fileInfoPtr;
    if (fileInfoPtr->hlPtr == hlPtr) {
	fileInfoPtr->hlPtr = hlPtr->nextPtr;
    } else {
	for (hl2Ptr = fileInfoPtr->hlPtr; hl2Ptr->nextPtr != hlPtr;
		hl2Ptr = hl2Ptr->nextPtr) {
	    /* Null loop body. */
	}
	hl2Ptr->nextPtr = hlPtr->nextPtr;
    }

    /*
     * Redisplay the area of the highlight to restore the screen.
     */

    for (mxwPtr = fileInfoPtr->mxwPtr; mxwPtr != NULL;
	    mxwPtr = mxwPtr->nextPtr) {
	MxRedisplayRange(mxwPtr, hlPtr->first, hlPtr->last);
    }

    /*
     * Trash the highlight.
     */
    
    Mx_FloaterDelete(hlPtr->floater);
    free((char *) hlPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * MxHighlightGetRange --
 *
 *	Return the current range of a highlight, which might have
 *	floated since the highlight was created.
 *
 * Results:
 *	*firstPtr and *lastPtr are set to contain the first and
 *	last locations being highlighted.  If the highlighted text
 *	has been deleted, the resulting value of *firstPtr will
 *	be less than that of *lastPtr.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
MxHighlightGetRange(hlPtr, firstPtr, lastPtr)
    MxHighlight *hlPtr;		/* Highlight whose range is desired. */
    Mx_Position *firstPtr;	/* Where to put position of first
				 * highlighted character. */
    Mx_Position *lastPtr;	/* Where to put position of last
				 * highlighted character. */
{
    *firstPtr = hlPtr->first;
    *lastPtr = hlPtr->last;
}

/*
 *----------------------------------------------------------------------
 *
 * MxHighlightSetRange --
 *
 *	Change the range of a highlight.  This procedure can usually
 *	do things more efficiently than would be possible if the
 *	highlight were deleted and then	recreated.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The range of highlight is changed to be first -> last,
 *	inclusive.
 *
 *----------------------------------------------------------------------
 */

void
MxHighlightSetRange(hlPtr, first, last)
    register MxHighlight *hlPtr;
    Mx_Position first;		/* New location of beginning of highlight. */
    Mx_Position last;		/* New location of end of highlight. */
{
    register MxWindow *mxwPtr;
    Mx_Position oldFirst, oldLast;

    oldFirst = hlPtr->first;
    oldLast = hlPtr->last;
    hlPtr->first = first;
    hlPtr->last = last;

    /*
     * The code below optimizes redisplay in the case where the new
     * range and the old range overlap.
     */
    
    for (mxwPtr = hlPtr->fileInfoPtr->mxwPtr; mxwPtr != NULL;
	    mxwPtr = mxwPtr->nextPtr) {
	if (MX_POS_LESS(oldLast, first) ||
		MX_POS_LESS(last, oldFirst)) {
	    MxRedisplayRange(mxwPtr, oldFirst, oldLast);
	    MxRedisplayRange(mxwPtr, first, last);
	} else {
	    if (MX_POS_LESS(first, oldFirst)) {
		MxRedisplayRange(mxwPtr, first, oldFirst);
	    } else if (MX_POS_LESS(oldFirst, first)) {
		MxRedisplayRange(mxwPtr, oldFirst, first);
	    }
	    if (MX_POS_LESS(last, oldLast)) {
		MxRedisplayRange(mxwPtr, last, oldLast);
	    } else if (MX_POS_LESS(oldLast, last)) {
		MxRedisplayRange(mxwPtr, oldLast, last);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DrawHighlight --
 *
 *	This local procedure does all the work of actually displaying
 *	a highlight.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The given string gets drawn at the given place in the given
 *	highlight style.
 *
 *----------------------------------------------------------------------
 */

void
DrawHighlight(mxwPtr, string, numChars, x, y, style, first, last)
    register MxWindow *mxwPtr;		/* Window in which to redisplay
					 * highlights. */
    char *string;			/* Characters to display in
					 * highlighted fashion. */
    int numChars;			/* Number of characters to display. */
    int x, y;				/* Coordinates in window of UL corner
					 * of first character in string. */
    int style;				/* How to display highlight:
					 * MX_REVERSE etc. */
    int first;				/* Non-zero means the first character
					 * of string is the first character
					 * of the highlight. */
    int last;				/* Non-zero means the last character
					 * of string is the last character
					 * of the highlight. */
{
    int height, width, i;
    char control[2];

    height = mxwPtr->fontHeight;
    width = MxMeasureChars(mxwPtr, string, numChars, x);

    /*
     * Replace control characters by ASCII strings.
     */

    if (iscntrl(*string) && (*string != '\n')
	    && (*string != '\t')) {
	control[0] = '^';
	if (*string == 0177) {
	    control[1] = '?';
	} else {
	    control[1] = *string + 64;
	}
	string = control;
	numChars = 2;
    }

    /*
     * Redisplay the highlight.
     */

    switch (style) {

	/*
	 * Reverse video.  Just draw the characters black on white,
	 * with solid black for empty spaces.
	 */

	case MX_REVERSE: {
	    if (mxwPtr->reverseGc == None) {
		XGCValues gcValues;

		gcValues.foreground = mxwPtr->background;
		gcValues.background = mxwPtr->foreground;
		gcValues.font = mxwPtr->fontPtr->fid;
		mxwPtr->reverseGc = XCreateGC(mxwPtr->display,
			mxwPtr->fileWindow,
			GCForeground|GCBackground|GCFont, &gcValues);
	    }
	    if ((*string == '\n') || (*string == '\t')) {
		XFillRectangle(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->textGc, x, y, width, height);
	    } else {
		XDrawImageString(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->reverseGc, x,
			y + mxwPtr->fontPtr->ascent, string, numChars);
	    }
	    break;
	}
	
	/*
	 * Gray:  stipple over the existing text to make it gray.
	 */
	
	case MX_GRAY: {
	    if (mxwPtr->grayGc == None) {
		XGCValues gcValues;

		gcValues.stipple = XCreateBitmapFromData(mxwPtr->display,
			DefaultRootWindow(mxwPtr->display), grayHl_bits,
			grayHl_width, grayHl_height);
		if (gcValues.stipple == 0) {
		    Sx_Panic(mxwPtr->display,
			    "MxHighlightCreate couldn't create gray pixmap.");
		}
		gcValues.foreground = mxwPtr->foreground;
		gcValues.background = mxwPtr->background;
		gcValues.fill_style = FillStippled;
		mxwPtr->grayGc = XCreateGC(mxwPtr->display,
			mxwPtr->fileWindow,
			GCForeground|GCBackground|GCStipple|GCFillStyle,
			&gcValues);
		XFreePixmap(mxwPtr->display, gcValues.stipple);
	    }
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow,
		    mxwPtr->grayGc, x, y, width, height);
	    break;
	}

	/*
	 * Underline.  Draw a line just below the baseline.
	 */

	case MX_UNDERLINE: {
	    int underlineY;

	    if (mxwPtr->fontPtr->descent < 3) {
		underlineY = y + mxwPtr->fontHeight - 2;
	    } else {
		underlineY = y + mxwPtr->fontPtr->ascent + 1;
	    }
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow,
		    mxwPtr->textGc, x, underlineY, width, 2);
	    break;
	}

	/*
	 * Box: draw a lines along the very top and bottom of the
	 * characters, plus vertical lines at each end of the
	 * range.
	 */
	
	case MX_BOX: {
	    XDrawLine(mxwPtr->display, mxwPtr->fileWindow,
		    mxwPtr->textGc, x, y, x+width-1, y);
	    i = y + mxwPtr->fontHeight - 1;
	    XDrawLine(mxwPtr->display, mxwPtr->fileWindow,
		    mxwPtr->textGc, x, i, x+width-1, i);
	    if (first) {
		XDrawLine(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->textGc, x, y, x, i);
	    }
	    if (last) {
		XDrawLine(mxwPtr->display, mxwPtr->fileWindow,
			mxwPtr->textGc, x+width-1, y, x+width-1, i);
	    }
	    break;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxDisplayHighlights --
 *
 *	This procedure is passed a chunk of characters that are currently
 *	being redisplayed.  If the chunk (or any part of it) is part of
 *	a highlight, the chunk is redrawn in the style for the highlight.
 *	The string passed in has the following properties:
 *	    a) it's all on a single display line.
 *	    b) if the string contains any control characters, then there's
 *	       only one character in the string.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Highlights get drawn on the display, if there are any in
 *	the range given by position and numChars.
 *
 *----------------------------------------------------------------------
 */

void
MxDisplayHighlights(mxwPtr, position0, numChars0, x0, y0, string0)
    register MxWindow *mxwPtr;		/* Window in which to redisplay
					 * highlights. */
    Mx_Position position0;		/* File position of first character
    					 * of string. */
    int numChars0;			/* How many characters are being
					 * redisplayed in this chunk.   Each
					 * chunk is guaranteed to fit on one
					 * display line. */
    int x0, y0;				/* Coordinates in window of UL corner
					 * of first char being displayed. */
    char *string0;			/* Character(s) being displayed. */
{
    register MxHighlight *hlPtr;
    int i;

    for (hlPtr = mxwPtr->fileInfoPtr->hlPtr; hlPtr != NULL;
	    hlPtr = hlPtr->nextPtr) {
	Mx_Position position;		/* Local copies of parameters, */
	int numChars, x, y;		/* modified in each iteration. */
	register char *string;

	/*
	 * Figure out if any part of this highlight is in the range
	 * to display.  If so, figure out which part.
	 */

	if ((hlPtr->last.lineIndex < position0.lineIndex)
		|| (hlPtr->first.lineIndex > position0.lineIndex)) {
	    continue;
	}

	position = position0;
	numChars = numChars0;
	x = x0;
	y = y0;
	string = string0;
	if (hlPtr->first.lineIndex == position.lineIndex) {
	    i = hlPtr->first.charIndex - position.charIndex;
	    if (i > 0) {
		position.charIndex += i;
		x += MxMeasureChars(mxwPtr, string, i, x);
		numChars -= i;
		string += i;
	    }
	}
	if (hlPtr->last.lineIndex == position.lineIndex) {
	    i = (position.charIndex + numChars - 1) - hlPtr->last.charIndex;
	    if (i > 0) {
		numChars -= i;
	    }
	}
	if (numChars > 0) {
	    int first, last;

	    first = last = 0;
	    if (MX_POS_EQUAL(position, hlPtr->first)) {
		first = 1;
	    }
	    position.charIndex += numChars - 1;
	    if (MX_POS_EQUAL(position, hlPtr->last)) {
		last = 1;
	    }
	    DrawHighlight(mxwPtr, string,  numChars, x, y, hlPtr->style,
		    first, last);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxCaretSetPosition --
 *
 *	Change the display position of the caret in a window.  If the
 *	caret is actually on in the window, redisplay the old and new
 *	positions to update the screen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The caret is redrawn, unless its position isn't changing or
 *	it isn't supposed to be on at all.
 *
 *----------------------------------------------------------------------
 */

void
MxCaretSetPosition(mxwPtr, position, right)
    register MxWindow *mxwPtr;	/* Info for window in which caret is
				 * to be displayed. */
    Mx_Position position;	/* Where to display caret in file of mxwPtr. */
    int right;			/* 1 means display caret to right of
				 * position, 0 means to left. */
{
    register MxFileInfo *fileInfoPtr = mxwPtr->fileInfoPtr;

    if (right) {
	position = Mx_Offset(fileInfoPtr->file, position, 1);
    }

    if (MX_POS_EQUAL(position, fileInfoPtr->caretFirst)) {
	return;
    }

    MxCaretRedisplayText(mxwPtr->fileInfoPtr);

    /*
     * When moving the caret from one line to another, clean up the
     * indentation on the line that the caret is leaving.
     */

    if (fileInfoPtr->caretFirst.lineIndex != position.lineIndex) {
	MxCleanIndent(fileInfoPtr->file, fileInfoPtr->caretFirst.lineIndex);
    }

    fileInfoPtr->caretFirst = position;
    fileInfoPtr->caretLast = fileInfoPtr->caretFirst;
    if (mxwPtr->flags & FOCUS_WINDOW) {
	mxwPtr->flags |= NEEDS_UPDATE;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxCaretRedisplayText --
 *
 *	If there's a caret in any window on this file, arrange for the
 *	text under the caret to be redisplayed.  This procedure is
 *	typically invoked when the caret is about to change position.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text around the caret will be redisplayed.
 *
 *----------------------------------------------------------------------
 */

void
MxCaretRedisplayText(fileInfoPtr)
    register MxFileInfo *fileInfoPtr;
{
    register MxWindow *mxwPtr;
    int x, y;

    mxwPtr = fileInfoPtr->caretWindow;
    if (mxwPtr == NULL) {
	return;
    }
    fileInfoPtr->caretWindow = NULL;
    if (MxCharToPoint(mxwPtr, mxwPtr->fileInfoPtr->caretFirst, &x, &y) != 0) {
	return;
    }

    y += mxwPtr->fontPtr->ascent - caret_y_hot;
    x -= caret_x_hot;
    MxRedisplayRegion(mxwPtr, x, y, caret_width, caret_height);
}

/*
 *----------------------------------------------------------------------
 *
 * MxDisplayCaret --
 *
 *	This procedure redraws the caret in the given window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The caret gets redrawn.
 *
 *----------------------------------------------------------------------
 */

void
MxDisplayCaret(mxwPtr)
    register MxWindow *mxwPtr;		/* Information about caret, and
					 * window in which to draw it. */
{
    int x, y;
    XGCValues gcValues;

    mxwPtr->fileInfoPtr->caretWindow = mxwPtr;
    if (MxCharToPoint(mxwPtr, mxwPtr->fileInfoPtr->caretFirst, &x, &y) != 0) {
	return;
    }

    if (mxwPtr->cursorMode == CARET || (mxwPtr->cursorMode == VIBLOCK &&
	    mxwPtr->fileInfoPtr->viLines <= 0)) {

	if (caret == 0) {
	    caret = XCreateBitmapFromData(mxwPtr->display,
		    DefaultRootWindow(mxwPtr->display), caret_bits,
		    caret_width, caret_height);
	    caretMask = XCreateBitmapFromData(mxwPtr->display,
		    DefaultRootWindow(mxwPtr->display), caretMask_bits,
		    caretMask_width, caretMask_height);
	    if ((caret == None) || (caretMask == None)) {
		Sx_Panic(mxwPtr->display,
			"MxDisplayCaret couldn't create caret bitmaps.");
	    }
	}

	y +=  mxwPtr->fontPtr->ascent - caret_y_hot;
	x -= caret_x_hot;
	/*
	 * White out a mask area, then blacken the caret area.
	 */
#define bugA 1
#ifdef bugA
	    gcValues.foreground = mxwPtr->background;
	    gcValues.foreground = mxwPtr->background;
	    gcValues.clip_x_origin = x;
	    gcValues.clip_y_origin = y;
	    gcValues.clip_mask = caretMask;
	    XChangeGC(mxwPtr->display, mxwPtr->textGc, GCForeground|
		    GCClipXOrigin |GCClipYOrigin|GCClipMask, &gcValues);
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->textGc,
		    x, y, caret_width, caret_height);
	    gcValues.foreground = mxwPtr->foreground;
	    gcValues.clip_mask= caret;
	    XChangeGC(mxwPtr->display, mxwPtr->textGc, GCForeground|GCClipMask,
		    &gcValues);
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->textGc,
		    x, y, caret_width, caret_height);
	    XSetClipMask(mxwPtr->display, mxwPtr->textGc, None);
#endif

	    /*
	     * Alternate way of doing above, which doesn't seem to work on some
	     * X servers.
	     */

#ifdef bugB
	    gcValues.ts_x_origin = x;
	    gcValues.ts_y_origin = y;
	    gcValues.stipple = caretMask;
	    gcValues.fill_style = FillStippled;
	    XChangeGC(mxwPtr->display, mxwPtr->textGc, GCForeground|
		    GCTileStipXOrigin |GCTileStipYOrigin|GCStipple|GCFillStyle,
		    &gcValues);
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->textGc,
		    x, y, caret_width, caret_height);
	    gcValues.foreground = mxwPtr->foreground;
	    gcValues.stipple = caret;
	    XChangeGC(mxwPtr->display, mxwPtr->textGc, GCForeground|GCStipple,
		    &gcValues);
	    XFillRectangle(mxwPtr->display, mxwPtr->fileWindow, mxwPtr->textGc,
		    x, y, caret_width, caret_height);
	    XSetFillStyle(mxwPtr->display, mxwPtr->textGc, FillSolid);
#endif
    } else if (mxwPtr->cursorMode == BLOCK || mxwPtr->cursorMode == VIBLOCK) {
	char *p;

	/*
	 * Box style cursor:  display just like reverse-video highlight,
	 * except for tabs and newlines only display a one-character wide
	 * swatch of reverse-video space.
	 */

	p = Mx_GetLine(mxwPtr->fileInfoPtr->file,
		mxwPtr->fileInfoPtr->caretFirst.lineIndex, (int *) NULL);
	p += mxwPtr->fileInfoPtr->caretFirst.charIndex;
	if ((*p == '\t') || (*p == '\n')) {
	    p = " ";
	}
	DrawHighlight(mxwPtr, p, 1, x, y, MX_REVERSE,1,1);
    } else if (mxwPtr->cursorMode != OFF) {
	panic("Invalid cursor mode\n");
    }
}
