/* 
 * mxEntry.c --
 *
 *	This file contains a collection of utility procedures that
 *	manage all of the entry subwindows in an Mx window, such
 *	as those used for commands and searching.
 *
 * Copyright (C) 1986, 1989 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/mxEntry.c,v 1.2 89/06/09 15:12:54 ouster Exp $ SPRITE (Berkeley)";
#endif not lint


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sx.h>
#include <sys/time.h>

#include "mx.h"
#include "mxInt.h"

/*
 * Forward references to procedures defined later in this file:
 */

static void		MxCmdKeyProc();
static void		ReplaceKeyProc();
static void		SearchKeyProc();

/*
 *----------------------------------------------------------------------
 *
 * MxMakeCmdWindow --
 *
 * 	If there isn't already a command subwindow for mxwPtr, this
 *	procedure makes a new one.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	mxwPtr->cmdWindow gets filled in.
 *
 *----------------------------------------------------------------------
 */

void
MxMakeCmdWindow(mxwPtr)
    register MxWindow *mxwPtr;		/* Window in which to create. */
{
    if (mxwPtr->cmdWindow != 0) {
	return;
    }
    if (mxwPtr->cmdString == NULL) {
	mxwPtr->cmdString = (char *) malloc((unsigned) MAX_STRING_LENGTH);
	mxwPtr->cmdString[0] = 0;
    }
    mxwPtr->cmdWindow = Sx_CreatePacked(mxwPtr->display, mxwPtr->w, SX_BOTTOM,
	    Sx_DefaultHeight(mxwPtr->display, mxwPtr->fontPtr), 0, 1,
	    mxwPtr->border, mxwPtr->scrollbar, mxwPtr->background);
    Sx_EntryMake(mxwPtr->display, mxwPtr->cmdWindow, mxwPtr->w, "Command: ",
	    mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
	    mxwPtr->cmdString, MAX_STRING_LENGTH);
    (void) Sx_HandlerCreate(mxwPtr->display, mxwPtr->cmdWindow,
	    KeyPressMask|ButtonPressMask, MxCmdKeyProc,
	    (ClientData) mxwPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * MxCmdKeyProc --
 *
 *	This procedure is invoked whenever a key or button is pressed
 *	in the command subwindow.  If it's a <return> or middle button,
 *	invoke the command that's in the window.  If it's a control-Q,
 *	then delete the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Whatever the command does.
 *
 *----------------------------------------------------------------------
 */

static void
MxCmdKeyProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;	/* Window in which command was invoked. */
    XEvent *eventPtr;		/* ButtonPress or KeyPress event. */
{
    int numBytes;
    char keyString[20];
    Window w = mxwPtr->w;
    Display *display = mxwPtr->display;

    if (mxwPtr->flags & DESTROYED) {
	return;
    }

    /*
     * Ignore all buttons but the middle one, which is treated the
     * same as carriage return.
     */

    if (eventPtr->type == ButtonPress) {
	if (eventPtr->xbutton.button == Button2) {
	    doCommand:
	    Undo_Mark(mxwPtr->fileInfoPtr->log);
	    (void) MxDoCmd(mxwPtr, mxwPtr->cmdString);

	    /*
	     * Watch out!  The command could have destroyed the window.
	     */

	    if (MxGetMxWindow(display, w) == mxwPtr) {
		Undo_Mark(mxwPtr->fileInfoPtr->log);
	    }
	}
	return;
    }

    /*
     * Handle some control characters specially:
     * return:		execute command.
     * ^Q:		delete command subwindow.
     */

    numBytes = XLookupString(&eventPtr->xkey, keyString, 20, (KeySym *) NULL,
	    (XComposeStatus *) NULL);
    if (numBytes != 1) {
	return;
    }
    if (*keyString == '\15') {
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	goto doCommand;
    } else if (*keyString == '\21') {
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	Sx_Unpack(mxwPtr->display, mxwPtr->cmdWindow);
	XDestroyWindow(mxwPtr->display, mxwPtr->cmdWindow);
	mxwPtr->cmdWindow = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxMakeSearchWindow --
 *
 *	This procedure creates the windows used to type search
 *	and replacement patterns.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If they don't exist already, the search and replacement
 *	subwindows are created.
 *
 *----------------------------------------------------------------------
 */

void
MxMakeSearchWindow(mxwPtr)
    register MxWindow *mxwPtr;		/* Window in which to create. */
{
    int height;

    if (mxwPtr->searchWindow != 0) {
	return;
    }
    if (mxwPtr->searchString == NULL) {
	mxwPtr->searchString = (char *) malloc((unsigned) MAX_STRING_LENGTH);
	*(mxwPtr->searchString) = 0;
    }
    if (mxwPtr->replaceString == NULL) {
	mxwPtr->replaceString = (char *) malloc((unsigned) MAX_STRING_LENGTH);
	*(mxwPtr->replaceString) = 0;
    }
    height = Sx_DefaultHeight(mxwPtr->display, mxwPtr->fontPtr);
    Sx_PackUpdateOff(mxwPtr->display, mxwPtr->w);
    if (mxwPtr->flags & TX_WINDOW) {
	mxwPtr->searchWindow = Sx_CreatePacked(mxwPtr->display, mxwPtr->w,
		SX_TOP, height, 0, 1, mxwPtr->border,
		mxwPtr->scrollbar, mxwPtr->background);
    } else {
	mxwPtr->searchWindow = Sx_CreatePacked(mxwPtr->display, mxwPtr->w,
		SX_TOP, height, 0, 0, mxwPtr->border,
		mxwPtr->scrollbar, mxwPtr->background);
	mxwPtr->replaceWindow = Sx_CreatePacked(mxwPtr->display, mxwPtr->w,
		SX_TOP, height, 0, 1, mxwPtr->border,
		mxwPtr->scrollbar, mxwPtr->background);
	Sx_EntryMake(mxwPtr->display, mxwPtr->replaceWindow,
		mxwPtr->w, "Replace: ",
		mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
		mxwPtr->replaceString, MAX_STRING_LENGTH);
	(void) Sx_HandlerCreate(mxwPtr->display, mxwPtr->replaceWindow,
		KeyPressMask|ButtonPressMask, ReplaceKeyProc,
		(ClientData) mxwPtr);
    }
    Sx_EntryMake(mxwPtr->display, mxwPtr->searchWindow,
	    mxwPtr->w, "Search: ",
	    mxwPtr->fontPtr, mxwPtr->foreground, mxwPtr->background,
	    mxwPtr->searchString, MAX_STRING_LENGTH);
    (void) Sx_HandlerCreate(mxwPtr->display, mxwPtr->searchWindow,
	    KeyPressMask|ButtonPressMask, SearchKeyProc, (ClientData) mxwPtr);
    Sx_PackUpdateOn(mxwPtr->display, mxwPtr->w);
}

/*
 *----------------------------------------------------------------------
 *
 * DestroySearchWindow --
 *
 *	Deletes the search and replacement windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	No more search or replace window.
 *
 *----------------------------------------------------------------------
 */

void
DestroySearchWindow(mxwPtr)
    register MxWindow *mxwPtr;		/* Window whose search and replace
					 * subwindows must go. */
{
    Sx_PackUpdateOff(mxwPtr->display, mxwPtr->w);
    Sx_Unpack(mxwPtr->display, mxwPtr->searchWindow);
    XDestroyWindow(mxwPtr->display, mxwPtr->searchWindow);
    if (mxwPtr->replaceWindow != 0) {
	Sx_Unpack(mxwPtr->display, mxwPtr->replaceWindow);
	XDestroyWindow(mxwPtr->display, mxwPtr->replaceWindow);
    }
    Sx_PackUpdateOn(mxwPtr->display, mxwPtr->w);
    mxwPtr->searchWindow = 0;
    mxwPtr->replaceWindow = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * SearchKeyProc --
 *
 *	Called for each key or button press in the "Search: "
 *	entry window.  This procedure handles control keys
 *	like ^Q and CR, plus the middle button.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window may be destroyed, or a search action may be invoked.
 *
 *----------------------------------------------------------------------
 */

static void
SearchKeyProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;	/* Window in which command was invoked. */
    XEvent *eventPtr;		/* ButtonPress or KeyPress event. */
{
    int numBytes;
    char keyString[20];

    if (mxwPtr->flags & DESTROYED) {
	return;
    }

    Tcl_SetVar(mxwPtr->interp, "searchString", mxwPtr->searchString, 1);
    
    if (eventPtr->type == ButtonPress) {
	if (eventPtr->xbutton.button == Button2) {
	    doSearch:
	    (void) MxDoCmd(mxwPtr, Tcl_GetVar(mxwPtr->interp, "searchCmd", 1));
	    Undo_Mark(mxwPtr->fileInfoPtr->log);
	}
	return;
    }

    numBytes = XLookupString(&eventPtr->xkey, keyString, 20, (KeySym *) NULL,
	    (XComposeStatus *) NULL);
    if (numBytes != 1) {
	return;
    }
    if (*keyString == '\15') {
	/*
	 * Carriage return:  invoke a search.
	 */
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	goto doSearch;
    } else if (*keyString == '\21') {
	/*
	 * Control-Q: delete the search window.
	 */
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	DestroySearchWindow(mxwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ReplaceKeyProc --
 *
 *	Called for each key or button press in the "Replace: " entry
 *	window.  This procedure handles control keys like ^Q and CR,
 *	plus the middle button.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window may be destroyed, or a replace command may be invoked.
 *
 *----------------------------------------------------------------------
 */

static void
ReplaceKeyProc(mxwPtr, eventPtr)
    register MxWindow *mxwPtr;	/* Window in which command was invoked. */
    XEvent *eventPtr;		/* ButtonPress or KeyPress event. */
{
    int numBytes;
    char keyString[20];

    if (mxwPtr->flags & DESTROYED) {
	return;
    }

    Tcl_SetVar(mxwPtr->interp, "replaceString", mxwPtr->replaceString, 1);

    if (eventPtr->type == ButtonPress) {
	if (eventPtr->xbutton.button == Button2) {
	    doReplace:
	    Undo_Mark(mxwPtr->fileInfoPtr->log);
	    (void) MxDoCmd(mxwPtr, Tcl_GetVar(mxwPtr->interp, "replaceCmd", 1));
	    Undo_Mark(mxwPtr->fileInfoPtr->log);
	}
	return;
    }

    numBytes = XLookupString(&eventPtr->xkey, keyString, 20, (KeySym *) NULL,
	    (XComposeStatus *) NULL);
    if (numBytes != 1) {
	return;
    }
    if (*keyString == '\15') {
	/*
	 * Carriage return:  invoke a replacement.
	 */
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	goto doReplace;
    } else if (*keyString == '\21') {
	/*
	 * Control-X: delete the search window.
	 */
	Sx_Focus(mxwPtr->display, mxwPtr->fileWindow);
	DestroySearchWindow(mxwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxOutputMsg --
 *
 * 	Display a one-line message in the message window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the message window doesn't exist, then it is created.
 *	An Sx_Entry is used to display the message.
 *
 *----------------------------------------------------------------------
 */

void
MxOutputMsg(mxwPtr, msg)
    register MxWindow *mxwPtr;		/* Window in which to create. */
    char *msg;				/* Message to display.  If it has
					 * more than MAX_STRING_LENGTH chars,
					 * only the first ones will appear. */
{
    static struct timeval flashPeriod = {0, 50000};		/* 50 ms */

    /*
     * If there isn't a message window, or if the string is too long
     * to be fully visible in the message window, then use a notifier.
     */
     
    if ((mxwPtr->msgWindow == NULL) ||
	    (XTextWidth(mxwPtr->fontPtr, msg, strlen(msg))
	    > mxwPtr->width)) {
	Sx_Notify(mxwPtr->display, mxwPtr->w, -1, -1, 0, msg, mxwPtr->fontPtr,
		0, "Continue", (char *) NULL);
	return;
    }

    if (mxwPtr->msgString == NULL) {
	mxwPtr->msgString = (char *) malloc((unsigned) MAX_STRING_LENGTH);
    }
    strncpy(mxwPtr->msgString, msg, MAX_STRING_LENGTH);
    mxwPtr->msgString[MAX_STRING_LENGTH-1] = 0;

    /*
     * Flash the window while refilling its contents.
     */

    XSetWindowBackground(mxwPtr->display, mxwPtr->msgWindow,
	    mxwPtr->foreground);
    XClearArea(mxwPtr->display, mxwPtr->msgWindow, 0, 0, 0, 0, False);
    XFlush(mxwPtr->display);
    select(0, (int *) 0, (int *) 0, (int *) 0, &flashPeriod);
    XSetWindowBackground(mxwPtr->display, mxwPtr->msgWindow,
	    mxwPtr->background);
    XClearArea(mxwPtr->display, mxwPtr->msgWindow, 0, 0, 0, 0, False);
    Sx_EntryMake(mxwPtr->display, mxwPtr->msgWindow,
	    mxwPtr->w, "", mxwPtr->fontPtr,
	    mxwPtr->foreground, mxwPtr->background, mxwPtr->msgString,
	    MAX_STRING_LENGTH);
}
