/* windMain.c -
 *
 *	This package implements overlapping windows for the
 *	Magic VLSI layout system.
 *
 * Design:
 *	Windows are structures that are kept in a doubly linked list.
 *	Windows near the front of the list are on top of windows further
 *	towards the tail of the list.  Each window has some information
 *	about what is in the window, as well as the size of the window
 *	(both unclipped and clipped to accommodate windows that overlay it).
 *	Transforms control what portion of the window's contents show up
 *	on the screen, and at what magnification.
 *
 *	Each window is owned by a client (the database, a menu package, etc.).
 *	The window package is notified of a new client by the AddClient
 *	call.  The client supplies routines to redisplay the contents of
 *	a window, and to do other things with the window (such as delete it).
 *	Each window is a view onto a surface maintained by the client.  The
 *	client may be asked to redisplay any part of this surface at any time.
 *	The client must also supply each window with an ID that uniquely
 *	identifies the surface.
 *
 *	There are currently two types of window packages supported:  Magic
 *	windows (implemented here) and Sun Windows.  In Magic windows, all
 * 	windows use the screen's coordinate system.  In Sun Windows, each
 *	window has it's own coordinate system with (0, 0) being at the lower
 *	left corner.  Also, under Sun Windows some of the screen managment
 *	stuff (such as clipping to obscuring areas and drawing of the
 *	screen background color) is ignored by us.
 *
 *
 *     *********************************************************************
 *     * 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>	/* for isalnum() */

#include "utils/magic.h"
#include "utils/geometry.h"
#include "graphics/glyphs.h"
#include "windows/windows.h"
#include "windows/windInt.h"
#include "utils/stack.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "textio/textio.h"
#include "graphics/graphics.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#include "textio/txcommands.h"

/* C99 compat */
#include "utils/main.h"

/* The type of windows that this package will implement */
int WindPackageType = WIND_MAGIC_WINDOWS;

/* The size of our scroll bars -- may be set externally (see windows.h)
 */
int WindScrollBarWidth = 7;

/* ------ Internal variables that are global within the window package ----- */
clientRec *windFirstClientRec = NULL;	/* the head of the linked list
					 * of clients
					 */
MagWindow *windTopWindow = NULL;		/* the topmost window */
MagWindow *windBottomWindow = NULL;	/* ...and the bottom window */
extern Plane *windRedisplayArea;	/* See windDisplay.c for details. */


/*
 * ----------------------------------------------------------------------------
 * WindInit --
 *
 *	Initialize the window package.  No windows are created, but the
 *	package will be initialized so that it can do these things in the
 *	future.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Variables internal to the window package are initialized.
 * ----------------------------------------------------------------------------
 */

void
WindInit()
{
    extern Stack *windGrabberStack;
    Rect ts;
    char glyphName[30];

    windClientInit();
    windGrabberStack = StackNew(2);
    windRedisplayArea = DBNewPlane((ClientData) TT_SPACE);

    sprintf(glyphName, "windows%d", WindScrollBarWidth);
    if (!GrReadGlyphs(glyphName, ".", SysLibPath, &windGlyphs))
	MainExit(1);
    GrTextSize("XWyqP", GR_TEXT_DEFAULT, &ts);
    windCaptionPixels = ts.r_ytop - ts.r_ybot + 3;
    WindAreaChanged((MagWindow *) NULL, (Rect *) NULL);
}


/*
 * ----------------------------------------------------------------------------
 * WindAddClient --
 *
 *	Add a new client of the window package.  The client must supply a
 *	set of routines, as described below.
 *
 * Results:
 *	A unique ID (of type WindClient) is returned.
 *	This is used to identify the client in future calls to the window
 *	package.
 *
 * Routines supplied:
 *
 *	( A new window was just created for this client.  Do things to
 *	  initialize the window, such as filling in the caption and making
 *	  the contents be empty. The client may refuse to create a new
 *	  window by returning FALSE, otherwise the client should return
 *	  TRUE.  The client will get passed argc and argv, with the command
 *	  name stripped off.  The client may do whatever it wants with this.
 *	  It may even modify parts of the window record -- such as changing
 *	  the window's location on the screen.)
 *
 *	bool
 *	create(w, argc, argv)
 *	    MagWindow *w;
 *	    int argc;
 *	    char *argv[];
 *	{
 *	}
 *
 *	( One of the client's windows is about to be deleted.  Do whatever
 *	  needs to be done, such as freeing up dynamically allocated data
 *	  structures. Fields manipulated by the window package, such as
 *	  the caption, should not be freed by the client.  The client should
 *	  normally return TRUE.  If the client returns FALSE, the window
 *	  manager will refuse the request to delete the window.)
 *
 *	bool
 *	delete(w)
 *	    MagWindow *w;
 *	{
 *	}
 *
 *	( Redisplay an area of the screen.  The client is passed the window,
 *	  the area in his coordinate system, and a clipping rectangle in
 *	  screen coordinates. )
 *
 *	redisplay(w, clientArea, screenArea)
 *	    MagWindow *w;
 *	    Rect *clientArea, *screenArea;
 *	{
 *	}
 *
 *
 *	( The window is about to be moved or resized.  This procedure will
 *	  be called twice.
 *
 *	  The first time (with 'final' == FALSE), the window
 *	  will be passed in 'w' as it is now and a suggested new w_screenarea
 *	  is passed in 'newpos'.  The client is free to modify 'newpos' to
 *	  be whatever screen location it desires.  The routine should not
 *	  pass 'w' to any window procedure such as windMove since 'w' has
 *	  the old transform, etc. instead of the new one.
 *
 *	  On the second call ('final' == TRUE), the window 'w' has all of
 *	  it's fields updated, newpos is equal to w->w_frameArea, and the
 *	  client is free to do things like windMove which require a window
 *	  as an argument.  It should not modify newpos.
 *
 *	reposition(w, newpos, final)
 *	    MagWindow *w;
 *	    Rect *newpos	-- new w_framearea (screen area of window)
 *	    bool final;
 *	{
 *	}
 *
 *
 *	( A command has been issued to this window.  The client should
 *	  process it.  It is split into Unix-style argc and argv words. )
 *
 *	command(w, client, cmd)
 *	    MagWindow *w;
 *	    TxCommand *cmd;
 *	{
 *	}
 *
 *	( A command has just finished.  Update any screen info that may have
 *	  been changed as a result. )
 *
 *	update()
 *	{
 *	}
 *
 * Side effects:
 *	Internal tables are expanded to include the new client.
 * ----------------------------------------------------------------------------
 */

WindClient
WindAddClient(clientName, create, delete, redisplay, command, update,
		exitproc, reposition, icon)
    char *clientName;		/* A textual name for the client.  This
				 * name will be visable in the user
				 * interface as the name to use to switch
				 * a window over to a new client
				 */
    bool (*create)();
    bool (*delete)();
    void (*redisplay)();
    void (*command)();
    void (*update)();
    bool (*exitproc)();
    void (*reposition)();
    GrGlyph *icon;		/* An icon to draw when the window is closed.
				 * (currently for Sun Windows only).
				 */
{
    clientRec *res;

    ASSERT( (clientName != NULL), "WindAddClient");
    ASSERT( (command != NULL), "WindAddClient");

    res = (clientRec *) mallocMagic(sizeof(clientRec));
    res->w_clientName = clientName;
    res->w_create = create;
    res->w_delete = delete;
    res->w_redisplay = redisplay;
    res->w_command = command;
    res->w_update = update;
    res->w_exit = exitproc;
    res->w_reposition = reposition;
    res->w_icon = icon;
    res->w_nextClient = windFirstClientRec;

    /* The command and function tables are dynamically allocated.  */
    /* Commands and functions should be registered with the client */
    /* using the WindAddCommand() function.			   */

    const char **newtable = (const char **)mallocMagic(sizeof(char *));
    newtable[0] = NULL;
    res->w_commandTable = newtable;
    res->w_functionTable = (void (**)())mallocMagic(sizeof(void (*)()));
    *(res->w_functionTable) = NULL;

    windFirstClientRec = res;

    return (WindClient) res;
}


/*
 * ----------------------------------------------------------------------------
 * WindGetClient --
 *
 *	Looks up the unique ID of a client of the window package.
 *
 * Results:
 *	A variable of type WindClient is returned if the client was found,
 *	otherwise NULL is returned.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

WindClient
WindGetClient(clientName, exact)
    char *clientName;	/* the textual name of the client */
    bool exact;		/* must the name be exact, or are abbreviations allowed */
{
    clientRec *cr, *found;
    int length;

    /* Accept only an exact match */

    if (exact)
    {
	for (cr = windFirstClientRec; cr != (clientRec *) NULL;
		cr = cr->w_nextClient)
	    if (!strcmp(clientName, cr->w_clientName))
		return (WindClient)cr;
	return (WindClient) NULL;
    }

    /* Accept any unique abbreviation */

    found = NULL;
    length = strlen(clientName);
    for (cr = windFirstClientRec; cr != (clientRec *) NULL;
	    cr = cr->w_nextClient)
    {
	if (!strncmp(clientName, cr->w_clientName, length))
	{
	    if (found != NULL) return (WindClient) NULL;
	    found = cr;
	}
    }

    return (WindClient) found;
}

/*
 * ----------------------------------------------------------------------------
 * WindGetClientName --
 *
 *	Looks up the name of a client from the unique client ID for a window.
 *
 * Results:
 *	A pointer to the client name is returned.  If the client is NULL,
 *	then NULL is returned.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

char *
WindGetClientName(client)
    WindClient client;
{
    clientRec *clientrec  = (clientRec *)client;
    if (client == (WindClient)NULL) return NULL;
    return clientrec->w_clientName;
}

/*
 * ----------------------------------------------------------------------------
 * WindNextClient --
 *
 *	Return the w_nextClient record to the caller as a WindClient
 *	variable.  If "client" is 0, pass the first client record.
 *	This allows the calling routine to enumerate all the known
 *	clients.
 *
 * Results:
 *	Type WindClient is returned.  If the end of the list is
 *	reached, (WindClient)NULL (0) is returned.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

WindClient
WindNextClient(client)
    WindClient client;
{
    clientRec *cr = (clientRec *)client;
    int length;

    if (cr == NULL)
	return (WindClient)windFirstClientRec;
    else
	return (WindClient)(cr->w_nextClient);
}

/*
 * ----------------------------------------------------------------------------
 * WindPrintClientList --
 *
 *	Print the name of each client of the window package.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
WindPrintClientList(wizard)
    bool wizard;	/* If true print the names of ALL clients, even those
			 * that don't have user-visable windows */
{
    clientRec *cr;

    for (cr = windFirstClientRec; cr != (clientRec *) NULL;
	    cr = cr->w_nextClient) {
	if (wizard || (cr->w_clientName[0] != '*'))
	    TxError("	%s\n", cr->w_clientName);
	}
}

/*
 * ----------------------------------------------------------------------------
 * WindExecute --
 *
 *	Execute the command associated with a windClient
 *
 * Results:
 *	Returns the command index on success.  Returns -1 if the
 *	command was not found in the client's command list.  Returns
 *	-2 if the procedure was sent an empty command.
 *
 * Side effects:
 *	Whatever is done by the command execution.
 *
 * ----------------------------------------------------------------------------
 */

int
WindExecute(w, rc, cmd)
    MagWindow *w;
    WindClient rc;
    TxCommand *cmd;
{
    int cmdNum;
    clientRec *client = (clientRec *) rc;
    const char * const *commandTable = client->w_commandTable;
    void (**functionTable)() = client->w_functionTable;

    if (cmd->tx_argc > 0)
    {
	cmdNum = Lookup(cmd->tx_argv[0], commandTable);

	if (cmdNum >= 0)
	{
	    (*functionTable[cmdNum])(w, cmd);
	    return cmdNum;
	}
	return -1;
    }
    return -2;
}

/*
 * ----------------------------------------------------------------------------
 * WindAddCommand --
 *
 *	Add a command to the indicated client.  The command is passed
 *	in "text", which also contains the (1-line) help text for the
 *	command.  "func" is a function pointer, and "volatile" is TRUE
 *	if the command "text" is dynamically allocated and must be
 *	copied before adding to the client.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The memory allocated to the command and function pointers may
 *	be reallocated and the entries in the client record updated.
 *
 * ----------------------------------------------------------------------------
 */

void
WindAddCommand(rc, text, func, dynamic)
    WindClient rc;
    char *text;
    void (*func)();
    bool dynamic;
{
    int cidx, numCommands = 0;
    clientRec *client = (clientRec *) rc;
    const char * const *commandTable = client->w_commandTable;
    void (**functionTable)() = client->w_functionTable;
    const char **newcmdTable;
    void (**newfnTable)();

    /* Find the number of commands and functions, increment by one, and */
    /* Allocate a new array of pointers.				*/

    while (commandTable[numCommands] != NULL) numCommands++;
    numCommands++;

    newcmdTable = (const char **)mallocMagic((numCommands + 1) * sizeof(char *));
    newfnTable = (void (**)())mallocMagic((numCommands + 1) * sizeof(void (*)()));

    /* Copy the old values, inserting the new command in alphabetical	*/
    /* order.								*/

    for (cidx = 0; (commandTable[cidx] != NULL) &&
		(strcmp(commandTable[cidx], text) < 0); cidx++)
    {
	newcmdTable[cidx] = commandTable[cidx];
	newfnTable[cidx] = functionTable[cidx];
    }

    if (dynamic)
	newcmdTable[cidx] = StrDup(NULL, text);
    else
	newcmdTable[cidx] = text;
    newfnTable[cidx] = func;

    for (; commandTable[cidx] != NULL; cidx++)
    {
	newcmdTable[cidx + 1] = commandTable[cidx];
	newfnTable[cidx + 1] = functionTable[cidx];
    }

    newcmdTable[cidx + 1] = NULL;

    /* Release memory for the original pointers, and replace the	*/
    /* pointers in the client record.					*/

    freeMagic((void*)commandTable);
    freeMagic(functionTable);

    client->w_commandTable = newcmdTable;
    client->w_functionTable = newfnTable;
}

/*
 * ----------------------------------------------------------------------------
 * WindReplaceCommand --
 *
 *	Change the function for the indicated command.  This routine
 *	is mainly used by the Tcl module interface, where commands
 *	are registered with the command interpreter pointing to an
 *	"auto" function which loads the module, then calls this routine
 *	to replace the auto-load function with the real one.
 *
 *      Note that this routine matches to the length of "command", then
 *	checks one character beyond in the command table to ensure that
 *	we don't inadvertently change a command which happens to be a
 *	substring of the intended command.  In cases where this is
 *	intended (e.g., "ext" and "extract"), the routine must be called
 *	separately for each command string.
 *
 * Results:
 *	0 on success, -1 if the command was not found.
 *
 * Side effects:
 *	The clientRec structure for the DBWind interface is altered.
 *
 * ----------------------------------------------------------------------------
 */

int
WindReplaceCommand(rc, command, newfunc)
    WindClient rc;
    char *command;
    void (*newfunc)();
{
    int cidx, clen;
    clientRec *client = (clientRec *) rc;
    const char * const *commandTable = client->w_commandTable;
    void (**functionTable)() = client->w_functionTable;

    clen = strlen(command);

    for (cidx = 0; commandTable[cidx] != NULL; cidx++)
	if (!strncmp(commandTable[cidx], command, clen))
	    if (!isalnum(*(commandTable[cidx] + clen)))
	    {
		functionTable[cidx] = newfunc;
		return 0;
	    }

    return -1;
}

/*
 * ----------------------------------------------------------------------------
 * WindGetCommandTable --
 *
 *	For functions wishing to parse the command table of a client
 *	directly, this routine returns a pointer to the top of the
 *	table.  The only purpose of this routine is to export the
 *	w_commandTable value inside the clientRec structure, which is
 *	not itself exported.
 *
 * Results:
 *	A pointer to the top of the command table of the indicated
 *	client.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

const char * const *
WindGetCommandTable(rc)
    WindClient rc;
{
    clientRec *client = (clientRec *) rc;
    return client->w_commandTable;
}
