/*
 * DBWbuttons.c --
 *
 * This file provides a general facility whereby clients that are
 * willing to provide handlers for button presses in layout windows
 * can register themselves, and the current handler can be switched
 * between them.  This file also provides the default button handler,
 * which is used to move the box.
 *
 *     ********************************************************************* 
 *     * 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/dbwind/DBWbuttons.c,v 1.2 2001/01/12 22:12:23 jsolomon Exp $";
#endif  not lint

#include <stdio.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "misc/styles.h"
#include "textio/textio.h"
#include "textio/txcommands.h"
#include "utils/utils.h"

/* The arrays below are used to store information about the various
 * button handlers that have registered themselves.
 */

#define MAXBUTTONHANDLERS 10

static char *dbwButtonHandlers[MAXBUTTONHANDLERS];
			/* Name of each button handler:  used to select
			 * that handler as the current one.  A NULL entry
			 * here means that this handler slot isn't in use.
			 */
static char *dbwButtonDoc[MAXBUTTONHANDLERS];
			/* A documentation string for each handler:  tells
			 * what the button pushes and releases mean.
			 */
static Void (*dbwButtonProcs[MAXBUTTONHANDLERS])();
			/* A procedure for each handler that is invoked
			 * on button presses and releases when that handler
			 * is the current one.
			 */
static int dbwButtonCursors[MAXBUTTONHANDLERS];
			/* Cursor shape to use for each handler. */

static int dbwButtonCurrentIndex;
			/* Index of current handler. */
Void (*DBWButtonCurrentProc)();
			/* Current button-handling procedure. */

static int buttonCorner = TOOL_ILG;	/* Nearest corner when button went
					 * down.
					 */

/*
 * ----------------------------------------------------------------------------
 *
 * DBWAddButtonHandler --
 *
 * 	This procedure is called by would-be button handlers to register
 *	themselves.  After a client has called this procedure, it may
 *	make itself the current button handler by calling the procedure
 *	DBWChangeButtonHandler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The client's information is added to the registry of potential
 *	button handlers.  When the handler is made the current one (by
 *	a call to DBWChangeButtonHandler) each button press or release
 *	in a layout window causes proc to be invoked as follows:
 *
 *	Void
 *	proc(w, cmd)
 *	    MagWindow *w;
 *	    TxCommand *cmd;
 *	{
 *	}
 *
 *	W is the window in which the button was pushed, and cmd describes
 *	exactly what happened.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWAddButtonHandler(name, proc, cursor, doc)
    char *name;			/* Name of this button handler.  This name
				 * is what's passed to DBWChangeButtonHandler
				 * to activate the handler.
				 */
    Void (*proc)();		/* Procedure to call on button actions when
				 * this handler is active.
				 */
    int cursor;			/* Cursor shape (e.g. STYLE_CURS_NORMAL) to
				 * use when this handler is active.
				 */
    char *doc;			/* A documentation string for this handler:
				 * describes what the button pushes do when
				 * this handler is active.
				 */
{
    int i;

    for (i = 0; i < MAXBUTTONHANDLERS; i++)
    {
	if (dbwButtonHandlers[i] != NULL) continue;
	(void) StrDup(&dbwButtonHandlers[i], name);
	(void) StrDup(&dbwButtonDoc[i], doc);
	dbwButtonProcs[i] = proc;
	dbwButtonCursors[i] = cursor;
	return;
    }

    TxError("Can't add tool \"%s\":  no space in button handler\n",
	    name);
    TxError("    table.  Get your Magic wizard to increase the size of\n");
    TxError("    MAXBUTTONHANDLERS in DBWbuttons.c\n");
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWChangeButtonHandler --
 *
 * 	Change the active button handler.
 *
 * Results:
 *	The return value is the name of the previous button handler, in
 *	case the caller should wish to restore it.
 *
 * Side effects:
 *	If name is NULL, then the "next" button handler is activated, in a
 *	circular fashion.  If name isn't NULL, then it is the name of a
 *	handler, which is activated.  If the name doesn't match a handler
 *	then a message is printed and the handler isn't changed.
 *
 * ----------------------------------------------------------------------------
 */

char *
DBWChangeButtonHandler(name)
    char *name;			/* Name of new handler.  Must be a unique
				 * abbreviation of a name passed previously
				 * to DBAddButtonHandler, or NULL.
				 */
{
    char *oldName = dbwButtonHandlers[dbwButtonCurrentIndex];
    static int firstTime = TRUE;

    if (name == NULL)
    {
	/* Just rotate to the next available client. */

	while (TRUE)
	{
	    dbwButtonCurrentIndex += 1;
	    if (dbwButtonCurrentIndex >= MAXBUTTONHANDLERS)
		dbwButtonCurrentIndex = 0;
	    if (dbwButtonHandlers[dbwButtonCurrentIndex] == NULL)
		continue;
	    if (firstTime)
	    {
		firstTime = FALSE;
		TxPrintf("Switching to \"%s\" tool.",
		    dbwButtonHandlers[dbwButtonCurrentIndex]);
		TxPrintf("  If you didn't really want to switch,\n");
		TxPrintf("    type \":tool box\" to");
		TxPrintf(" switch back to the box tool.\n");
	    }
	    else
	    {
		TxPrintf("Switching to \"%s\" tool.\n",
			dbwButtonHandlers[dbwButtonCurrentIndex]);
	    }
	    break;
	}
    }
    else
    {
	int i, match, length;

	match = -1;
	length = strlen(name);
	for (i = 0; i < MAXBUTTONHANDLERS; i++)
	{
	    if (dbwButtonHandlers[i] == NULL) continue;
	    if (strncmp(name, dbwButtonHandlers[i], length) != 0) continue;
	    if (match >= 0)
	    {
		TxError("\"%s\" is an ambiguous tool name.", name);
		match = -2;
		break;
	    }
	    match = i;
	}

	if (match == -1)
	    TxError("\"%s\" isn't a tool name.", name);
	if (match < 0)
	{
	    TxError("  The legal names are:\n");
	    for (i = 0; i < MAXBUTTONHANDLERS; i++)
	    {
		if (dbwButtonHandlers[i] == NULL) continue;
		TxError("    %s\n", dbwButtonHandlers[i]);
	    }
	    return oldName;
	}
	dbwButtonCurrentIndex = match;
    }

    GrSetCursor(dbwButtonCursors[dbwButtonCurrentIndex]);
    DBWButtonCurrentProc = dbwButtonProcs[dbwButtonCurrentIndex];
    return oldName;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWPrintButtonDoc --
 *
 * 	This procedure prints out documentation for the current
 *	button handler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff gets printed on the tty, ostensibly describing what
 *	the current buttons do.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWPrintButtonDoc()
{
    TxPrintf("%s", dbwButtonDoc[dbwButtonCurrentIndex]);
}


/*
 * ----------------------------------------------------------------------------
 *	dbwButtonSetCursor --
 *
 * 	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.
 * ----------------------------------------------------------------------------
 */

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

{
    switch (corner)
    {
	case TOOL_BL:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_LLBOX);
	    else
		GrSetCursor(STYLE_CURS_LLCORNER);
	    break;
	case TOOL_BR:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_LRBOX);
	    else
		GrSetCursor(STYLE_CURS_LRCORNER);
	    break;
	case TOOL_TL:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_ULBOX);
	    else
		GrSetCursor(STYLE_CURS_ULCORNER);
	    break;
	case TOOL_TR:
	    if (button == TX_LEFT_BUTTON)
		GrSetCursor(STYLE_CURS_URBOX);
	    else
		GrSetCursor(STYLE_CURS_URCORNER);
	    break;
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBWBoxHandler --
 *
 * 	This procedure is called to handle button actions in layout
 *	windows when the "box" handler is active.  It adjusts the box
 *	position and size.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Left button:  used to move the whole box by the lower-left corner.
 *	Right button: used to re-size the box 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.
 *	Middle button: used to paint whatever layers are underneath the
 *		crosshair.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */
Void
DBWBoxHandler(w, cmd)
    MagWindow *w;			/* Window containing cursor. */
    TxCommand *cmd;		/* Describes what happened. */
{
    int button = cmd->tx_button;

    if (button == TX_MIDDLE_BUTTON)
    {
	if (cmd->tx_buttonAction == TX_BUTTON_DOWN)
	    CmdPaintButton(w, &cmd->tx_p);
	return;
    }

    if (cmd->tx_buttonAction == TX_BUTTON_DOWN)
    {
	if ((WindNewButtons & (TX_LEFT_BUTTON|TX_RIGHT_BUTTON))
		== (TX_LEFT_BUTTON|TX_RIGHT_BUTTON))
	{
	    /* Both buttons are now down.  In this case, the FIRST
	     * button pressed determines whether we move or size,
	     * and the second button is just used as a signal to pick
	     * the closest corner.
	     */

	    buttonCorner = ToolGetCorner(&cmd->tx_p);
	    if (button == TX_LEFT_BUTTON) button = TX_RIGHT_BUTTON;
	    else button = TX_LEFT_BUTTON;
	}
	else if (button == TX_LEFT_BUTTON) buttonCorner = TOOL_BL;
	else buttonCorner = TOOL_TR;
	dbwButtonSetCursor(button, buttonCorner);
    }
    else
    {
	/* A button has just come up.  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 box).
	 */

	if (WindNewButtons != 0)
	{
	    if (button == TX_LEFT_BUTTON)
		dbwButtonSetCursor(TX_RIGHT_BUTTON, buttonCorner);
	    else dbwButtonSetCursor(TX_LEFT_BUTTON, buttonCorner);
	    return;
	}

	/* The last button has been released.  Reset the cursor to normal
	 * form and then move or size the box.
	 */

	GrSetCursor(STYLE_CURS_NORMAL);
	switch (button)
	{
	    case TX_LEFT_BUTTON:
		ToolMoveBox(buttonCorner, &cmd->tx_p, TRUE, (CellDef *) NULL);
		break;
	    case TX_RIGHT_BUTTON:
		ToolMoveCorner(buttonCorner, &cmd->tx_p, TRUE,
			(CellDef *) NULL);
	}
    }
}
