/* windSend.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[] __attribute__ ((unused)) = "$Header$";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "windows/windows.h"
#include "graphics/glyphs.h"
#include "windows/windInt.h"
#include "utils/stack.h"
#include "utils/utils.h"
#include "utils/signals.h"
#include "textio/txcommands.h"

clientRec *windClient = NULL;

bool windPrintCommands = FALSE;	/* debugging flag */

global TxCommand *WindCurrentCmd; /* The current command.
				   */
global MagWindow *WindCurrentWindow; /* The window at which the current command
				   * was invoked.
				   */

global int WindOldButtons;	/* The buttons for the last command */
global int WindNewButtons;	/* The buttons this time */

static WindClient windGrabber =  (WindClient) NULL;
				/* If this variable is non-null then send
				 * all commands to it
				 */

Stack *windGrabberStack = NULL;

/* Forward declarations */

extern void WindGrabInput();
extern void WindReleaseInput();
extern void windHelp();


/*
 * ----------------------------------------------------------------------------
 * WindSendCommand --
 *
 *	Send a command to a window to be executed.  If the window passed is
 *	NULL then whatever window is at the point given in the command is
 *	used.
 *
 * Results:
 *	 0 if the command was able to be processed.
 *	-1 on an ambiguous command error.
 *	-2 on an unknown command error.
 *	-3 on other error.
 *
 * Side effects:
 *	Whatever the window wishes to do with the command.
 * ----------------------------------------------------------------------------
 */

int
WindSendCommand(w, cmd, quiet)
    MagWindow *w;
    TxCommand *cmd;	/* A pointer to a command */
    bool quiet;		/* Don't print error/warning messages if this is set */
{
    int windCmdNum, clientCmdNum;
    clientRec *rc;
    bool inside;	/* tells us if we are inside of a window */

    /* This thing is a horrendous mess.  Changing WindClient to	*/
    /* MagWindow in the arguments list is a big help, but the	*/
    /* following code should be simplified.  Namely, w == 0	*/
    /* only when the command comes from the command line.	*/
    /* Tcl/Tk commands and graphics are set up to supply a	*/
    /* valid w.  Under normal conditions, we should not need to	*/
    /* guess what the client is.				*/

    /* The big problem here is that all windows are expected	*/
    /* to take windClient commands---but that prevents setting	*/
    /* up windows which don't.  This should be handled better,	*/
    /* probably at the low level of the MagWindow structure	*/
    /* itself.							*/

    if (windClient == (clientRec *) NULL)
	windClient = (clientRec *) WindGetClient(WINDOW_CLIENT, TRUE);

    /* ignore no-op commands */
    if ( (cmd->tx_button == TX_NO_BUTTON) && (cmd->tx_argc == 0) )
    {
	return 0;
    }

    inside = FALSE;
    ASSERT( (cmd->tx_button == TX_NO_BUTTON) || (cmd->tx_argc == 0),
	"WindSendCommand");

    WindOldButtons = WindNewButtons;
    if (cmd->tx_button == TX_NO_BUTTON)
    {
	if (windClient == (clientRec *)NULL) return -2;

	/* If window commands are disallowed by the client (set by */
	/* the client's WIND_COMMANDS flag), report no command.	   */

	if ((w != NULL) && !(w->w_flags & WIND_COMMANDS))
	    windCmdNum = -2;
	else
	    windCmdNum = Lookup(cmd->tx_argv[0], windClient->w_commandTable);
    }
    else
    {
	if (cmd->tx_buttonAction == TX_BUTTON_DOWN)
	    WindNewButtons |= cmd->tx_button;
	else
	    WindNewButtons &= ~(cmd->tx_button);
    }

    /* If we were passed a NULL MagWindow pointer, try to determine the */
    /* window from the command's window ID number.			*/

    if (w == (MagWindow *)NULL)
    {
	if (cmd->tx_wid == WIND_UNKNOWN_WINDOW)
	{
	    w = windSearchPoint( &(cmd->tx_p), &inside);
	    if (w != NULL)
		cmd->tx_wid = w->w_wid;
	}
	else if (cmd->tx_wid >= 0)
	    w = WindSearchWid(cmd->tx_wid);
    }

    if (w != (MagWindow *) NULL)
    {
	inside = GEO_ENCLOSE(&cmd->tx_p, &w->w_screenArea);
	if ((!inside) && (w->w_flags & WIND_COMMANDS))
	    rc = windClient;	/* Handles border regions */
	else
	    rc = (clientRec *) w->w_client;
    }
    else
	/* Can't determine a window---assume a windowless layout client */
	rc = (clientRec *) WindGetClient(DEFAULT_CLIENT, TRUE);

    if (windGrabber != (WindClient) NULL)
    {
	/* this client wants to hog all commands */
	rc = (clientRec *) windGrabber;
    }

    /* At this point, the command is all set up and ready to send to
     * the client.
     */
    ASSERT(rc != (clientRec *) NULL, "WindSendCommand");
    if (windPrintCommands)
    {
	TxPrintf("Sending command:\n");
	windPrintCommand(cmd);
    }
    WindCurrentCmd = cmd;
    WindCurrentWindow = w;

    if (cmd->tx_button == TX_NO_BUTTON)
    {
	clientCmdNum = Lookup(cmd->tx_argv[0], rc->w_commandTable);

	if ((clientCmdNum == -1) || (windCmdNum == -1))
	{
	    if (quiet == FALSE)
		TxError("That command abbreviation is ambiguous.\n");
	    return -1;
	}
	if ((windCmdNum == -2) && (clientCmdNum == -2))
	{
	    /* Not a valid command.  Help the user out by telling him
	     * what might be wrong. And also print out the command!
	     */
	    if (quiet == FALSE)
	    {
		TxError("Unknown command:");
		windPrintCommand(cmd);
		if (WindNewButtons != 0)
		{
		    char *bname = "unknown";
		    if (WindNewButtons & TX_LEFT_BUTTON) bname = "left";
		    else if (WindNewButtons & TX_RIGHT_BUTTON) bname = "right";
		    else if (WindNewButtons & TX_MIDDLE_BUTTON) bname = "middle";

		    TxError( "'%s' window is waiting for %s button to be released.\n",
		    		rc->w_clientName, bname);
		}
		return -3;
	    }
	    else if (windGrabber != (WindClient) NULL)
	    {
		if (quiet == FALSE)
		    TxError( "'%s' window is grabbing all input.\n", rc->w_clientName);
		return -3;
	    }

	    if (quiet == FALSE)
		TxError("Did you point to the correct window?\n");
	    return -2;
	}

	/* intercept 'help' */
	if ((windCmdNum >= 0) &&
		(strncmp(windClient->w_commandTable[windCmdNum],
		"help", 4) == 0) )
	{
	    TxUseMore();
	    windHelp(cmd, "Global", windClient->w_commandTable);
	    if (rc != windClient)
		windHelp(cmd, rc->w_clientName, rc->w_commandTable);
	    TxStopMore();
	    return 0;
	}

	/* If both command tables point to window commands,	*/
	/* only do the command once.				*/

	if (rc == windClient) clientCmdNum = -2;

	/* Ambiguity resolution.  If only one client reports a	*/
	/* valid command, then execute it.  If both clients	*/
	/* report a valid command, then compare them against	*/
	/* each other so that a full command name will take	*/
	/* precedence over an ambiguous command abbreviation.	*/
	/* Finally, if this doesn't resolve the command, the	*/
	/* registered client takes precedence over the general-	*/
	/* purpose window client, allowing clients to override	*/
	/* the general-purpose functions (like "zoom" and	*/
	/* "view", for instance).  This is the reverse of how	*/
	/* it was implemented prior to magic-7.3.61, but that	*/
	/* makes no sense.					*/

	if ((windCmdNum < 0) && (clientCmdNum >= 0))
	    (*(rc->w_command))(w, cmd);
	else if ((windCmdNum >= 0) && (clientCmdNum < 0))
	    (*(windClient->w_command))(w, cmd);
	else if ((windCmdNum >= 0) && (clientCmdNum >= 0))
	{
	    const char *(ownTable[3]);
	    int ownCmdNum;

	    ownTable[0] = rc->w_commandTable[clientCmdNum];
	    ownTable[1] = windClient->w_commandTable[windCmdNum];
	    ownTable[2] = NULL;
	    ownCmdNum = Lookup(cmd->tx_argv[0], ownTable);
	    ASSERT(ownCmdNum != -2, "WindSendCommand");
	    if (ownCmdNum == -1)
	    {
		if (quiet == FALSE)
		    TxError("That command abbreviation is ambiguous\n");
		return -1;
	    }
	    if (ownCmdNum == 0)
		(*(rc->w_command))(w, cmd);
	    else
		(*(windClient->w_command))(w, cmd);
	}
    }
    else
    {
	/* A button has been pushed.
	 * If there were no buttons pressed on the last command
	 * now there are, and direct all future button pushes to this
	 * client until all buttons are up again.
	 */

	/* windClient is responsible for processing actions in the	*/
	/* window border, assuming that the window handles its own	*/
	/* borders.  Scrollbars are only available on the layout	*/
	/* window, which is the "default" client.  So we check for a	*/
	/* position in the border region, and launch the window client	*/
	/* if it is.							*/

        if (WindOldButtons == 0) WindGrabInput((WindClient) rc);
	else if (WindNewButtons == 0) WindReleaseInput((WindClient) rc);
	(*(rc->w_command))(w, cmd);
    }

    /* A client may modify WindNewButtons & WindOldButtons in rare cases,
     * so we better check again.
     */
    if ((WindNewButtons == 0) && (windGrabber != (WindClient) NULL))
	WindReleaseInput((WindClient) rc);

    /* This is a bit of a hack, because it shouldn't be done in so many
     * places, but the command logging needs to regenerate the setpoint
     * positions as commands, and it needs the command's Y value to be
     * correct for the command.  It is okay to modify the command's
     * recorded position value, because logging the command is the only
     * other thing done with the structure.
     */
    if (w != NULL)
    {
	switch (WindPackageType)
	{
            case WIND_X_WINDOWS:
        	/* Windows have origin at lower-left corner */
	        cmd->tx_p.p_y = w->w_allArea.r_ytop - cmd->tx_p.p_y;
	    break;
	}
    }

    return 0;
}




/*
 * ----------------------------------------------------------------------------
 * WindGrabInput --
 *
 *	Grab all input -- that is, send all further commands to the
 *	specified client.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	pushes old grabber onto a stack.
 * ----------------------------------------------------------------------------
 */

void
WindGrabInput(client)
    WindClient client;
{
    ASSERT( client != NULL, "WindGrabInput");
    StackPush( (ClientData) windGrabber, windGrabberStack);
    windGrabber = client;
}



/*
 * ----------------------------------------------------------------------------
 * WindReleaseInput --
 *
 *	Stop grabbing the input (the inverse of WindGrabInput).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The previous grabber (if any) is restored.
 * ----------------------------------------------------------------------------
 */

void
WindReleaseInput(client)
    WindClient client;
{
      ASSERT( client == windGrabber, "WindReleaseInput");
      windGrabber = (WindClient) StackPop(windGrabberStack);
}


/*
 * ----------------------------------------------------------------------------
 * windHelp --
 *
 *	Print out help information for a client.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windHelp(cmd, name, table)
    TxCommand *cmd;		/* Information about command options. */
    char *name;			/* Name of client for whom help is being
				 * printed.
				 */
    char *table[];		/* Client's command table. */
{
    static char *capName = NULL;
    static char patString[200], *pattern;
    bool wiz;
    char **tp;
#define WIZARD_CHAR	'*'

    if (cmd->tx_argc > 2)
    {
	TxError("Usage:  help [pattern]\n");
	return;
    }

    if (SigInterruptPending) return;
    (void) StrDup(&capName, name);
    if (islower(capName[0])) capName[0] += 'A' - 'a';

    TxPrintf("\n");
    if ((cmd->tx_argc == 2) && strcmp(cmd->tx_argv[1], "wizard") == 0)
    {
	pattern = "*";
	wiz = TRUE;
	TxPrintf("Wizard %s Commands\n", capName);
	TxPrintf("----------------------\n");
    }
    else
    {
	if (cmd->tx_argc == 2)
	{
	    pattern = patString;
	    (void) sprintf(patString, "*%.195s*", cmd->tx_argv[1]);
	}
	else
	    pattern = "*";
	wiz = FALSE;
	TxPrintf("%s Commands\n", capName);
	TxPrintf("---------------\n");
    }
    for (tp = table; *tp != (char *) NULL; tp++)
    {
	if (SigInterruptPending) return;
	if (Match(pattern, *tp) && (wiz ^ (**tp != WIZARD_CHAR)) )
	    TxPrintf("%s\n", *tp);
    }
}
