/* 
 * mxWindow.c --
 *
 *	This file provides the top-level procedures for creating
 *	 and deleting windows on files.  These are all routines that
 *	apply ONLY to Mx, and not to Tx.
 *
 * Copyright (C) 1986, 1987, 1988 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/mxWindow.c,v 1.36 90/04/19 09:24:36 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Intrinsic.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mxInt.h"

/*
 * Library imports:
 */

char *getenv();

/*
 * The context below maps from X window ids to MxWindow structures:
 */

static XContext mxContext;
static int init = 0;

/*
 * Default cursor to use in Mx windows (change the names of these
 * things when running lint to avoid stupid complaints):
 */

#ifdef lint
#define cursor_bits mx_bits
#define cursMask_bits mxMask_bits
#endif

#include "cursor.bits"
#include "cursMask.bits"
static Cursor cursor;

/*
 * Head of list of all MxFileInfo structures:
 */

static MxFileInfo *fileListPtr = NULL;

/*
 * Total count of number of files open.  When this reaches zero, it
 * may be time to exit the program.
 */

int mx_FileCount = 0;

/*
 * Procedures defined in this file but referenced before defined:
 */

static void		DetachFileInfo();
static void		MxInit();
static int		SetupFileInfo();
static void		SetTitle();
static void		StructureProc();
static void		RecycleFileInfo();

/*
 * The information below is used to create the command table.
 */

typedef struct {
    char *name;			/* Command name. */
    int (*proc)();		/* Procedure to process command. */
} CmdInfo;

static CmdInfo commands[] = {
    {"bind",		Mx_BindCmd},
    {"caret",		Mx_CaretCmd},
    {"clean",		Mx_CleanCmd},
    {"column",		Mx_ColumnCmd},
    {"control",		Mx_ControlCmd},
    {"delete",		Mx_DeleteCmd},
    {"extract",		Mx_ExtractCmd},
    {"focus",		Mx_FocusCmd},
    {"geometry",	Mx_GeometryCmd},
    {"history",		Mx_HistoryCmd},
    {"indent",		Mx_IndentCmd},
    {"insert", 		Mx_InsertCmd},
    {"mark",		Mx_MarkCmd},
    {"menu",		Mx_MenuCmd},
    {"message",		Mx_MessageCmd},
    {"newline",		Mx_NewlineCmd},
    {"open",		Mx_OpenCmd},
    {"quit",		Mx_QuitCmd},
    {"quote",		Mx_QuoteCmd},
    {"read",		Mx_ReadCmd},
    {"replace",		Mx_ReplaceCmd},
    {"reset",		Mx_ResetCmd},
    {"search",		Mx_SearchCmd},
    {"see",		Mx_SeeCmd},
    {"selection",	Mx_SelectionCmd},
    {"send",		Mx_SendCmd},
    {"switch",		Mx_SwitchCmd},
    {"taginfo",		Mx_TaginfoCmd},
    {"undo", 		Mx_UndoCmd},
    {"update",		Mx_UpdateCmd},
    {"write",		Mx_WriteCmd},
    {(char *) NULL, (int (*)()) NULL}
};

/*
 * Range of characters for which the standard insert binding is to be used,
 * unless overridden by something else.
 */

static char insertFirst = 040;
static char insertLast = 0176;

/*
 * Additional flags to SetupFileInfo, used only within this module
 * (primarily when resetting the window):
 */

#define NO_NAME_MATCH		0x800
#define IGNORE_LOG_EXISTS	0x1000

/*
 *----------------------------------------------------------------------
 *
 * Mx_Make --
 *
 *	Given a window, this procedure creates a set of subwindows
 *	to allow a file to be edited in the window.
 *
 * Results:
 *	In the normal case where all goes well, TCL_OK is returned.  If
 *	an unusual situation occurred where the user requested that
 *	the window not be made after all, then TCL_ERROR is returned and
 *	an error message pointer is left in interp->result.
 *
 * Side effects:
 *	Data structures are allocated and subwindows are created.
 *	See documentation on infoPtr (in mx.h) for details of the
 *	various options available.
 *
 *----------------------------------------------------------------------
 */

int
Mx_Make(display, window, infoPtr, interp)
    Display *display;			/* Connection to X server. */
    Window window;			/* Window to use for edit display. */
    register Mx_WindowInfo *infoPtr;	/* Describes configuration for Mx
					 * window. */
    Tcl_Interp *interp;			/* Interpreter to use for error
					 * reporting. */
{
    register MxWindow *mxwPtr;
    int titleHeight, i, result;
    CmdInfo *cmd;
    Mx_Position eof;
    char msg[100], *statusString, string[200], *s;
    caddr_t data;
    XGCValues gcValues;

    /*
     * Initialze the module if it hasn't been done before.
     */

    if (!init) {
	MxInit(display);
    }

    /*
     * Create the MxWindow structure and register it in a hash table
     * of all such structures.  If there was already an MxWindow here,
     * flush its old contents (but a few things stay around:  see
     * DeleteProc).
     */

    if (XFindContext(display, window, mxContext, &data) == 0) {
	Sx_Panic(display, "Mx_Make: window already contains Mx information.");
    }
    mxwPtr = (MxWindow *) malloc(sizeof(MxWindow));

    if ((infoPtr->file == NULL) && (infoPtr->name == NULL)) {
	Sx_Panic(display, "Mx_Make: no open file or name given.");
    }

    /*
     * Create the MxFileInfo structure if there isn't one already.  Note:
     * must initialize the Tcl interpreter for the window immediately,
     * since it's used in procedures called by SetupFileInfo.
     */

    mxwPtr->interp = Tcl_CreateInterp();
    mxwPtr->display = display;
    mxwPtr->w = window;
    mxwPtr->fontPtr = infoPtr->fontPtr;
    if (SetupFileInfo(mxwPtr, infoPtr->name, infoPtr->file,
	    infoPtr->flags, interp) == TCL_ERROR) {
	free((char *) mxwPtr);
	return TCL_ERROR;
    }
    XSaveContext(display, window, mxContext, (caddr_t) mxwPtr);

    /*
     * Initialize information used for displaying:
     */

    mxwPtr->foreground = infoPtr->foreground;
    mxwPtr->background = infoPtr->background;
    mxwPtr->border = infoPtr->border;
    mxwPtr->titleForeground = infoPtr->titleForeground;
    mxwPtr->titleBackground = infoPtr->titleBackground;
    mxwPtr->titleStripe = infoPtr->titleStripe;
    mxwPtr->sbForeground = infoPtr->sbForeground;
    mxwPtr->sbBackground = infoPtr->sbBackground;
    mxwPtr->sbElevator = infoPtr->sbElevator;
    mxwPtr->cursor = cursor;
    gcValues.foreground = mxwPtr->foreground;
    gcValues.background = mxwPtr->background;
    gcValues.font = mxwPtr->fontPtr->fid;
    mxwPtr->textGc = XCreateGC(display, mxwPtr->w,
	    GCForeground|GCBackground|GCFont, &gcValues);
    mxwPtr->grayGc = None;
    mxwPtr->reverseGc = None;
    mxwPtr->iconPixmap = None;
    mxwPtr->cursorMode = CARET;

    /*
     * Initialize stuff related to command interpretation.
     */

    mxwPtr->cmdTable = Cmd_TableCreate();
    for (cmd = commands; cmd->name != NULL; cmd++) {
	Tcl_CreateCommand(mxwPtr->interp, cmd->name, cmd->proc,
		(ClientData) mxwPtr, (void (*)()) NULL);
    }
    for (i = insertFirst; i <= insertLast; i++) {
	char string[2];
	string[0] = i;
	string[1] = 0;
	Cmd_BindingCreate(mxwPtr->cmdTable, string, "!@");
    }
    Tcl_SetVar(mxwPtr->interp, "helpDir", MX_LIB_DIR, 1);

    mxwPtr->quoteFlag = 0;
    mxwPtr->cmdString = NULL;
    mxwPtr->searchString = NULL;
    mxwPtr->replaceString = NULL;
    mxwPtr->msgString = NULL;
    MxHistoryInit(mxwPtr);

    /*
     * Initialize subwindow information:
     */

    titleHeight = Sx_DefaultHeight(display, infoPtr->fontPtr);
    if (infoPtr->flags & MODIFIED) {
	statusString = "Modified";
    } else {
	statusString = NULL;
    }
    if (infoPtr->flags & MX_NO_TITLE) {
	mxwPtr->title = NULL;
    } else {
	mxwPtr->title = Sx_CreatePacked(display, mxwPtr->w, SX_TOP,
		titleHeight, 0, 2, mxwPtr->border, None,
		mxwPtr->background);
    }
    SetTitle(mxwPtr);
    mxwPtr->menuBar = Sx_CreatePacked(display, mxwPtr->w, SX_TOP, titleHeight,
	    0, 1, mxwPtr->border, None, mxwPtr->background);
    mxwPtr->msgWindow = Sx_CreatePacked(display, mxwPtr->w, SX_TOP,
	    titleHeight, 0, 1, mxwPtr->border, None,
	    mxwPtr->background);
    mxwPtr->searchWindow = NULL;
    mxwPtr->replaceWindow = NULL;
    mxwPtr->cmdWindow = NULL;
    mxwPtr->scrollbar = Sx_ScrollbarCreate(display, mxwPtr->w, SX_RIGHT, 1,
	    mxwPtr->sbForeground, mxwPtr->sbBackground,
	    mxwPtr->sbElevator, mxwPtr->border,
	    MxScroll, (ClientData) mxwPtr);
    mxwPtr->fileWindow = Sx_CreatePacked(display, mxwPtr->w, SX_TOP, 0,
	    1, 0, mxwPtr->border, None, mxwPtr->background);

    /*
     * Initialize other, random, information about window.
     */

    mxwPtr->flags = 0;
    mxwPtr->width = infoPtr->width - Sx_ScrollbarWidth() - 1;
    mxwPtr->height = infoPtr->height - 3*titleHeight - 3;
    if (mxwPtr->title != NULL) {
	mxwPtr->height -= titleHeight + 2;
    }
    MxSetupRedisplay(mxwPtr);
    XDefineCursor(display, mxwPtr->w, mxwPtr->cursor);
    XSetWindowBorder(display, mxwPtr->w, infoPtr->border);
    Sx_EnableFocus(display, mxwPtr->w, mxwPtr->fileWindow,
		MxFocusProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->fileWindow, KeyPressMask,
	    MxKeyProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->fileWindow, ButtonPressMask
	    |ButtonReleaseMask|Button1MotionMask|Button3MotionMask,
	    MxMouseProc, (ClientData) mxwPtr);
    (void) Sx_HandlerCreate(display, mxwPtr->w, StructureNotifyMask,
	    StructureProc, (ClientData) mxwPtr);
    Sx_Focus(display, mxwPtr->fileWindow);
    Tcl_SetVar(mxwPtr->interp, "file", mxwPtr->fileInfoPtr->name, 1);

    /*
     * Note:  MX_VERSION should be defined with a "-D" switch in the Makefile.
     */

    Tcl_SetVar(mxwPtr->interp, "version", MX_VERSION, 1);

    /*
     * Read the system's default .mx file, then read .mx files from
     * the home directory and the current directory, if they exist.
     */

    sprintf(string, "source %.150s/default.mx", MX_LIB_DIR);
    result = MxDoCmd(mxwPtr, string);
    if (result != TCL_OK) {
	char msg[300];

	sprintf(msg, "Error executing \"%.50s/default.mx\": %.200s",
		MX_LIB_DIR, mxwPtr->interp->result);
	(void) Sx_Notify(mxwPtr->display,  DefaultRootWindow(mxwPtr->display),
		-1, -1, 0, msg, mxwPtr->fontPtr, 1, "Quit", (char *) NULL);
	XDestroyWindow(mxwPtr->display, mxwPtr->w);
	mxwPtr->flags |= DESTROYED;
	Tcl_Return(interp, mxwPtr->interp->result, TCL_VOLATILE);
	return result;
    }
    s = getenv("HOME");
    if (s != NULL) {
	sprintf(string, "%.150s/.mx", s);
	if (access(string, R_OK) == 0) {
	    sprintf(string, "source %.150s/.mx", s);
	    result = MxDoCmd(mxwPtr, string);
	}
    }
    if (result == TCL_OK) {
	if (access(".mx", R_OK) == 0) {
	    struct stat homeStat, cwdStat;

	    /*
	     * Don't process the .mx file in the current directory if
	     * the current directory is the same as the home directory:
	     * it will already have been processed above.
	     */

	    (void) stat(s, &homeStat);
	    (void) stat(".", &cwdStat);
	    if (bcmp((char *) &homeStat, (char *) &cwdStat,
		    sizeof(cwdStat))) {
		result = MxDoCmd(mxwPtr, "source .mx");
	    }
	}
    }

    MxHistoryOn(mxwPtr);

    /*
     * Put up a message with information about the file, unless an
     * error occurred in one of the startup files (in that case,
     * don't do anything else, so the error will show in the message
     * window.
     */
     
    if (result != TCL_OK) {
	return TCL_OK;
    }
    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    statusString = "";
    if (access(mxwPtr->fileInfoPtr->name, W_OK) != 0) {
	if (errno == EACCES) {
	    statusString = " (read-only)";
	} else if (errno == ENOENT) {
	    statusString = " (new file)";
	}
    }
    sprintf(msg, "Mx version %s, editing \"%.50s\"%s: %d lines", MX_VERSION,
	    mxwPtr->fileInfoPtr->name, statusString, eof.lineIndex+1);
    MxOutputMsg(mxwPtr, msg);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_Update --
 *
 *	Update the screen.  If any changes have been made to any
 *	Mx window since the last time this procedure was called,
 *	they've probably not been displayed properly on the screen.
 *	This procedure corrects everything on the screen to reflect
 *	the current state of the world.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information on the screen gets redisplayed.
 *
 *----------------------------------------------------------------------
 */

void
Mx_Update()
{
    register MxFileInfo *infoPtr;
    register MxWindow *mxwPtr;
    int reTitle;

    for (infoPtr = fileListPtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
	/*
	 * Check to see if the file has just been modified for
	 * the first time, or if it has been undone in a way that
	 * totally reverses all modifications.  If so, update the
	 * title bar and icon title.
	 */

	reTitle = 0;
	if (Mx_FileModified(infoPtr->file)) {
	    if ((infoPtr->flags & MODIFIED) == 0) {
		reTitle = 1;
		infoPtr->flags |= MODIFIED;
	    }
	} else if (infoPtr->flags & MODIFIED) {
	    reTitle = 1;
	    infoPtr->flags &= ~MODIFIED;
	}

	for (mxwPtr = infoPtr->mxwPtr; mxwPtr != NULL;
		mxwPtr = mxwPtr->nextPtr) {
	    if (mxwPtr->flags & DESTROYED) {
		continue;
	    }
	    if (mxwPtr->flags & NEEDS_UPDATE) {
		MxUpdateWindow(mxwPtr);
		if (mxwPtr->flags & FOCUS_WINDOW) {
		    MxDisplayCaret(mxwPtr);
		}
	    }

	    if (reTitle) {
		SetTitle(mxwPtr);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Mx_Cleanup --
 *
 *	This routine may be called to perform "last-ditch" cleanup
 *	before the process dies.  Typically, it's invoked in response
 *	to fatal signals.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Undo logs are deleted if they correspond to clean.
 *
 *----------------------------------------------------------------------
 */

void
Mx_Cleanup()
{
    register MxFileInfo *fileInfoPtr;

    for (fileInfoPtr = fileListPtr; fileInfoPtr != NULL;
	    fileInfoPtr = fileInfoPtr->nextPtr) {
	if ((fileInfoPtr->file != NULL)
		&& !Mx_FileModified(fileInfoPtr->file)
		&& (fileInfoPtr->log != NULL)) {
	    Undo_LogDelete(fileInfoPtr->log);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxGetMxWindow --
 *
 *	Given an X window id, returns the address of the Mx data
 *	structure for the window.
 *
 * Results:
 *	The return value is the address of the MxWindow corresponding
 *	to window, or NULL if there's no existing MxWindow for window.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

MxWindow *
MxGetMxWindow(display, window)
    Display *display;			/* Connection to X server. */
    Window window;			/* X window id for Mx window. */
{
    caddr_t data;

    if (!init) {
	return NULL;
    }
    if (XFindContext(display, window, mxContext, &data) != 0) {
	return NULL;
    }
    return (MxWindow *) data;
}

/*
 *----------------------------------------------------------------------
 *
 * MxSwitchFile --
 *
 *	Change the file being edited in an Mx window.
 *
 * Results:
 *	TCL_OK is returned if everything went OK.  If something happened
 *	such that the user requested that the window contents not be
 *	switched after all, then TCL_ERROR is returned, a pointer to
 *	an error message is left in mxwPtr->interp->result, and the
 *	window is not changed.
 *
 * Side effects:
 *	The window is modified so that it displays the file named
 *	"name".  The caller should make sure that the old file was
 *	saved, is such is desired, since if this was the only window
 *	on the old file its modifications will be lost.
 *
 *----------------------------------------------------------------------
 */

int
MxSwitchFile(mxwPtr, name)
    register MxWindow *mxwPtr;	/* Window of interest. */
    char *name;			/* Name of new file to appear in window. */
{
    int undoable;
    Mx_Position eof;
    int result;
    char msg[100], *statusString;
    MxFileInfo *oldFileInfoPtr;

    if (mxwPtr->fileInfoPtr->log != NULL) {
	undoable = MX_UNDO;
    } else {
	undoable = 0;
    }

    /*
     * Save around the old file information, then attempt to set things
     * up for the new file.  If it fails, restore the old information.
     */

    oldFileInfoPtr = mxwPtr->fileInfoPtr;
    DetachFileInfo(mxwPtr);
    result = SetupFileInfo(mxwPtr, name, (Mx_File) NULL, undoable,
	    mxwPtr->interp);
    if (result == TCL_OK) {
	MxCleanupRedisplay(mxwPtr);
	RecycleFileInfo(oldFileInfoPtr, mxwPtr->display);
    } else {
	mxwPtr->nextPtr = oldFileInfoPtr->mxwPtr;
	oldFileInfoPtr->mxwPtr = mxwPtr;
	mxwPtr->fileInfoPtr = oldFileInfoPtr;
	return result;
    }
    MxSetupRedisplay(mxwPtr);
    SetTitle(mxwPtr);
    Tcl_SetVar(mxwPtr->interp, "file", name, 1);
    XStoreName(mxwPtr->display, mxwPtr->w, name);

    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    statusString = "";
    if (access(mxwPtr->fileInfoPtr->name, W_OK) != 0) {
	if (errno == EACCES) {
	    statusString = " (read-only)";
	} else if (errno == ENOENT) {
	    statusString = " (new file)";
	}
    }
    sprintf(msg, "Loaded \"%.50s\"%s: %d lines", 
	    mxwPtr->fileInfoPtr->name, statusString, eof.lineIndex+1);
    MxOutputMsg(mxwPtr, msg);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MxResetFile --
 *
 *	Throw away the current contents of a file, reload it from disk,
 *	and reset all windows using the file to use the current disk
 *	version.
 *
 * Results:
 *	TCL_OK is returned if everything went OK;  in this case a message
 *	about the file is returned in mxwPtr->interp->result.  If something
 *	happened such that the window contents could not be reset after
 *	all, then TCL_ERROR is returned, a pointer to an error message is
 *	left in mxwPtr->interp->result, and no windows are changed.
 *
 * Side effects:
 *	All windows on mxwPtr->fileInfoPtr are reset.
 *
 *----------------------------------------------------------------------
 */

int
MxResetFile(mxwPtr)
    MxWindow *mxwPtr;		/* Window on file to reset. */
{
    int undoable, result;
    Mx_File newFile;
    register MxWindow *mxwPtr2;
    MxFileInfo *oldFileInfoPtr, *newFileInfoPtr;
    Mx_Position eof;
    char *statusString;

    oldFileInfoPtr = mxwPtr->fileInfoPtr;
    if (oldFileInfoPtr->log != NULL) {
	undoable = MX_UNDO;
    } else {
	undoable = 0;
    }

    /*
     * Read in the disk version of the file.
     */

    newFile = Mx_FileLoad(oldFileInfoPtr->name);
    if (newFile == NULL) {
	if (errno != ENOENT) {
	    char msg[200];
	    int option, errNum;

	    errNum = errno;
	    sprintf(msg,
		    "Mx couldn't read\n\"%.50s\":\n%.100s.",
		    oldFileInfoPtr->name, strerror(errNum));
	    option = Sx_Notify(mxwPtr->display,
		    DefaultRootWindow(mxwPtr->display), -1, -1, 0,
		    msg, mxwPtr->fontPtr, 1, "Make a new file",
		    "Quit", NULL);
	    if (option == 1) {
		sprintf(mxwPtr->interp->result,
			"couldn't read \"%.50s\": %.100s",
			oldFileInfoPtr->name, strerror(errNum));
		return TCL_ERROR;
	    }
	}
	newFile = Mx_FileLoad((char *) NULL);
    }

    /*
     * Attempt to set things up for the new file.  If an error occurs,
     * then restore all of the windows on the file to their previous
     * contents.
     */

    newFileInfoPtr = NULL;
    for (mxwPtr2 = oldFileInfoPtr->mxwPtr; mxwPtr2 != NULL; ) {
	MxWindow *nextPtr = mxwPtr2->nextPtr;

	DetachFileInfo(mxwPtr2);
	result = SetupFileInfo(mxwPtr2, oldFileInfoPtr->name, newFile,
		undoable|NO_NAME_MATCH|IGNORE_LOG_EXISTS, mxwPtr->interp);
	if (result != TCL_OK) {
	    mxwPtr2->nextPtr = oldFileInfoPtr->mxwPtr;
	    oldFileInfoPtr->mxwPtr = mxwPtr2;
	    mxwPtr2->fileInfoPtr = oldFileInfoPtr;
	    if (newFileInfoPtr != NULL) {
		for (mxwPtr2 = newFileInfoPtr->mxwPtr; mxwPtr2 != NULL; ) {
		    nextPtr = mxwPtr2->nextPtr;
		    DetachFileInfo(mxwPtr2);
		    mxwPtr2->nextPtr = oldFileInfoPtr->mxwPtr;
		    oldFileInfoPtr->mxwPtr = mxwPtr2;
		    mxwPtr2->fileInfoPtr = oldFileInfoPtr;
		    mxwPtr2 = nextPtr;
		}
		RecycleFileInfo(newFileInfoPtr, mxwPtr->display);
	    }
	    return result;
	}
	newFileInfoPtr = mxwPtr2->fileInfoPtr;
	mxwPtr2 = nextPtr;
    }

    /*
     * Everything was OK, so make a second pass through the windows
     * to reset their display information.
     */

    for (mxwPtr2 = newFileInfoPtr->mxwPtr; mxwPtr2 != NULL;
	    mxwPtr2 = mxwPtr2->nextPtr) {
	MxCleanupRedisplay(mxwPtr2);
	MxSetupRedisplay(mxwPtr2);
	SetTitle(mxwPtr2);
    }
    RecycleFileInfo(oldFileInfoPtr, mxwPtr->display);
    eof = Mx_EndOfFile(mxwPtr->fileInfoPtr->file);
    statusString = "";
    if (access(mxwPtr->fileInfoPtr->name, W_OK) != 0) {
	if (errno == EACCES) {
	    statusString = " (read-only)";
	} else if (errno == ENOENT) {
	    statusString = " (new file)";
	}
    }
    sprintf(mxwPtr->interp->result, "re-loaded \"%.50s\"%s: %d lines", 
	    mxwPtr->fileInfoPtr->name, statusString, eof.lineIndex+1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SetupFileInfo --
 *
 *	This utility procedure establishes the MxFileInfo structure
 *	for a window.
 *
 * Results:
 *	TCL_OK is returned if everything went OK.  If for some reason
 *	the user requested that the new window not be opened after
 *	all, then TCL_ERROR is returned, a pointer to an error message
 *	is left in interp->result, and the fileInfoPtr field of mxwPtr is
 *	left NULL.
 *
 * Side effects:
 *	A MxFileInfo structure gets allocated for mxwPtr, if one
 *	doesn't already exist, and mxwPtr gets linked into its list
 *	of windows.
 *
 *----------------------------------------------------------------------
 */

static int
SetupFileInfo(mxwPtr, name, file, flags, interp)
    register MxWindow *mxwPtr;	/* Window for which file-related information
				 * is to be set up.   Its fontPtr and w
				 * fields must be valid. */
    char *name;			/* Name of file to be associated with mxwPtr.
				 * If caller has already opened the file,
				 * so we're not supposed to muck with it,
				 * then this is NULL. */
    Mx_File file;		/* If the caller has already opened the
				 * file, this points to it.  Otherwise,
				 * this is NULL. */
    int flags;			/* Some OR'ed combination of the flag bits
				 * MX_UNDO, MX_DELETE, NO_NAME_MATCH,
				 * and IGNORE_LOG_EXISTS. */
    Tcl_Interp *interp;		/* Interpreter to use for error reporting. */
{
    register MxFileInfo *fileInfoPtr;
    int option, result;

    /*
     * Handle tilde's in name.
     */

    if (name != NULL) {
	name = Tcl_TildeSubst(interp, name);
	if (name == NULL) {
	    mxwPtr->fileInfoPtr = NULL;
	    return TCL_ERROR;
	}
    }

    /*
     * Create an MxFileInfo structure if there isn't one already.
     */

    for (fileInfoPtr = fileListPtr; fileInfoPtr != NULL;
	    fileInfoPtr = fileInfoPtr->nextPtr) {
	if ((fileInfoPtr->file == file)
		|| ((file == NULL) && (name != NULL)
		&& !(flags & NO_NAME_MATCH)
		&& (strcmp(name, fileInfoPtr->name) == 0))) {
	    file = fileInfoPtr->file;
	    break;
	}
    }
    if (fileInfoPtr == NULL) {
	fileInfoPtr = (MxFileInfo *) malloc(sizeof(MxFileInfo));
	if (file == NULL) {
	    fileInfoPtr->file = file = Mx_FileLoad(name);
	    if (fileInfoPtr->file == NULL) {
		if (errno != ENOENT) {
		    char msg[200];
		    int errNum;

		    errNum = errno;
		    sprintf(msg,
			    "Mx couldn't read\n\"%.50s\":\n%.100s.", name,
			    strerror(errNum));
		    option = Sx_Notify(mxwPtr->display,
			    DefaultRootWindow(mxwPtr->display), -1, -1, 0,
			    msg, mxwPtr->fontPtr, 1, "Make a new file",
			    "Quit", NULL);
		    if (option == 1) {
			free((char *) fileInfoPtr);
			mxwPtr->fileInfoPtr = NULL;
			sprintf(interp->result,
				"couldn't read \"%.50s\": %.100s",
				name, strerror(errNum));
			return TCL_ERROR;
		    }
		}
		fileInfoPtr->file = Mx_FileLoad((char *) NULL);
	    }
	} else {
	    fileInfoPtr->file = file;
	}
	if (name == NULL) {
	    name = "";
	}
	fileInfoPtr->name = (char *)
		malloc((unsigned) (strlen(name) + 1));
	strcpy(fileInfoPtr->name, name);
	fileInfoPtr->lastMod = MxLastMod(name);
	fileInfoPtr->log = NULL;
	fileInfoPtr->hlPtr = NULL;
	fileInfoPtr->mxwPtr = NULL;
	fileInfoPtr->nextPtr = fileListPtr;
	fileInfoPtr->flags = 0;
	if (flags & MX_DELETE) {
	    fileInfoPtr->flags |= DELETE;
	}
	fileInfoPtr->caretFirst = Mx_ZeroPosition;
	fileInfoPtr->caretLast = Mx_ZeroPosition;
	fileInfoPtr->caretWindow = NULL;
	(void) Mx_FloaterCreate(fileInfoPtr->file,
	    &fileInfoPtr->caretFirst, &fileInfoPtr->caretLast);
	(void) Mx_SpyCreate(fileInfoPtr->file, MX_BEFORE,
		MxCaretRedisplayText, (ClientData) fileInfoPtr);
	fileListPtr = fileInfoPtr;
	fileInfoPtr->openParenPtr = NULL;
	fileInfoPtr->closeParenPtr = NULL;
	mx_FileCount++;
    }
    mxwPtr->nextPtr = fileInfoPtr->mxwPtr;
    fileInfoPtr->mxwPtr = mxwPtr;
    mxwPtr->fileInfoPtr = fileInfoPtr;

    /*
     * Create an undo log if one is requested and there isn't
     * already one for the file.
     */

    if ((fileInfoPtr->log == NULL) && (flags & MX_UNDO)
	    && (name != NULL)) {
	char oldLogName[UNDO_NAME_LENGTH], logName[UNDO_NAME_LENGTH];
	char idLine[UNDO_ID_LENGTH];

	result = Undo_FindLog(name, logName, idLine, oldLogName, interp);
	if (result != TCL_OK) {
	    char msg[200];
	    sprintf(msg, "%s %s %s", interp->result,
		    "  I can continue, but",
		    " without undoing or crash recovery.");
	    option = Sx_Notify(mxwPtr->display,
		    DefaultRootWindow(mxwPtr->display), -1, -1, 0, msg,
		    mxwPtr->fontPtr, 1, "Continue Anyway", "Quit", NULL);
	    Tcl_Return(interp, (char *) NULL, TCL_STATIC);
	    if (option == 1) {
		goto abort;
	    }
	    oldLogName[0] = 0;
	} else {
	    fileInfoPtr->log = Undo_LogCreate(fileInfoPtr, logName,
		    idLine);
	    if (fileInfoPtr->log == NULL) {
		static char msg[100];
		unlink(logName);
		sprintf(msg, "Couldn't set up the undo log.  %s %s",
			"I can continue, but without undoing ",
			"or crash recovery.");
		option = Sx_Notify(mxwPtr->display,
			DefaultRootWindow(mxwPtr->display), -1, -1, 0, msg,
			mxwPtr->fontPtr, 1, "Continue Anyway", "Quit",
			NULL);
		if (option == 1) {
		    goto abort;
		}
	    }
	}
	if ((oldLogName[0] != 0) && !(flags & IGNORE_LOG_EXISTS)) {
	    char msg[300], unknown[20], *modTime, *userName;
	    struct stat atts;
	    struct passwd *pwd;
	    int i;

	    if (stat(oldLogName, &atts) == 0) {
		modTime = ctime(&atts.st_mtime);
		modTime[24] = 0;
		pwd = getpwuid((int) atts.st_uid);
		if (pwd == NULL) {
		    sprintf(unknown, "user %d", atts.st_uid);
		    userName = unknown;
		} else {
		    userName = pwd->pw_name;
		}
	    } else {
		modTime = userName = "??";
	    }
	    sprintf(msg, "There's a log file \"%.50s\" that describes \
edits made to \"%.50s\" by \"%s\" on %s.  This means that either \
someone's already editing the file \
(maybe you?), or the editor crashed during the middle of the last \
edit.  Should I recover the changes made during that edit session?",
		    oldLogName, name, userName, modTime);
	    i = Sx_Notify(mxwPtr->display,
		    DefaultRootWindow(mxwPtr->display), -1, -1, 0, msg,
		    mxwPtr->fontPtr, 1, "Recover & Delete Log",
		    "Ignore Log", "Delete Log", "Quit", NULL);
	    if (i == 0) {
		Tcl_Return(interp, (char *) NULL, TCL_STATIC);
		result = Undo_Recover(fileInfoPtr->file, oldLogName, interp);
		Undo_Mark(fileInfoPtr->log);
		if (result != TCL_OK) {
		    (void) Sx_Notify(mxwPtr->display,
			    DefaultRootWindow(mxwPtr->display), -1, -1, 0,
			    interp->result, mxwPtr->fontPtr, 1,
			    "Skip Recovery", (char *) NULL);
		    Tcl_Return(interp, (char *) NULL, TCL_STATIC);
		} else {
		    unlink(oldLogName);
		}
	    } else if (i == 2) {
		unlink(oldLogName);
	    } else if (i == 3) {
		goto abort;
	    }
	}
    }
    return TCL_OK;

    abort:
    DetachFileInfo(mxwPtr);
    RecycleFileInfo(fileInfoPtr, mxwPtr->display);
    interp->result = "command aborted at your request";
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DetachFileInfo --
 *
 *	Unlike a window from its MxFileInfo structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	MxwPtr will no longer be in the chain of windows attached to
 *	mxwPtr->fileInfoPtr.
 *
 *----------------------------------------------------------------------
 */

static void
DetachFileInfo(mxwPtr)
    register MxWindow *mxwPtr;		/* Window to cut away from its file. */
{
    register MxFileInfo *fileInfoPtr;

    fileInfoPtr = mxwPtr->fileInfoPtr;
    if (fileInfoPtr == NULL) {
	return;
    }
    if (fileInfoPtr->mxwPtr == mxwPtr) {
	fileInfoPtr->mxwPtr = mxwPtr->nextPtr;
    } else {
	register MxWindow *mxwPtr2;
	for (mxwPtr2 = fileInfoPtr->mxwPtr; mxwPtr2 != NULL;
		mxwPtr2 = mxwPtr2->nextPtr) {
	    if (mxwPtr2->nextPtr == mxwPtr) {
		mxwPtr2->nextPtr = mxwPtr->nextPtr;
		break;
	    }
	}
    }
    mxwPtr->fileInfoPtr = NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * RecycleFileInfo --
 *
 *	De-allocate everything in an MxFileInfo structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The stuff in fileInfoPtr gets released, if there are no
 *	windows referring to it anymore.
 *
 *----------------------------------------------------------------------
 */

static void
RecycleFileInfo(fileInfoPtr, display)
    register MxFileInfo *fileInfoPtr;	/* File info to be cleaned up. */
    Display *display;			/* Connection to X server (needed to
					 * clear the selection). */
{

    if (fileInfoPtr->mxwPtr != NULL) {
	return;
    }

    if (fileInfoPtr == MxSelectedFile) {
	Sx_SelectionClear(display);
    }
    if (fileListPtr == fileInfoPtr) {
	fileListPtr = fileInfoPtr->nextPtr;
    } else {
	register MxFileInfo *fileInfoPtr2;
	for (fileInfoPtr2 = fileListPtr; fileInfoPtr2 != NULL;
		fileInfoPtr2 = fileInfoPtr2->nextPtr) {
	    if (fileInfoPtr2->nextPtr == fileInfoPtr) {
		fileInfoPtr2->nextPtr = fileInfoPtr->nextPtr;
		break;
	    }
	}
    }
    if (fileInfoPtr->name != NULL) {
	free((char *) fileInfoPtr->name);
    }
    if (fileInfoPtr->log != NULL) {
	Undo_LogDelete(fileInfoPtr->log);
    }
    while (fileInfoPtr->hlPtr != NULL) {
	MxHighlightDelete(fileInfoPtr->hlPtr);
    }
    if (fileInfoPtr->flags & DELETE) {
	Mx_FileClose(fileInfoPtr->file);
    }
    free((char *) fileInfoPtr);
    mx_FileCount--;
}

/*
 *----------------------------------------------------------------------
 *
 * StructureProc --
 *
 *	Invoked by Sx on changes to the window's structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the window just got destroyed, clean up the MxWindow structure
 *	and recycle its space.
 *
 *----------------------------------------------------------------------
 */

static void
StructureProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;		/* Window to clean up. */
    XEvent *eventPtr;			/* Event that just happened. */
{
    MxFileInfo *fileInfoPtr;

    if (eventPtr->type != DestroyNotify) {
	return;
    }
    XFreeGC(mxwPtr->display, mxwPtr->textGc);
    if (mxwPtr->grayGc != None) {
	XFreeGC(mxwPtr->display, mxwPtr->grayGc);
    }
    if (mxwPtr->reverseGc != None) {
	XFreeGC(mxwPtr->display, mxwPtr->reverseGc);
    }
    MxCleanupRedisplay(mxwPtr);
    Cmd_TableDelete(mxwPtr->cmdTable);
    Tcl_DeleteInterp(mxwPtr->interp);
    if (mxwPtr->cmdString != NULL) {
	free((char *) mxwPtr->cmdString);
    }
    if (mxwPtr->searchString != NULL) {
	free((char *) mxwPtr->searchString);
    }
    if (mxwPtr->replaceString != NULL) {
	free((char *) mxwPtr->replaceString);
    }
    if (mxwPtr->msgString != NULL) {
	free((char *) mxwPtr->msgString);
    }
    MxHistoryCleanup(mxwPtr);

    /*
     * Remove from list of windows on file and recycle file
     * information also, if necessary.
     */

    fileInfoPtr = mxwPtr->fileInfoPtr;
    if (fileInfoPtr->caretWindow == mxwPtr) {
	fileInfoPtr->caretWindow = NULL;
    }
    DetachFileInfo(mxwPtr);
    RecycleFileInfo(fileInfoPtr, mxwPtr->display);
    XDeleteContext(mxwPtr->display, mxwPtr->w, mxContext);
    free((char *) mxwPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * SetTitle --
 *
 *	This procedure sets up correct information in the title of
 *	an Mx window, and also stores the correct name within X.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The title bar gets re-made, plus XStoreName is called to set
 *	up X's idea of name for this window.
 *
 *----------------------------------------------------------------------
 */

static void
SetTitle(mxwPtr)
    register MxWindow *mxwPtr;		/* Window whose title is to be set. */
{
    char name[100];
    char *title;

    if (*mxwPtr->fileInfoPtr->name != 0) {
	title = mxwPtr->fileInfoPtr->name;
    } else {
	title = "Scratch Window";
    }
    if (mxwPtr->fileInfoPtr->flags & MODIFIED) {
	if (mxwPtr->title != NULL) {
	    Sx_TitleMake(mxwPtr->display, mxwPtr->title,
		    mxwPtr->fontPtr, mxwPtr->titleForeground,
		    mxwPtr->titleBackground, mxwPtr->titleStripe,
		    title, (char *) NULL, "Modified");
	}
	sprintf(name, "%.80s!", title);
    } else {
	if (mxwPtr->title != NULL) {
	    Sx_TitleMake(mxwPtr->display, mxwPtr->title,
		    mxwPtr->fontPtr, mxwPtr->titleForeground,
		    mxwPtr->titleBackground, mxwPtr->titleStripe,
		    title, (char *) NULL, (char *) NULL);
	}
	sprintf(name, "%.80s ", title);
    }
    XStoreName(mxwPtr->display, mxwPtr->w, name);
    XSetIconName(mxwPtr->display, mxwPtr->w, name);
}

/*
 *----------------------------------------------------------------------
 *
 * MxInit --
 *
 *	Once-only initialization for Mx widgets.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Create the Mx context, cursors, etc.
 *
 *----------------------------------------------------------------------
 */

static void
MxInit(display)
    Display *display;		/* Connection to server. */
{
    static XColor black = {0, 0, 0, 0, 0};
    static XColor white = {0, ~0, ~0, ~0, 0};
    Pixmap source, mask;

    init = 1;
    mxContext = XUniqueContext();


    source = XCreateBitmapFromData(display, DefaultRootWindow(display),
	    cursor_bits, cursor_width, cursor_height);
    mask = XCreateBitmapFromData(display, DefaultRootWindow(display),
	    cursMask_bits, cursMask_width, cursMask_height);
    cursor = XCreatePixmapCursor(display, source, mask, &black, &white,
	    cursor_x_hot, cursor_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);
}
