/*
 * DBWbuttons.c --
 *
 * This file provides a general facility whereby clients that are
 * willing to provide handlers for button presses in layout windows
 * can 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[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/dbwind/DBWbuttons.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
#endif  /* not lint */

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

#include "tcltk/tclmagic.h"
#include "utils/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 "utils/styles.h"
#include "textio/textio.h"
#include "textio/txcommands.h"
#include "utils/utils.h"

/* C99 compat */
#include "commands/commands.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 cb_database_buttonhandler_t 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. */
cb_database_buttonhandler_t 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:
 *
 *	int
 *	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(
    const char *name,		/* Name of this button handler.  This name
				 * is what's passed to DBWChangeButtonHandler
				 * to activate the handler.
				 */
    const cb_database_buttonhandler_t 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.
				 */
    const 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;
	StrDup(&dbwButtonHandlers[i], name);
	if (doc != NULL)
	    StrDup(&dbwButtonDoc[i], doc);
	else
	    dbwButtonDoc[i] = (char *)NULL;
	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;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWGetButtonHandler --
 *
 *	Return the name of the current button handler.  This does the same
 *	thing as DBWChangeButtonHandler(name) with an invalid name, but
 *	without printing the error messages.  However, if magic is running
 *	with the Tcl interpreter wrapper, then return the string value of
 *	$Opts(tool), if it exists.
 *
 * Results:
 *	A pointer to the name of the current button handler.
 *
 * Side effects:
 *	None.	
 *
 * ----------------------------------------------------------------------------
 */

char *
DBWGetButtonHandler()
{
#ifdef MAGIC_WRAPPER
    char *toolName;
    toolName = (char *)Tcl_GetVar2(magicinterp, "Opts", "tool", TCL_GLOBAL_ONLY);
    if (toolName != NULL) return toolName;
#endif
    
    return dbwButtonHandlers[dbwButtonCurrentIndex];
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWButtonHandlerIndex()
 *
 *	Given a string, return the index of the button handler.  If the
 *	string does not correspond to any button handler name, then
 *	return -1.
 *
 * Results:
 *	Index of button handler, if it exists;  -1 otherwise.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

int
DBWButtonHandlerIndex(char *toolName)
{
    int i;

    for (i = 0; i < MAXBUTTONHANDLERS; i++)
    {
	if (dbwButtonHandlers[i] == NULL) return -1;
	else if (!strcmp(toolName, dbwButtonHandlers[i])) return i;
    }
    return -1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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()
{
    if (dbwButtonDoc[dbwButtonCurrentIndex])
	TxPrintf("%s", dbwButtonDoc[dbwButtonCurrentIndex]);
    else
	TxPrintf("(no usage information)\n");
}


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

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

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)
	    CmdPaintEraseButton(w, &cmd->tx_p, TRUE, TRUE);
	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);
	}
    }
}
