/* windClient.c -
 *
 *	Send button pushes and commands to the window's command
 *	interpreters.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 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.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[]="$Header: /ufs/repository/magic/windows/windClient.c,v 1.3 2001/01/12 22:13:34 jsolomon Exp $";
#endif  not lint

#include <stdio.h>
#include "misc/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "windows/windows.h"
#include "graphics/glyphs.h"
#include "windows/windInt.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "main/main.h"
#include "macros/macros.h"
#include "utils/utils.h"
#include "utils/malloc.h"
#include "graphics/graphics.h"
#include "misc/styles.h"
#include "textio/txcommands.h"
#include "undo/undo.h"

/* The following defines are used to indicate corner positions
 * of the box:
 */

#define WIND_BL 0
#define WIND_BR 1
#define WIND_TR 2
#define WIND_TL 3
#define WIND_ILG -1

/* our window client ID */
static WindClient windClientID = NULL;

global char *windCommands[] =
    {
	"*crash			cause a core dump",
	"*files			print out currently open files",
	"*grstats		print out stats on graphics",
	"*pause	[args]		print args and wait for <cr>",
	"*winddebug		set debugging mode",
	"*winddump		print out debugging info",

	"center [x y]		center window on the cursor or indicated coordinate",
	"closewindow		close a window",
	"echo [-n] [strings]	print text on the terminal",
	"grow			blow a window up to full-screen size or back again",
	"help [pattern]		print out synopses for all commands valid\n\
			in the current window (or just those\n\
			containing pattern)",
	"imacro [char [string]]	define or print an interactive macro called char",
	"logcommands [file [update]]\n\
			log all commands into a file",
	"macro [char [string]]	define or print a macro called char",
	"openwindow [cell][name]\n\
			open a new window with indicated name, bound to indicated cell",
	"over			move a window over (on top of) the rest",
	"pushbutton button act	push a mouse button",
	"redo [count]		redo commands",
	"redraw			redraw the display",
	"reset			reset the display",
	"scroll dir [amount]	scroll the window",
	"send type command	send a command to a certain window type",
	"setpoint [x y [WID]]	force to cursor (point) to x,y in window WID",
	"sleep seconds		sleep for a number of seconds",
	"source filename		read in commands from file",
	"specialopen [coords] type [args]\n\
			open a special window",
	"quit			exit magic",
	"underneath		move a window underneath the rest",
	"undo [count]		undo commands",
	"updatedisplay		force the display to be updated",
	"version			print out version info",
	"view [get]              zoom window out so everything is visible",
	"windowcaption [on|off]	toggle title caption for new windows",	
	"windowscrollbars [on|off]\n\
			toggle scroll bars for new windows",
	"windowpositions [file]	print out window positions",
	"xview               	zoom window out so everything is unexpanded",
	"zoom amount		zoom window by amount",
	0
    };

extern int windCaptionCmd(), windCrashCmd();
extern int windFilesCmd(), windCloseCmd(), windOpenCmd();
extern int windQuitCmd(), windRedrawCmd();
extern int windResetCmd(), windSpecialOpenCmd();
extern int windOverCmd(), windUnderCmd(), windDebugCmd();
extern int windDumpCmd(), windHelpCmd();
extern int windLogCommandsCmd(), windUpdateCmd(), windSleepCmd();
extern int windSourceCmd();
extern int windSetpointCmd();
extern int windPushbuttonCmd(), windSendCmd();
extern int windPauseCmd(), windGrstatsCmd();
extern int windEchoCmd(), windGrowCmd(), windMacroCmd(), windIntMacroCmd();
extern int windUndoCmd(), windRedoCmd();
extern int windCenterCmd(), windScrollCmd();
extern int windVersionCmd(), windViewCmd(), windXviewCmd(), windZoomCmd();
extern int windScrollBarsCmd(), windPositionsCmd();

static int (*windFuncs[])() =
    {
	windCrashCmd,
	windFilesCmd,
	windGrstatsCmd,
	windPauseCmd,
	windDebugCmd,
	windDumpCmd,

	windCenterCmd,
	windCloseCmd,
	windEchoCmd,
	windGrowCmd,
	windHelpCmd,
	windIntMacroCmd,
	windLogCommandsCmd,
	windMacroCmd,
	windOpenCmd,
	windOverCmd,
	windPushbuttonCmd,
	windRedoCmd,
	windRedrawCmd,
	windResetCmd,
	windScrollCmd,
	windSendCmd,
	windSetpointCmd,
	windSleepCmd,
	windSourceCmd,
	windSpecialOpenCmd,
	windQuitCmd,
	windUnderCmd,
	windUndoCmd,
	windUpdateCmd,
	windVersionCmd,
	windViewCmd,
	windCaptionCmd,
	windScrollBarsCmd,
	windPositionsCmd,
	windXviewCmd,
	windZoomCmd
    };

static Rect windFrameRect;
static MagWindow *windFrameWindow;
static int windButton = TX_LEFT_BUTTON;
static int windCorner = WIND_ILG;	/* Nearest corner when button went
					 * down.
					 */


/*
 * ----------------------------------------------------------------------------
 *	windButtonSetCursor --
 *
 * 	Used to set the programmable cursor for a particular
 *	button state.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Selects and sets a programmable cursor based on the given
 *	button (for sizing or moving) and corner.
 * ----------------------------------------------------------------------------
 */

windButtonSetCursor(button, corner)
int button;			/* Button that is down. */
int corner;			/* Corner to be displayed in cursor. */

{
    switch (corner)
    {
	case WIND_BL:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_LLWIND);
	    else
		GrSetCursor(STYLE_CURS_LLWINDCORN);
	    break;
	case WIND_BR:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_LRWIND);
	    else
		GrSetCursor(STYLE_CURS_LRWINDCORN);
	    break;
	case WIND_TL:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_ULWIND);
	    else
		GrSetCursor(STYLE_CURS_ULWINDCORN);
	    break;
	case WIND_TR:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_URWIND);
	    else
		GrSetCursor(STYLE_CURS_URWINDCORN);
	    break;
    }
}


/*
 * ----------------------------------------------------------------------------
 * windGetCorner --
 *
 * 	Returns the corner of the window closest to a given screen location.
 *
 * Results:
 *	An integer value is returned, indicating the corner closest to
 *	the given screen location.  
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

int
windGetCorner(screenPoint, screenRect)
    Point *screenPoint;
    Rect *screenRect;

{
    Rect r;

    /* Find out which corner is closest.  Consider only the
     * intersection of the box with the window (otherwise it
     * may not be possible to select off-screen corners.
     */

    r = *screenRect;
    GeoClip(&r, &GrScreenRect);
    if (screenPoint->p_x < ((r.r_xbot + r.r_xtop)/2))
    {
	if (screenPoint->p_y < ((r.r_ybot + r.r_ytop)/2))
	    return WIND_BL;
	else 
	    return WIND_TL;
    }
    else
    {
	if (screenPoint->p_y < ((r.r_ybot + r.r_ytop)/2))
	    return WIND_BR;
	else 
	    return WIND_TR;
    }
}

/*
 * ----------------------------------------------------------------------------
 * windMoveRect --
 *
 * 	Repositions a rectangle by one of its corners.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The rectangle is changed so that the given corner is at the
 *	given position.  
 * ----------------------------------------------------------------------------
 */

void
windMoveRect(wholeRect, corner, p, rect)
    bool wholeRect;		/* move the whole thing?  or just a corner? */
    int corner;			/* Specifies a corner in the format
				 * returned by ToolGetCorner.
				 */
    Point *p;			/* New position of corner, in screen
				 * coordinates.
				 */
    Rect *rect;
{
    int x, y, tmp;

    /* Move the rect.  If an illegal corner is specified, then
     * move by the bottom-left corner.
     */

    if (wholeRect)
    {
	switch (corner)
	{
	    case WIND_BL:
		x = p->p_x - rect->r_xbot;
		y = p->p_y - rect->r_ybot;
		break;
	    case WIND_BR:
		x = p->p_x - rect->r_xtop;
		y = p->p_y - rect->r_ybot;
		break;
	    case WIND_TR:
		x = p->p_x - rect->r_xtop;
		y = p->p_y - rect->r_ytop;
		break;
	    case WIND_TL:
		x = p->p_x - rect->r_xbot;
		y = p->p_y - rect->r_ytop;
		break;
	    default:
		x = p->p_x - rect->r_xbot;
		y = p->p_y - rect->r_ybot;
		break;
	}
	rect->r_xbot += x;
	rect->r_ybot += y;
	rect->r_xtop += x;
	rect->r_ytop += y;
    }
    else
    {
	switch (corner)
	{
	    case WIND_BL:
		rect->r_xbot = p->p_x;
		rect->r_ybot = p->p_y;
		break;
	    case WIND_BR:
		rect->r_xtop = p->p_x;
		rect->r_ybot = p->p_y;
		break;
	    case WIND_TR:
		rect->r_xtop = p->p_x;
		rect->r_ytop = p->p_y;
		break;
	    case WIND_TL:
		rect->r_xbot = p->p_x;
		rect->r_ytop = p->p_y;
		break;
	}

	/* If the movement turned the box inside out, turn it right
	 * side out again.
	 */
	
	if (rect->r_xbot > rect->r_xtop)
	{
	    tmp = rect->r_xtop;
	    rect->r_xtop = rect->r_xbot;
	    rect->r_xbot = tmp;
	}
	if (rect->r_ybot > rect->r_ytop)
	{
	    tmp = rect->r_ytop;
	    rect->r_ytop = rect->r_ybot;
	    rect->r_ybot = tmp;
	}

    }
}


/*
 * ----------------------------------------------------------------------------
 * Button Routines --
 *
 * 	This page contains a set of routines to handle the puck
 *	buttons within the window border.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Left button:  used to move the whole window by the lower-left corner.
 *	Right button: used to re-size the window by its upper-right corner.
 *		If one of the left or right buttons is pushed, then the
 *		other is pushed, the corner is switched to the nearest
 *		one to the cursor.  This corner is remembered for use
 *		in box positioning/sizing when both buttons have gone up.
 *	Bottom button: Center the view on the point where the crosshair is
 *		at when the button is released.
 * ----------------------------------------------------------------------------
 */

windFrameDown(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if (WindOldButtons == 0)
    {
	windFrameRect = w->w_frameArea;
	windFrameWindow = w;
	windButton = cmd->tx_button;
    }
#define BOTHBUTTONS (TX_LEFT_BUTTON | TX_RIGHT_BUTTON)
    if ((WindNewButtons & BOTHBUTTONS) == BOTHBUTTONS)
    {
	windCorner = windGetCorner(&(cmd->tx_p), &(windFrameWindow->w_frameArea));
    }
    else if (cmd->tx_button == TX_LEFT_BUTTON) 
    {
	windCorner = WIND_BL;
	windButtonSetCursor(windButton, windCorner);
    }
    else if (cmd->tx_button == TX_RIGHT_BUTTON) 
    {
	windCorner = WIND_TR;
	windButtonSetCursor(windButton, windCorner);
    }
}

    /*ARGSUSED*/
void
windFrameUp(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if (WindNewButtons == 0)
    {
	GrSetCursor(STYLE_CURS_NORMAL);
	switch (cmd->tx_button)
	{
	    case TX_LEFT_BUTTON:
	    case TX_RIGHT_BUTTON:
		windMoveRect( (windButton == TX_LEFT_BUTTON),
			windCorner, &(cmd->tx_p), &windFrameRect);
		WindReframe(windFrameWindow, &windFrameRect, FALSE,
			(windButton == TX_LEFT_BUTTON) );
		break;
	}
    }
    else
    {
	/* If both buttons are down and one is released, we just change
	 * the cursor to reflect the current corner and the remaining
	 * button (i.e. move or size window).
	 */

	windCorner = windGetCorner(&(cmd->tx_p), &(windFrameWindow->w_frameArea));
	windButtonSetCursor(windButton, windCorner);
    }
}



/*
 * ----------------------------------------------------------------------------
 * windClientButtons --
 *
 *	Handle button pushes to the window border.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	depends upon where the button was pushed.
 * ----------------------------------------------------------------------------
 */

void
windClientButtons(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    extern void windBarLocations();

    /*
     * Is this an initial 'down push' in a non-iconic window?  If so, we
     * will initiate some user-interaction sequence, such as moving the corner
     * of the window or growing it to full-screen size.
     *
     * (An 'iconic' window is one that is closed down to an icon -- this
     * currently only happens when we are using the Sun window package, but
     * in the future it might happen in other cases too.)
     *
     */
    if ((WindOldButtons == 0) && ((w->w_flags & WIND_ISICONIC) == 0)) {   
	/* single button down */
	Point p;
	Rect caption, leftBar, botBar, up, down, right, left, zoom;

	windFrameWindow = NULL;
	if (w == NULL) return;
	p.p_x = w->w_screenArea.r_xtop - w->w_screenArea.r_xbot;
	p.p_y = w->w_screenArea.r_ytop - w->w_screenArea.r_ybot;
	caption = w->w_allArea;
	if ((w->w_flags & WIND_CAPTION) != 0)
	    caption.r_ybot = caption.r_ytop - TOP_BORDER(w) + 1;
	else
	    caption.r_ybot = caption.r_ytop;

	/* Handle 'grow' for our window package. */
	if (WindPackageType == WIND_MAGIC_WINDOWS) {
	    if ((cmd->tx_button == TX_MIDDLE_BUTTON) && 
		GEO_ENCLOSE(&cmd->tx_p, &caption)) {
		    WindFullScreen(w);
		    return;
	    };
	}

	/* Always handle scroll bars ourselves, even if there is an external
	 * window package.
	 */
	if ((w->w_flags & WIND_SCROLLBARS) != 0) {
	    windBarLocations(w, &leftBar, &botBar, &up, &down, 
		&right, &left, &zoom);
	    if (cmd->tx_button == TX_MIDDLE_BUTTON) {
		if (GEO_ENCLOSE(&cmd->tx_p, &leftBar)) {
		    /* move elevator */
		    p.p_x = 0;
		    p.p_y = 
			w->w_bbox->r_ybot + 
			((w->w_bbox->r_ytop - w->w_bbox->r_ybot) * 
			(cmd->tx_p.p_y - leftBar.r_ybot))
			/ (leftBar.r_ytop - leftBar.r_ybot) -
			(w->w_surfaceArea.r_ytop + w->w_surfaceArea.r_ybot)/2;
		    WindScroll(w, &p, (Point *) NULL);
		    return;
		}
		else if (GEO_ENCLOSE(&cmd->tx_p, &botBar)) {
		    /* move elevator */
		    p.p_y = 0;
		    p.p_x = 
			w->w_bbox->r_xbot + 
			((w->w_bbox->r_xtop - w->w_bbox->r_xbot) *
			(cmd->tx_p.p_x - botBar.r_xbot))
			/ (botBar.r_xtop - botBar.r_xbot) -
			(w->w_surfaceArea.r_xtop + w->w_surfaceArea.r_xbot)/2;
		    WindScroll(w, &p, (Point *) NULL);
		    return;
		}
		else if (GEO_ENCLOSE(&cmd->tx_p, &up)) {
		    /* scroll up */
		    p.p_y = -p.p_y;
		    p.p_x = 0;
		    WindScroll(w, (Point *) NULL, &p);
		    return;
		}
		else if (GEO_ENCLOSE(&cmd->tx_p, &down)) {
		    /* scroll down */
		    p.p_x = 0;
		    WindScroll(w, (Point *) NULL, &p);
		    return;
		}
		else if (GEO_ENCLOSE(&cmd->tx_p, &right)) {
		    /* scroll right */
		    p.p_x = -p.p_x;
		    p.p_y = 0;
		    WindScroll(w, (Point *) NULL, &p);
		    return;
		}
		else if (GEO_ENCLOSE(&cmd->tx_p, &left)) {
		    /* scroll left */
		    p.p_y = 0;
		    WindScroll(w, (Point *) NULL, &p);
		    return;
		}
	    }
	    if (GEO_ENCLOSE(&cmd->tx_p, &zoom)) {
		/* zoom in, out, or view */
		switch (cmd->tx_button)
		{
		    case TX_LEFT_BUTTON:
			WindZoom(w, 2.0);
			break;
		    case TX_MIDDLE_BUTTON:
			WindView(w);
			break;
		    case TX_RIGHT_BUTTON:
			WindZoom(w, 0.5);
			break;
		};
		return;
	    }
	};

	/* else fall through */
    };

    /*
     * At this point, we have decided that the button was not an initial
     * button push for Magic's window package.  Maybe an external window
     * package wants it, or maybe it is a continuation of a previous Magic
     * sequence (such as moving a corner of a window).
     */
    switch ( WindPackageType )
    {
	case WIND_X_WINDOWS:
	    break;
	
	case WIND_SUN_WINDOWS:
#ifdef	SUNVIEW
	    windSunHandleCaptionInput(w, cmd);
#endif	SUNVIEW
	    break;
	
	default:
	    /* Magic Windows */
	    if (cmd->tx_button == TX_MIDDLE_BUTTON) 
		return;
	    if ((cmd->tx_buttonAction == TX_BUTTON_UP) && 
		(windFrameWindow == NULL)) 
		return;

	    /* no special area or else an up push -- reframe window */
	    switch (cmd->tx_buttonAction)
	    {
		case TX_BUTTON_DOWN:
		    windFrameDown(w, cmd);
		    break;
		case TX_BUTTON_UP:
		    windFrameUp(w, cmd);
		    break;
		default:
		    TxError("windClientButtons failed!!.\n");
		    break;
	    }
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * windClientInterp
 *
 * Window's command interpreter.
 * Dispatches to long commands, providing them with the window and command
 * we are passed.
 *
 * Results:
 *	none.
 *
 * Side effects:
 *	Whatever occur as a result of executing the long
 *	command supplied.
 *
 * ----------------------------------------------------------------------------
 */

Void
windCmdInterp(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int cmdNum;

    switch (cmd->tx_button)
    {
        case TX_LEFT_BUTTON:
        case TX_RIGHT_BUTTON:
        case TX_MIDDLE_BUTTON:
	    windClientButtons(w, cmd);
	    break;
	case TX_NO_BUTTON:
	    if (cmd->tx_argc > 0)
	    {
		cmdNum = Lookup(cmd->tx_argv[0], windCommands);
		ASSERT(cmdNum >= 0, "windCmdInterp");

		(void) (*windFuncs[cmdNum])(w, cmd);
		UndoNext();
	    }
	    break;
	default:
	    ASSERT(FALSE, "windCmdInterp");
	    break;
    }
}


/*
 * ----------------------------------------------------------------------------
 *  windCmdInit --
 *
 *	Initialize the window client.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windClientInit()
{
    windClientID = WindAddClient(WINDOW_CLIENT, ( Void (*)() ) NULL,
	( Void (*)() ) NULL, ( Void (*)() ) NULL, windCmdInterp, 
	( Void (*)() ) NULL, ( Void (*)() ) NULL, ( Void (*)() ) NULL,
	windCommands, (GrGlyph *) NULL);
}
